diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..22a1b15fb92547b0f6ce0e23d4b5e733688ec307 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,57 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2415-1-desk.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2417-1-goldfinch,[[:space:]]Carduelis[[:space:]]carduelis.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2418-1-bell[[:space:]]pepper.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2419-1-bicycle.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2428-1-desk.jpg filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-cutoff/images/cover.jpg filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-cutoff/images/sample-1.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-cutoff/images/sample-2.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-cutoff/images/sample-3.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-cutoff/images/sample-4.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-cutoff/images/sample-4_small.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-cutoff/images/sample-5.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-cutoff/images/sample-5_small.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-fabric/static/example_2_default.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-fabric/static/example_2_feedback.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-fabric/static/example_3_10.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-fabric/static/example_3_feedback.png filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-fabric/static/fabric_demo.gif filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-llul/images/llul_yuv420p.mp4 filter=lfs diff=lfs merge=lfs -text +extensions/CHECK/sd-webui-llul/images/mask_effect.jpg filter=lfs diff=lfs merge=lfs -text +extensions/diffusion-noise-alternatives-webui/images/ColorGrade.png filter=lfs diff=lfs merge=lfs -text +extensions/diffusion-noise-alternatives-webui/images/GrainCompare.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-civitai-browser-plus/aria2/lin/aria2 filter=lfs diff=lfs merge=lfs -text +extensions/sd-civitai-browser-plus/aria2/win/aria2.exe filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-cads/samples/comparison.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-cads/samples/grid-7069.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-cads/samples/grid-7070.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-dycfg/images/05.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-dycfg/images/09.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-image-comparison/tab.gif filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-incantations/images/xyz_grid-0463-3.jpg filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-incantations/images/xyz_grid-0469-4.jpg filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-incantations/images/xyz_grid-2652-1419902843-cinematic[[:space:]]4K[[:space:]]photo[[:space:]]of[[:space:]]a[[:space:]]dog[[:space:]]riding[[:space:]]a[[:space:]]bus[[:space:]]and[[:space:]]eating[[:space:]]cake[[:space:]]and[[:space:]]wearing[[:space:]]headphones.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-incantations/images/xyz_grid-2660-1590472902-A[[:space:]]photo[[:space:]]of[[:space:]]a[[:space:]]lion[[:space:]]and[[:space:]]a[[:space:]]grizzly[[:space:]]bear[[:space:]]and[[:space:]]a[[:space:]]tiger[[:space:]]in[[:space:]]the[[:space:]]woods.jpg filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-incantations/images/xyz_grid-3040-1-a[[:space:]]puppy[[:space:]]and[[:space:]]a[[:space:]]kitten[[:space:]]on[[:space:]]the[[:space:]]moon.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-incantations/images/xyz_grid-3348-1590472902-A[[:space:]]photo[[:space:]]of[[:space:]]a[[:space:]]lion[[:space:]]and[[:space:]]a[[:space:]]grizzly[[:space:]]bear[[:space:]]and[[:space:]]a[[:space:]]tiger[[:space:]]in[[:space:]]the[[:space:]]woods.jpg filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-pag/images/xyz_grid-3040-1-a[[:space:]]puppy[[:space:]]and[[:space:]]a[[:space:]]kitten[[:space:]]on[[:space:]]the[[:space:]]moon.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-semantic-guidance/samples/comparison.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-semantic-guidance/samples/enhance.jpg filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-smea/sample.jpg filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-smea/sample2.jpg filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-vectorscope-cc/samples/XYZ.jpg filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-xl_vec/images/crop_top.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-xl_vec/images/mult.png filter=lfs diff=lfs merge=lfs -text +extensions/sd-webui-xl_vec/images/original_size.png filter=lfs diff=lfs merge=lfs -text +extensions/ssasd/images/sample.png filter=lfs diff=lfs merge=lfs -text +extensions/ssasd/images/sample2.png filter=lfs diff=lfs merge=lfs -text +extensions/ssasd/images/sample3.png filter=lfs diff=lfs merge=lfs -text +extensions/stable-diffusion-webui-composable-lora/readme/changelog_2023-04-08.png filter=lfs diff=lfs merge=lfs -text +extensions/stable-diffusion-webui-composable-lora/readme/fig11.png filter=lfs diff=lfs merge=lfs -text +extensions/stable-diffusion-webui-composable-lora/readme/fig12.png filter=lfs diff=lfs merge=lfs -text +extensions/stable-diffusion-webui-composable-lora/readme/fig13.png filter=lfs diff=lfs merge=lfs -text +extensions/stable-diffusion-webui-composable-lora/readme/fig8.png filter=lfs diff=lfs merge=lfs -text +extensions/stable-diffusion-webui-composable-lora/readme/fig9.png filter=lfs diff=lfs merge=lfs -text diff --git a/extensions/1-sd-dynamic-thresholding/.github/FUNDING.yml b/extensions/1-sd-dynamic-thresholding/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..26b92efb8256bf0f862bb45af71b7e57f391efaa --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/.github/FUNDING.yml @@ -0,0 +1 @@ +github: mcmonkey4eva diff --git a/extensions/1-sd-dynamic-thresholding/.github/workflows/publish.yml b/extensions/1-sd-dynamic-thresholding/.github/workflows/publish.yml new file mode 100644 index 0000000000000000000000000000000000000000..c1f20b5e40e99ebb84f9b87a84792054631e3750 --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/.github/workflows/publish.yml @@ -0,0 +1,21 @@ +name: Publish to Comfy registry +on: + workflow_dispatch: + push: + branches: + - master + paths: + - "pyproject.toml" + +jobs: + publish-node: + name: Publish Custom Node to registry + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Publish Custom Node + uses: Comfy-Org/publish-node-action@main + with: + ## Add your own personal access token to your Github Repository secrets and reference it here. + personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} diff --git a/extensions/1-sd-dynamic-thresholding/.gitignore b/extensions/1-sd-dynamic-thresholding/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c18dd8d83ceed1806b50b0aaa46beb7e335fff13 --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/extensions/1-sd-dynamic-thresholding/LICENSE.txt b/extensions/1-sd-dynamic-thresholding/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..560a9bf3eafa56c30f36a6f8b70304eadbd66cc2 --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2023 Alex "mcmonkey" Goodwin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/1-sd-dynamic-thresholding/README.md b/extensions/1-sd-dynamic-thresholding/README.md new file mode 100644 index 0000000000000000000000000000000000000000..647e254f76ba26c829bada35ffa42bc924a81745 --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/README.md @@ -0,0 +1,120 @@ +# Stable Diffusion Dynamic Thresholding (CFG Scale Fix) + +### Concept + +Extension for [SwarmUI](https://github.com/mcmonkeyprojects/SwarmUI), [ComfyUI](https://github.com/comfyanonymous/ComfyUI), and [AUTOMATIC1111 Stable Diffusion WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) that enables a way to use higher CFG Scales without color issues. + +This works by clamping latents between steps. You can read more [here](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/3962) or [here](https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/3268) or [this tweet](https://twitter.com/Birchlabs/status/1582165379832348672). + +-------------- + +### Credit + +The core functionality of this PR was originally developed by [Birch-san](https://github.com/Birch-san) and ported to the WebUI by [dtan3847](https://github.com/dtan3847), then converted to an Auto WebUI extension and given a UI by [mcmonkey4eva](https://github.com/mcmonkey4eva), further development and research done by [mcmonkey4eva](https://github.com/mcmonkey4eva) and JDMLeverton. Ported by ComfyUI by [TwoDukes](https://github.com/TwoDukes) and [mcmonkey4eva](https://github.com/mcmonkey4eva). Ported to SwarmUI by [mcmonkey4eva](https://github.com/mcmonkey4eva). + +-------------- + +### Examples + +![img](github/cat_demo_1.jpg) + +![img](github/ui.png) + + +-------------- + +### Demo Grid + +View at . + +![img](github/grid_preview.png) + +(Was generated via [this YAML config](https://gist.github.com/mcmonkey4eva/fccd29172f44424dfc0217a482c824f6) for the [Infinite Grid Generator](https://github.com/mcmonkeyprojects/sd-infinity-grid-generator-script)) + +-------------- + +### Installation and Usage + +#### SwarmUI + +- Supported out-of-the-box on default installations. + - If using a custom installation, just make sure the backend you use has this repo installed per the instructions specific to the backend as written below. +- It's under the "Display Advanced Options" parameter checkbox. + +#### Auto WebUI + +- You must have the [AUTOMATIC1111 Stable Diffusion WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) already installed and working. Refer to that project's readme for help with that. +- Open the WebUI, go to the `Extensions` tab +- -EITHER- Option **A**: + - go to the `Available` tab with + - click `Load from` (with the default list) + - Scroll down to find `Dynamic Thresholding (CFG Scale Fix)`, or use `CTRL+F` to find it +- -OR- Option **B**: + - Click on `Install from URL` + - Copy/paste this project's URL into the `URL for extension's git repository` textbox: `https://github.com/mcmonkeyprojects/sd-dynamic-thresholding` +- Click `Install` +- Restart or reload the WebUI +- Go to txt2img or img2img +- Check the `Enable Dynamic Thresholding (CFG Scale Fix)` box +- Read the info on-page and set the sliders where you want em. +- Click generate. + + +#### ComfyUI + +- Must have [ComfyUI](https://github.com/comfyanonymous/ComfyUI) already installed and working. Refer to that project's readme for help with that. +- -EITHER- Option **A**: (TODO: Manager install) +- -OR- Option **B**: + - `cd ComfyUI/custom_nodes` + - `git clone https://github.com/mcmonkeyprojects/sd-dynamic-thresholding` + - restart ComfyUI + - Add node `advanced/mcmonkey/DynamicThresholdingSimple` (or `Full`) + - Link your model to the input, and then link the output model to your KSampler's input + +![img](github/comfy_node.png) + +-------------- + +### Supported Auto WebUI Extensions + +- This can be configured within the [Infinity Grid Generator](https://github.com/mcmonkeyprojects/sd-infinity-grid-generator-script#supported-extensions) extension, see the readme of that project for details. + +### ComfyUI Compatibility + +- This would work with any variant of the `KSampler` node, including custom ones, so long as they do not totally override the internal sampling function (most don't). + +---------------------- + +### Licensing pre-note: + +This is an open source project, provided entirely freely, for everyone to use and contribute to. + +If you make any changes that could benefit the community as a whole, please contribute upstream. + +### The short of the license is: + +You can do basically whatever you want, except you may not hold any developer liable for what you do with the software. + +### The long version of the license follows: + +The MIT License (MIT) + +Copyright (c) 2023 Alex "mcmonkey" Goodwin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/1-sd-dynamic-thresholding/__init__.py b/extensions/1-sd-dynamic-thresholding/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..dda9e43f908bb0783616c3c7178e7838ccb9d205 --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/__init__.py @@ -0,0 +1,6 @@ +from . import dynthres_comfyui + +NODE_CLASS_MAPPINGS = { + "DynamicThresholdingSimple": dynthres_comfyui.DynamicThresholdingSimpleComfyNode, + "DynamicThresholdingFull": dynthres_comfyui.DynamicThresholdingComfyNode, +} diff --git a/extensions/1-sd-dynamic-thresholding/__pycache__/dynthres_core.cpython-310.pyc b/extensions/1-sd-dynamic-thresholding/__pycache__/dynthres_core.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6564d09b5ef7bc3af65c4f49531e2c7d3265bf7d Binary files /dev/null and b/extensions/1-sd-dynamic-thresholding/__pycache__/dynthres_core.cpython-310.pyc differ diff --git a/extensions/1-sd-dynamic-thresholding/__pycache__/dynthres_unipc.cpython-310.pyc b/extensions/1-sd-dynamic-thresholding/__pycache__/dynthres_unipc.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d5f956fa6bf28889dcd198978f73409ae3fd91a Binary files /dev/null and b/extensions/1-sd-dynamic-thresholding/__pycache__/dynthres_unipc.cpython-310.pyc differ diff --git a/extensions/1-sd-dynamic-thresholding/dynthres_comfyui.py b/extensions/1-sd-dynamic-thresholding/dynthres_comfyui.py new file mode 100644 index 0000000000000000000000000000000000000000..58934906cd44c278b4a8876faed7bdc4e71d10f4 --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/dynthres_comfyui.py @@ -0,0 +1,86 @@ +from .dynthres_core import DynThresh + +class DynamicThresholdingComfyNode: + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "mimic_scale": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.5}), + "threshold_percentile": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "mimic_mode": (DynThresh.Modes, ), + "mimic_scale_min": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.5}), + "cfg_mode": (DynThresh.Modes, ), + "cfg_scale_min": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.5}), + "sched_val": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}), + "separate_feature_channels": (["enable", "disable"], ), + "scaling_startpoint": (DynThresh.Startpoints, ), + "variability_measure": (DynThresh.Variabilities, ), + "interpolate_phi": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + } + } + + RETURN_TYPES = ("MODEL",) + FUNCTION = "patch" + CATEGORY = "advanced/mcmonkey" + + def patch(self, model, mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, cfg_scale_min, sched_val, separate_feature_channels, scaling_startpoint, variability_measure, interpolate_phi): + + dynamic_thresh = DynThresh(mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, cfg_scale_min, sched_val, 0, 999, separate_feature_channels == "enable", scaling_startpoint, variability_measure, interpolate_phi) + + def sampler_dyn_thresh(args): + input = args["input"] + cond = input - args["cond"] + uncond = input - args["uncond"] + cond_scale = args["cond_scale"] + time_step = model.model.model_sampling.timestep(args["sigma"]) + time_step = time_step[0].item() + dynamic_thresh.step = 999 - time_step + + if cond_scale == mimic_scale: + return input - (uncond + (cond - uncond) * cond_scale) + else: + return input - dynamic_thresh.dynthresh(cond, uncond, cond_scale, None) + + m = model.clone() + m.set_model_sampler_cfg_function(sampler_dyn_thresh) + return (m, ) + +class DynamicThresholdingSimpleComfyNode: + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "mimic_scale": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.5}), + "threshold_percentile": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + } + } + + RETURN_TYPES = ("MODEL",) + FUNCTION = "patch" + CATEGORY = "advanced/mcmonkey" + + def patch(self, model, mimic_scale, threshold_percentile): + + dynamic_thresh = DynThresh(mimic_scale, threshold_percentile, "CONSTANT", 0, "CONSTANT", 0, 0, 0, 999, False, "MEAN", "AD", 1) + + def sampler_dyn_thresh(args): + input = args["input"] + cond = input - args["cond"] + uncond = input - args["uncond"] + cond_scale = args["cond_scale"] + time_step = model.model.model_sampling.timestep(args["sigma"]) + time_step = time_step[0].item() + dynamic_thresh.step = 999 - time_step + + if cond_scale == mimic_scale: + return input - (uncond + (cond - uncond) * cond_scale) + else: + return input - dynamic_thresh.dynthresh(cond, uncond, cond_scale, None) + + m = model.clone() + m.set_model_sampler_cfg_function(sampler_dyn_thresh) + return (m, ) diff --git a/extensions/1-sd-dynamic-thresholding/dynthres_core.py b/extensions/1-sd-dynamic-thresholding/dynthres_core.py new file mode 100644 index 0000000000000000000000000000000000000000..292c2043d2aea459c60bb202ceb57cf99c01255a --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/dynthres_core.py @@ -0,0 +1,167 @@ +import torch, math + +######################### DynThresh Core ######################### + +class DynThresh: + + Modes = ["Constant", "Linear Down", "Cosine Down", "Half Cosine Down", "Linear Up", "Cosine Up", "Half Cosine Up", "Power Up", "Power Down", "Linear Repeating", "Cosine Repeating", "Sawtooth"] + Startpoints = ["MEAN", "ZERO"] + Variabilities = ["AD", "STD"] + + def __init__(self, mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, cfg_scale_min, sched_val, experiment_mode, max_steps, separate_feature_channels, scaling_startpoint, variability_measure, interpolate_phi): + self.mimic_scale = mimic_scale + self.threshold_percentile = threshold_percentile + self.mimic_mode = mimic_mode + self.cfg_mode = cfg_mode + self.max_steps = max_steps + self.cfg_scale_min = cfg_scale_min + self.mimic_scale_min = mimic_scale_min + self.experiment_mode = experiment_mode + self.sched_val = sched_val + self.sep_feat_channels = separate_feature_channels + self.scaling_startpoint = scaling_startpoint + self.variability_measure = variability_measure + self.interpolate_phi = interpolate_phi + + def interpret_scale(self, scale, mode, min): + scale -= min + max = self.max_steps - 1 + frac = self.step / max + if mode == "Constant": + pass + elif mode == "Linear Down": + scale *= 1.0 - frac + elif mode == "Half Cosine Down": + scale *= math.cos(frac) + elif mode == "Cosine Down": + scale *= math.cos(frac * 1.5707) + elif mode == "Linear Up": + scale *= frac + elif mode == "Half Cosine Up": + scale *= 1.0 - math.cos(frac) + elif mode == "Cosine Up": + scale *= 1.0 - math.cos(frac * 1.5707) + elif mode == "Power Up": + scale *= math.pow(frac, self.sched_val) + elif mode == "Power Down": + scale *= 1.0 - math.pow(frac, self.sched_val) + elif mode == "Linear Repeating": + portion = (frac * self.sched_val) % 1.0 + scale *= (0.5 - portion) * 2 if portion < 0.5 else (portion - 0.5) * 2 + elif mode == "Cosine Repeating": + scale *= math.cos(frac * 6.28318 * self.sched_val) * 0.5 + 0.5 + elif mode == "Sawtooth": + scale *= (frac * self.sched_val) % 1.0 + scale += min + return scale + + def dynthresh(self, cond, uncond, cfg_scale, weights): + mimic_scale = self.interpret_scale(self.mimic_scale, self.mimic_mode, self.mimic_scale_min) + cfg_scale = self.interpret_scale(cfg_scale, self.cfg_mode, self.cfg_scale_min) + # uncond shape is (batch, 4, height, width) + conds_per_batch = cond.shape[0] / uncond.shape[0] + assert conds_per_batch == int(conds_per_batch), "Expected # of conds per batch to be constant across batches" + cond_stacked = cond.reshape((-1, int(conds_per_batch)) + uncond.shape[1:]) + + ### Normal first part of the CFG Scale logic, basically + diff = cond_stacked - uncond.unsqueeze(1) + if weights is not None: + diff = diff * weights + relative = diff.sum(1) + + ### Get the normal result for both mimic and normal scale + mim_target = uncond + relative * mimic_scale + cfg_target = uncond + relative * cfg_scale + ### If we weren't doing mimic scale, we'd just return cfg_target here + + ### Now recenter the values relative to their average rather than absolute, to allow scaling from average + mim_flattened = mim_target.flatten(2) + cfg_flattened = cfg_target.flatten(2) + mim_means = mim_flattened.mean(dim=2).unsqueeze(2) + cfg_means = cfg_flattened.mean(dim=2).unsqueeze(2) + mim_centered = mim_flattened - mim_means + cfg_centered = cfg_flattened - cfg_means + + if self.sep_feat_channels: + if self.variability_measure == 'STD': + mim_scaleref = mim_centered.std(dim=2).unsqueeze(2) + cfg_scaleref = cfg_centered.std(dim=2).unsqueeze(2) + else: # 'AD' + mim_scaleref = mim_centered.abs().max(dim=2).values.unsqueeze(2) + cfg_scaleref = torch.quantile(cfg_centered.abs(), self.threshold_percentile, dim=2).unsqueeze(2) + + else: + if self.variability_measure == 'STD': + mim_scaleref = mim_centered.std() + cfg_scaleref = cfg_centered.std() + else: # 'AD' + mim_scaleref = mim_centered.abs().max() + cfg_scaleref = torch.quantile(cfg_centered.abs(), self.threshold_percentile) + + if self.scaling_startpoint == 'ZERO': + scaling_factor = mim_scaleref / cfg_scaleref + result = cfg_flattened * scaling_factor + + else: # 'MEAN' + if self.variability_measure == 'STD': + cfg_renormalized = (cfg_centered / cfg_scaleref) * mim_scaleref + else: # 'AD' + ### Get the maximum value of all datapoints (with an optional threshold percentile on the uncond) + max_scaleref = torch.maximum(mim_scaleref, cfg_scaleref) + ### Clamp to the max + cfg_clamped = cfg_centered.clamp(-max_scaleref, max_scaleref) + ### Now shrink from the max to normalize and grow to the mimic scale (instead of the CFG scale) + cfg_renormalized = (cfg_clamped / max_scaleref) * mim_scaleref + + ### Now add it back onto the averages to get into real scale again and return + result = cfg_renormalized + cfg_means + + actual_res = result.unflatten(2, mim_target.shape[2:]) + + if self.interpolate_phi != 1.0: + actual_res = actual_res * self.interpolate_phi + cfg_target * (1.0 - self.interpolate_phi) + + if self.experiment_mode == 1: + num = actual_res.cpu().numpy() + for y in range(0, 64): + for x in range (0, 64): + if num[0][0][y][x] > 1.0: + num[0][1][y][x] *= 0.5 + if num[0][1][y][x] > 1.0: + num[0][1][y][x] *= 0.5 + if num[0][2][y][x] > 1.5: + num[0][2][y][x] *= 0.5 + actual_res = torch.from_numpy(num).to(device=uncond.device) + elif self.experiment_mode == 2: + num = actual_res.cpu().numpy() + for y in range(0, 64): + for x in range (0, 64): + over_scale = False + for z in range(0, 4): + if abs(num[0][z][y][x]) > 1.5: + over_scale = True + if over_scale: + for z in range(0, 4): + num[0][z][y][x] *= 0.7 + actual_res = torch.from_numpy(num).to(device=uncond.device) + elif self.experiment_mode == 3: + coefs = torch.tensor([ + # R G B W + [0.298, 0.207, 0.208, 0.0], # L1 + [0.187, 0.286, 0.173, 0.0], # L2 + [-0.158, 0.189, 0.264, 0.0], # L3 + [-0.184, -0.271, -0.473, 1.0], # L4 + ], device=uncond.device) + res_rgb = torch.einsum("laxy,ab -> lbxy", actual_res, coefs) + max_r, max_g, max_b, max_w = res_rgb[0][0].max(), res_rgb[0][1].max(), res_rgb[0][2].max(), res_rgb[0][3].max() + max_rgb = max(max_r, max_g, max_b) + print(f"test max = r={max_r}, g={max_g}, b={max_b}, w={max_w}, rgb={max_rgb}") + if self.step / (self.max_steps - 1) > 0.2: + if max_rgb < 2.0 and max_w < 3.0: + res_rgb /= max_rgb / 2.4 + else: + if max_rgb > 2.4 and max_w > 3.0: + res_rgb /= max_rgb / 2.4 + actual_res = torch.einsum("laxy,ab -> lbxy", res_rgb, coefs.inverse()) + + return actual_res diff --git a/extensions/1-sd-dynamic-thresholding/dynthres_unipc.py b/extensions/1-sd-dynamic-thresholding/dynthres_unipc.py new file mode 100644 index 0000000000000000000000000000000000000000..cb330513e3e237e6c553136070692ab1acdd7082 --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/dynthres_unipc.py @@ -0,0 +1,111 @@ +import gradio as gr +import torch +import math +import traceback +from modules import shared +try: + from modules.models.diffusion import uni_pc +except Exception as e: + from modules import unipc as uni_pc + +######################### UniPC Implementation logic ######################### + +# The majority of this is straight from modules.models/diffusion/uni_pc/sampler.py +# Unfortunately that's not an easy middle-injection point, so, just copypasta'd it all +# It's like they designed it to intentionally be as difficult to inject into as possible :( +# (It has hooks but not in useful locations) +# I stripped the original comments for brevity. +# Some never-used code (scheduler modes, noise modes, guidance modes) have been removed as well for brevity. +# The actual impl comes down to just the last line in particular, and the `before_sample` insert to track step count. + +class CustomUniPCSampler(uni_pc.sampler.UniPCSampler): + def __init__(self, model, **kwargs): + super().__init__(model, *kwargs) + @torch.no_grad() + def sample(self, S, batch_size, shape, conditioning=None, callback=None, normals_sequence=None, img_callback=None, + quantize_x0=False, eta=0., mask=None, x0=None, temperature=1., noise_dropout=0., score_corrector=None, + corrector_kwargs=None, verbose=True, x_T=None, log_every_t=100, unconditional_guidance_scale=1., + unconditional_conditioning=None, **kwargs): + if conditioning is not None: + if isinstance(conditioning, dict): + ctmp = conditioning[list(conditioning.keys())[0]] + while isinstance(ctmp, list): ctmp = ctmp[0] + cbs = ctmp.shape[0] + if cbs != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + + elif isinstance(conditioning, list): + for ctmp in conditioning: + if ctmp.shape[0] != batch_size: + print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}") + else: + if conditioning.shape[0] != batch_size: + print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}") + C, H, W = shape + size = (batch_size, C, H, W) + device = self.model.betas.device + if x_T is None: + img = torch.randn(size, device=device) + else: + img = x_T + ns = uni_pc.uni_pc.NoiseScheduleVP('discrete', alphas_cumprod=self.alphas_cumprod) + model_type = "v" if self.model.parameterization == "v" else "noise" + model_fn = CustomUniPC_model_wrapper(lambda x, t, c: self.model.apply_model(x, t, c), ns, model_type=model_type, guidance_scale=unconditional_guidance_scale, dt_data=self.main_class) + self.main_class.step = 0 + def before_sample(x, t, cond, uncond): + self.main_class.step += 1 + return self.before_sample(x, t, cond, uncond) + uni_pc_inst = uni_pc.uni_pc.UniPC(model_fn, ns, predict_x0=True, thresholding=False, variant=shared.opts.uni_pc_variant, condition=conditioning, unconditional_condition=unconditional_conditioning, before_sample=before_sample, after_sample=self.after_sample, after_update=self.after_update) + x = uni_pc_inst.sample(img, steps=S, skip_type=shared.opts.uni_pc_skip_type, method="multistep", order=shared.opts.uni_pc_order, lower_order_final=shared.opts.uni_pc_lower_order_final) + return x.to(device), None + +def CustomUniPC_model_wrapper(model, noise_schedule, model_type="noise", model_kwargs={}, guidance_scale=1.0, dt_data=None): + def expand_dims(v, dims): + return v[(...,) + (None,)*(dims - 1)] + def get_model_input_time(t_continuous): + return (t_continuous - 1. / noise_schedule.total_N) * 1000. + def noise_pred_fn(x, t_continuous, cond=None): + if t_continuous.reshape((-1,)).shape[0] == 1: + t_continuous = t_continuous.expand((x.shape[0])) + t_input = get_model_input_time(t_continuous) + if cond is None: + output = model(x, t_input, None, **model_kwargs) + else: + output = model(x, t_input, cond, **model_kwargs) + if model_type == "noise": + return output + elif model_type == "v": + alpha_t, sigma_t = noise_schedule.marginal_alpha(t_continuous), noise_schedule.marginal_std(t_continuous) + dims = x.dim() + return expand_dims(alpha_t, dims) * output + expand_dims(sigma_t, dims) * x + def model_fn(x, t_continuous, condition, unconditional_condition): + if t_continuous.reshape((-1,)).shape[0] == 1: + t_continuous = t_continuous.expand((x.shape[0])) + if guidance_scale == 1. or unconditional_condition is None: + return noise_pred_fn(x, t_continuous, cond=condition) + else: + x_in = torch.cat([x] * 2) + t_in = torch.cat([t_continuous] * 2) + if isinstance(condition, dict): + assert isinstance(unconditional_condition, dict) + c_in = dict() + for k in condition: + if isinstance(condition[k], list): + c_in[k] = [torch.cat([ + unconditional_condition[k][i], + condition[k][i]]) for i in range(len(condition[k]))] + else: + c_in[k] = torch.cat([ + unconditional_condition[k], + condition[k]]) + elif isinstance(condition, list): + c_in = list() + assert isinstance(unconditional_condition, list) + for i in range(len(condition)): + c_in.append(torch.cat([unconditional_condition[i], condition[i]])) + else: + c_in = torch.cat([unconditional_condition, condition]) + noise_uncond, noise = noise_pred_fn(x_in, t_in, cond=c_in).chunk(2) + #return noise_uncond + guidance_scale * (noise - noise_uncond) + return dt_data.dynthresh(noise, noise_uncond, guidance_scale, None) + return model_fn diff --git a/extensions/1-sd-dynamic-thresholding/github/cat_demo_1.jpg b/extensions/1-sd-dynamic-thresholding/github/cat_demo_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..080c3787d952ee0da74bf9f697dde10410e419f0 Binary files /dev/null and b/extensions/1-sd-dynamic-thresholding/github/cat_demo_1.jpg differ diff --git a/extensions/1-sd-dynamic-thresholding/github/comfy_node.png b/extensions/1-sd-dynamic-thresholding/github/comfy_node.png new file mode 100644 index 0000000000000000000000000000000000000000..b81280bb6826c13760a1ef9b5fe892c0e994980e Binary files /dev/null and b/extensions/1-sd-dynamic-thresholding/github/comfy_node.png differ diff --git a/extensions/1-sd-dynamic-thresholding/github/grid_preview.png b/extensions/1-sd-dynamic-thresholding/github/grid_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..f5cec8b940dcc90d8008b02c187cbbe531d4d1dd Binary files /dev/null and b/extensions/1-sd-dynamic-thresholding/github/grid_preview.png differ diff --git a/extensions/1-sd-dynamic-thresholding/github/ui.png b/extensions/1-sd-dynamic-thresholding/github/ui.png new file mode 100644 index 0000000000000000000000000000000000000000..5186888bb590e32be1ffd4be50a447235ece9ef8 Binary files /dev/null and b/extensions/1-sd-dynamic-thresholding/github/ui.png differ diff --git a/extensions/1-sd-dynamic-thresholding/javascript/active.js b/extensions/1-sd-dynamic-thresholding/javascript/active.js new file mode 100644 index 0000000000000000000000000000000000000000..0e8fcc3d969ae4e7f65a9e3d0358cf0dd151fdbb --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/javascript/active.js @@ -0,0 +1,68 @@ +let dynthres_update_enabled = function() { + return Array.from(arguments); +}; + +(function(){ + let accordions = {}; + let enabled = {}; + onUiUpdate(() => { + let accordion_id_prefix = "#dynthres_"; + let extension_checkbox_class = ".dynthres-enabled"; + + dynthres_update_enabled = function() { + let res = Array.from(arguments); + let tabname = res[1] ? "img2img" : "txt2img"; + + let checkbox = accordions[tabname]?.querySelector(extension_checkbox_class + ' input'); + checkbox?.dispatchEvent(new Event('change')); + + return res; + }; + + function attachEnabledButtonListener(checkbox, accordion) { + let span = accordion.querySelector('.label-wrap span'); + let badge = document.createElement('input'); + badge.type = "checkbox"; + badge.checked = checkbox.checked; + badge.addEventListener('click', (e) => { + checkbox.checked = !checkbox.checked; + badge.checked = checkbox.checked; + checkbox.dispatchEvent(new Event('change')); + e.stopPropagation(); + }); + + badge.className = checkbox.className; + badge.classList.add('primary'); + span.insertBefore(badge, span.firstChild); + let space = document.createElement('span'); + space.innerHTML = " "; + span.insertBefore(space, badge.nextSibling); + + checkbox.addEventListener('change', () => { + let badge = accordion.querySelector('.label-wrap span input'); + badge.checked = checkbox.checked; + }); + checkbox.parentNode.style.display = "none"; + } + + if (Object.keys(accordions).length < 2) { + let accordion = gradioApp().querySelector(accordion_id_prefix + 'txt2img'); + if (accordion) { + accordions.txt2img = accordion; + } + accordion = gradioApp().querySelector(accordion_id_prefix + 'img2img'); + if (accordion) { + accordions.img2img = accordion; + } + } + + if (Object.keys(accordions).length > 0 && accordions.txt2img && !enabled.txt2img) { + enabled.txt2img = accordions.txt2img.querySelector(extension_checkbox_class + ' input'); + attachEnabledButtonListener(enabled.txt2img, accordions.txt2img); + } + if (Object.keys(accordions).length > 0 && accordions.img2img && !enabled.img2img) { + enabled.img2img = accordions.img2img.querySelector(extension_checkbox_class + ' input'); + attachEnabledButtonListener(enabled.img2img, accordions.img2img); + } + }); +})(); diff --git a/extensions/1-sd-dynamic-thresholding/pyproject.toml b/extensions/1-sd-dynamic-thresholding/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..b4db0a44ec589e5ffb8ac7580db38b3407baf42d --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "sd-dynamic-thresholding" +description = "Adds nodes for Dynamic Thresholding, CFG scheduling, and related techniques" +version = "1.0.1" +license = { file = "LICENSE.txt" } + +[project.urls] +Repository = "https://github.com/mcmonkeyprojects/sd-dynamic-thresholding" + +[tool.comfy] +PublisherId = "mcmonkey" +DisplayName = "Dynamic Thresholding" +Icon = "" diff --git a/extensions/1-sd-dynamic-thresholding/scripts/__pycache__/dynamic_thresholding.cpython-310.pyc b/extensions/1-sd-dynamic-thresholding/scripts/__pycache__/dynamic_thresholding.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55265daabbe81353f3bae7422403a5e6270177d3 Binary files /dev/null and b/extensions/1-sd-dynamic-thresholding/scripts/__pycache__/dynamic_thresholding.cpython-310.pyc differ diff --git a/extensions/1-sd-dynamic-thresholding/scripts/dynamic_thresholding.py b/extensions/1-sd-dynamic-thresholding/scripts/dynamic_thresholding.py new file mode 100644 index 0000000000000000000000000000000000000000..a989e17484314eb9aca1d5ecc0862d520b753b47 --- /dev/null +++ b/extensions/1-sd-dynamic-thresholding/scripts/dynamic_thresholding.py @@ -0,0 +1,270 @@ +################## +# Stable Diffusion Dynamic Thresholding (CFG Scale Fix) +# +# Author: Alex 'mcmonkey' Goodwin +# GitHub URL: https://github.com/mcmonkeyprojects/sd-dynamic-thresholding +# Created: 2022/01/26 +# Last updated: 2023/01/30 +# +# For usage help, view the README.md file in the extension root, or via the GitHub page. +# +################## + +import gradio as gr +import torch, traceback +import dynthres_core +from modules import scripts, script_callbacks, sd_samplers, sd_samplers_compvis, sd_samplers_common +try: + import dynthres_unipc +except Exception as e: + print(f"\n\n======\nError! UniPC sampler support failed to load! Is your WebUI up to date?\n(Error: {e})\n======") +try: + from modules.sd_samplers_kdiffusion import CFGDenoiserKDiffusion as cfgdenoisekdiff + IS_AUTO_16 = True +except Exception as e: + print(f"\n\n======\nWarning! Using legacy KDiff version! Is your WebUI up to date?\n======") + from modules.sd_samplers_kdiffusion import CFGDenoiser as cfgdenoisekdiff + IS_AUTO_16 = False + +DISABLE_VISIBILITY = True + +######################### Data values ######################### +MODES_WITH_VALUE = ["Power Up", "Power Down", "Linear Repeating", "Cosine Repeating", "Sawtooth"] + +######################### Script class entrypoint ######################### +class Script(scripts.Script): + + def title(self): + return "Dynamic Thresholding (CFG Scale Fix)" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + def vis_change(is_vis): + return {"visible": is_vis, "__type__": "update"} + # "Dynamic Thresholding (CFG Scale Fix)" + dtrue = gr.Checkbox(value=True, visible=False) + dfalse = gr.Checkbox(value=False, visible=False) + with gr.Accordion("Dynamic Thresholding (CFG Scale Fix)", open=False, elem_id="dynthres_" + ("img2img" if is_img2img else "txt2img")): + with gr.Row(): + enabled = gr.Checkbox(value=False, label="Enable Dynamic Thresholding (CFG Scale Fix)", elem_classes=["dynthres-enabled"], elem_id='dynthres_enabled') + with gr.Group(): + gr.HTML(value=f"View the wiki for usage tips.

", elem_id='dynthres_wiki_link') + mimic_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='Mimic CFG Scale', value=7.0, elem_id='dynthres_mimic_scale') + with gr.Accordion("Advanced Options", open=False, elem_id='dynthres_advanced_opts'): + with gr.Row(): + threshold_percentile = gr.Slider(minimum=90.0, value=100.0, maximum=100.0, step=0.05, label='Top percentile of latents to clamp', elem_id='dynthres_threshold_percentile') + interpolate_phi = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label="Interpolate Phi", value=1.0, elem_id='dynthres_interpolate_phi') + with gr.Row(): + mimic_mode = gr.Dropdown(dynthres_core.DynThresh.Modes, value="Constant", label="Mimic Scale Scheduler", elem_id='dynthres_mimic_mode') + cfg_mode = gr.Dropdown(dynthres_core.DynThresh.Modes, value="Constant", label="CFG Scale Scheduler", elem_id='dynthres_cfg_mode') + mimic_scale_min = gr.Slider(minimum=0.0, maximum=30.0, step=0.5, visible=DISABLE_VISIBILITY, label="Minimum value of the Mimic Scale Scheduler", elem_id='dynthres_mimic_scale_min') + cfg_scale_min = gr.Slider(minimum=0.0, maximum=30.0, step=0.5, visible=DISABLE_VISIBILITY, label="Minimum value of the CFG Scale Scheduler", elem_id='dynthres_cfg_scale_min') + sched_val = gr.Slider(minimum=0.0, maximum=40.0, step=0.5, value=4.0, visible=DISABLE_VISIBILITY, label="Scheduler Value", info="Value unique to the scheduler mode - for Power Up/Down, this is the power. For Linear/Cosine Repeating, this is the number of repeats per image.", elem_id='dynthres_sched_val') + with gr.Row(): + separate_feature_channels = gr.Checkbox(value=True, label="Separate Feature Channels", elem_id='dynthres_separate_feature_channels') + scaling_startpoint = gr.Radio(["ZERO", "MEAN"], value="MEAN", label="Scaling Startpoint") + variability_measure = gr.Radio(["STD", "AD"], value="AD", label="Variability Measure") + def should_show_scheduler_value(cfg_mode, mimic_mode): + sched_vis = cfg_mode in MODES_WITH_VALUE or mimic_mode in MODES_WITH_VALUE or DISABLE_VISIBILITY + return vis_change(sched_vis), vis_change(mimic_mode != "Constant" or DISABLE_VISIBILITY), vis_change(cfg_mode != "Constant" or DISABLE_VISIBILITY) + cfg_mode.change(should_show_scheduler_value, inputs=[cfg_mode, mimic_mode], outputs=[sched_val, mimic_scale_min, cfg_scale_min]) + mimic_mode.change(should_show_scheduler_value, inputs=[cfg_mode, mimic_mode], outputs=[sched_val, mimic_scale_min, cfg_scale_min]) + enabled.change( + _js="dynthres_update_enabled", + fn=None, + inputs=[enabled, dtrue if is_img2img else dfalse], + show_progress = False) + self.infotext_fields = ( + (enabled, lambda d: gr.Checkbox.update(value="Dynamic thresholding enabled" in d)), + (mimic_scale, "Mimic scale"), + (separate_feature_channels, "Separate Feature Channels"), + (scaling_startpoint, lambda d: gr.Radio.update(value=d.get("Scaling Startpoint", "MEAN"))), + (variability_measure, lambda d: gr.Radio.update(value=d.get("Variability Measure", "AD"))), + (interpolate_phi, "Interpolate Phi"), + (threshold_percentile, "Threshold percentile"), + (mimic_scale_min, "Mimic scale minimum"), + (mimic_mode, lambda d: gr.Dropdown.update(value=d.get("Mimic mode", "Constant"))), + (cfg_mode, lambda d: gr.Dropdown.update(value=d.get("CFG mode", "Constant"))), + (cfg_scale_min, "CFG scale minimum"), + (sched_val, "Scheduler value")) + return [enabled, mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, cfg_scale_min, sched_val, separate_feature_channels, scaling_startpoint, variability_measure, interpolate_phi] + + last_id = 0 + + def process_batch(self, p, enabled, mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, cfg_scale_min, sched_val, separate_feature_channels, scaling_startpoint, variability_measure, interpolate_phi, batch_number, prompts, seeds, subseeds): + enabled = getattr(p, 'dynthres_enabled', enabled) + if not enabled: + return + orig_sampler_name = p.sampler_name + orig_latent_sampler_name = getattr(p, 'latent_sampler', None) + if orig_sampler_name in ["DDIM", "PLMS"]: + raise RuntimeError(f"Cannot use sampler {orig_sampler_name} with Dynamic Thresholding") + if orig_latent_sampler_name in ["DDIM", "PLMS"]: + raise RuntimeError(f"Cannot use secondary sampler {orig_latent_sampler_name} with Dynamic Thresholding") + if 'UniPC' in (orig_sampler_name, orig_latent_sampler_name) and p.enable_hr: + raise RuntimeError(f"UniPC does not support Hires Fix. Auto WebUI silently swaps to DDIM for this, which DynThresh does not support. Please swap to a sampler capable of img2img processing for HR Fix to work.") + mimic_scale = getattr(p, 'dynthres_mimic_scale', mimic_scale) + separate_feature_channels = getattr(p, 'dynthres_separate_feature_channels', separate_feature_channels) + scaling_startpoint = getattr(p, 'dynthres_scaling_startpoint', scaling_startpoint) + variability_measure = getattr(p, 'dynthres_variability_measure', variability_measure) + interpolate_phi = getattr(p, 'dynthres_interpolate_phi', interpolate_phi) + threshold_percentile = getattr(p, 'dynthres_threshold_percentile', threshold_percentile) + mimic_mode = getattr(p, 'dynthres_mimic_mode', mimic_mode) + mimic_scale_min = getattr(p, 'dynthres_mimic_scale_min', mimic_scale_min) + cfg_mode = getattr(p, 'dynthres_cfg_mode', cfg_mode) + cfg_scale_min = getattr(p, 'dynthres_cfg_scale_min', cfg_scale_min) + experiment_mode = getattr(p, 'dynthres_experiment_mode', 0) + sched_val = getattr(p, 'dynthres_scheduler_val', sched_val) + p.extra_generation_params["Dynamic thresholding enabled"] = True + p.extra_generation_params["Mimic scale"] = mimic_scale + p.extra_generation_params["Separate Feature Channels"] = separate_feature_channels + p.extra_generation_params["Scaling Startpoint"] = scaling_startpoint + p.extra_generation_params["Variability Measure"] = variability_measure + p.extra_generation_params["Interpolate Phi"] = interpolate_phi + p.extra_generation_params["Threshold percentile"] = threshold_percentile + p.extra_generation_params["Sampler"] = orig_sampler_name + if mimic_mode != "Constant": + p.extra_generation_params["Mimic mode"] = mimic_mode + p.extra_generation_params["Mimic scale minimum"] = mimic_scale_min + if cfg_mode != "Constant": + p.extra_generation_params["CFG mode"] = cfg_mode + p.extra_generation_params["CFG scale minimum"] = cfg_scale_min + if cfg_mode in MODES_WITH_VALUE or mimic_mode in MODES_WITH_VALUE: + p.extra_generation_params["Scheduler value"] = sched_val + # Note: the ID number is to protect the edge case of multiple simultaneous runs with different settings + Script.last_id += 1 + # Percentage to portion + threshold_percentile *= 0.01 + + def make_sampler(orig_sampler_name): + fixed_sampler_name = f"{orig_sampler_name}_dynthres{Script.last_id}" + + # Make a placeholder sampler + sampler = sd_samplers.all_samplers_map[orig_sampler_name] + dt_data = dynthres_core.DynThresh(mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, cfg_scale_min, sched_val, experiment_mode, p.steps, separate_feature_channels, scaling_startpoint, variability_measure, interpolate_phi) + if orig_sampler_name == "UniPC": + def unipc_constructor(model): + return CustomVanillaSDSampler(dynthres_unipc.CustomUniPCSampler, model, dt_data) + new_sampler = sd_samplers_common.SamplerData(fixed_sampler_name, unipc_constructor, sampler.aliases, sampler.options) + else: + def new_constructor(model): + result = sampler.constructor(model) + cfg = CustomCFGDenoiser(result if IS_AUTO_16 else result.model_wrap_cfg.inner_model, dt_data) + result.model_wrap_cfg = cfg + return result + new_sampler = sd_samplers_common.SamplerData(fixed_sampler_name, new_constructor, sampler.aliases, sampler.options) + return fixed_sampler_name, new_sampler + + # Apply for usage + p.orig_sampler_name = orig_sampler_name + p.orig_latent_sampler_name = orig_latent_sampler_name + p.fixed_samplers = [] + + if orig_latent_sampler_name: + latent_sampler_name, latent_sampler = make_sampler(orig_latent_sampler_name) + sd_samplers.all_samplers_map[latent_sampler_name] = latent_sampler + p.fixed_samplers.append(latent_sampler_name) + p.latent_sampler = latent_sampler_name + + if orig_sampler_name != orig_latent_sampler_name: + p.sampler_name, new_sampler = make_sampler(orig_sampler_name) + sd_samplers.all_samplers_map[p.sampler_name] = new_sampler + p.fixed_samplers.append(p.sampler_name) + else: + p.sampler_name = p.latent_sampler + + if p.sampler is not None: + p.sampler = sd_samplers.create_sampler(p.sampler_name, p.sd_model) + + def postprocess_batch(self, p, enabled, mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, cfg_scale_min, sched_val, separate_feature_channels, scaling_startpoint, variability_measure, interpolate_phi, batch_number, images): + if not enabled or not hasattr(p, 'orig_sampler_name'): + return + p.sampler_name = p.orig_sampler_name + if p.orig_latent_sampler_name: + p.latent_sampler = p.orig_latent_sampler_name + for added_sampler in p.fixed_samplers: + del sd_samplers.all_samplers_map[added_sampler] + del p.fixed_samplers + del p.orig_sampler_name + del p.orig_latent_sampler_name + +######################### CompVis Implementation logic ######################### + +class CustomVanillaSDSampler(sd_samplers_compvis.VanillaStableDiffusionSampler): + def __init__(self, constructor, sd_model, dt_data): + super().__init__(constructor, sd_model) + self.sampler.main_class = dt_data + +######################### K-Diffusion Implementation logic ######################### + +class CustomCFGDenoiser(cfgdenoisekdiff): + def __init__(self, model, dt_data): + super().__init__(model) + self.main_class = dt_data + + def combine_denoised(self, x_out, conds_list, uncond, cond_scale): + if isinstance(uncond, dict) and 'crossattn' in uncond: + uncond = uncond['crossattn'] + denoised_uncond = x_out[-uncond.shape[0]:] + # conds_list shape is (batch, cond, 2) + weights = torch.tensor(conds_list, device=uncond.device).select(2, 1) + weights = weights.reshape(*weights.shape, 1, 1, 1) + self.main_class.step = self.step + if hasattr(self, 'total_steps'): + self.main_class.max_steps = self.total_steps + + if self.main_class.experiment_mode >= 4 and self.main_class.experiment_mode <= 5: + # https://arxiv.org/pdf/2305.08891.pdf "Rescale CFG". It's not good, but if you want to test it, just set experiment_mode = 4 + phi. + denoised = torch.clone(denoised_uncond) + fi = self.main_class.experiment_mode - 4.0 + for i, conds in enumerate(conds_list): + for cond_index, weight in conds: + xcfg = (denoised_uncond[i] + (x_out[cond_index] - denoised_uncond[i]) * (cond_scale * weight)) + xrescaled = xcfg * (torch.std(x_out[cond_index]) / torch.std(xcfg)) + xfinal = fi * xrescaled + (1.0 - fi) * xcfg + denoised[i] = xfinal + return denoised + + return self.main_class.dynthresh(x_out[:-uncond.shape[0]], denoised_uncond, cond_scale, weights) + +######################### XYZ Plot Script Support logic ######################### + +def make_axis_options(): + xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ in ("xyz_grid.py", "scripts.xyz_grid")][0].module + def apply_mimic_scale(p, x, xs): + if x != 0: + setattr(p, "dynthres_enabled", True) + setattr(p, "dynthres_mimic_scale", x) + else: + setattr(p, "dynthres_enabled", False) + def confirm_scheduler(p, xs): + for x in xs: + if x not in dynthres_core.DynThresh.Modes: + raise RuntimeError(f"Unknown Scheduler: {x}") + extra_axis_options = [ + xyz_grid.AxisOption("[DynThres] Mimic Scale", float, apply_mimic_scale), + xyz_grid.AxisOption("[DynThres] Separate Feature Channels", int, + xyz_grid.apply_field("dynthres_separate_feature_channels")), + xyz_grid.AxisOption("[DynThres] Scaling Startpoint", str, xyz_grid.apply_field("dynthres_scaling_startpoint"), choices=lambda:['ZERO', 'MEAN']), + xyz_grid.AxisOption("[DynThres] Variability Measure", str, xyz_grid.apply_field("dynthres_variability_measure"), choices=lambda:['STD', 'AD']), + xyz_grid.AxisOption("[DynThres] Interpolate Phi", float, xyz_grid.apply_field("dynthres_interpolate_phi")), + xyz_grid.AxisOption("[DynThres] Threshold Percentile", float, xyz_grid.apply_field("dynthres_threshold_percentile")), + xyz_grid.AxisOption("[DynThres] Mimic Scheduler", str, xyz_grid.apply_field("dynthres_mimic_mode"), confirm=confirm_scheduler, choices=lambda: dynthres_core.DynThresh.Modes), + xyz_grid.AxisOption("[DynThres] Mimic minimum", float, xyz_grid.apply_field("dynthres_mimic_scale_min")), + xyz_grid.AxisOption("[DynThres] CFG Scheduler", str, xyz_grid.apply_field("dynthres_cfg_mode"), confirm=confirm_scheduler, choices=lambda: dynthres_core.DynThresh.Modes), + xyz_grid.AxisOption("[DynThres] CFG minimum", float, xyz_grid.apply_field("dynthres_cfg_scale_min")), + xyz_grid.AxisOption("[DynThres] Scheduler value", float, xyz_grid.apply_field("dynthres_scheduler_val")) + ] + if not any("[DynThres]" in x.label for x in xyz_grid.axis_options): + xyz_grid.axis_options.extend(extra_axis_options) + +def callback_before_ui(): + try: + make_axis_options() + except Exception as e: + traceback.print_exc() + print(f"Failed to add support for X/Y/Z Plot Script because: {e}") + +script_callbacks.on_before_ui(callback_before_ui) diff --git a/extensions/4-adetailer/.github/ISSUE_TEMPLATE/bug_report.yaml b/extensions/4-adetailer/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8093934c08d1a5895a4b302494d2ec880bbffe29 --- /dev/null +++ b/extensions/4-adetailer/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,63 @@ +name: Bug report +description: Create a report +title: "[Bug]: " +labels: + - bug + +body: + - type: textarea + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + placeholder: | + Any language accepted + 아무 언어 사용가능 + すべての言語に対応 + 接受所有语言 + Se aceptan todos los idiomas + Alle Sprachen werden akzeptiert + Toutes les langues sont acceptées + Принимаются все языки + validations: + required: true + + - type: textarea + attributes: + label: Steps to reproduce + description: | + Description of how we can reproduce this issue. + validations: + required: true + + - type: textarea + attributes: + label: Screenshots + description: Screenshots related to the issue. + + - type: textarea + attributes: + label: Console logs, from start to end. + description: | + The full console log of your terminal. + placeholder: | + Python ... + Version: ... + Commit hash: ... + Installing requirements + ... + + Launching Web UI with arguments: ... + [-] ADetailer initialized. version: ... + ... + ... + + Traceback (most recent call last): + ... + ... + render: Shell + validations: + required: true + + - type: textarea + attributes: + label: List of installed extensions diff --git a/extensions/4-adetailer/.github/ISSUE_TEMPLATE/feature_request.yaml b/extensions/4-adetailer/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 0000000000000000000000000000000000000000..01511988754461cf9637a8ba8b79c0c4893c130b --- /dev/null +++ b/extensions/4-adetailer/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,26 @@ +name: Feature request +description: Suggest an idea for this project +title: "[Feature Request]: " +labels: + - enhancement + +body: + - type: textarea + attributes: + label: Is your feature request related to a problem? Please describe. + description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + + - type: textarea + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen. + + - type: textarea + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered. + + - type: textarea + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. diff --git a/extensions/4-adetailer/.github/workflows/lgtm.yml b/extensions/4-adetailer/.github/workflows/lgtm.yml new file mode 100644 index 0000000000000000000000000000000000000000..08c0fadf19ec9c8b6532d65060f0eb834abe391b --- /dev/null +++ b/extensions/4-adetailer/.github/workflows/lgtm.yml @@ -0,0 +1,23 @@ +name: Empirical Implementation of JDD + +on: + pull_request: + types: + - opened + +jobs: + lint: + permissions: + issues: write + pull-requests: write + runs-on: ubuntu-latest + + steps: + - uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + ![Imgur](https://i.imgur.com/ESow3BL.png) + + LGTM + reactions: hooray diff --git a/extensions/4-adetailer/.github/workflows/pypi.yml b/extensions/4-adetailer/.github/workflows/pypi.yml new file mode 100644 index 0000000000000000000000000000000000000000..fee205c9af5fb6526cb945f93fbd260e184a502a --- /dev/null +++ b/extensions/4-adetailer/.github/workflows/pypi.yml @@ -0,0 +1,49 @@ +name: Publish to PyPI +on: + push: + tags: + - "v*" + +jobs: + test: + name: test + runs-on: macos-14 + strategy: + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - uses: yezz123/setup-uv@v4 + + - name: Install dependencies + run: | + uv pip install --system . pytest + + - name: Run tests + run: pytest -v + + build: + name: build + runs-on: ubuntu-latest + permissions: + id-token: write + needs: [test] + + steps: + - uses: actions/checkout@v4 + + - name: Build wheel + run: pipx run build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/extensions/4-adetailer/.github/workflows/stale.yml b/extensions/4-adetailer/.github/workflows/stale.yml new file mode 100644 index 0000000000000000000000000000000000000000..0f4fd9b4c8d248bb35332b8c0013d643104caf3b --- /dev/null +++ b/extensions/4-adetailer/.github/workflows/stale.yml @@ -0,0 +1,13 @@ +name: Close stale issues and PRs +on: + schedule: + - cron: "30 1 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + days-before-stale: 17 + days-before-close: 3 diff --git a/extensions/4-adetailer/.gitignore b/extensions/4-adetailer/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..aefb8187d07240aada8888a2ff8c8711f1a19017 --- /dev/null +++ b/extensions/4-adetailer/.gitignore @@ -0,0 +1,197 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=python,visualstudiocode + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode +*.ipynb +node_modules diff --git a/extensions/4-adetailer/.pre-commit-config.yaml b/extensions/4-adetailer/.pre-commit-config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..667ab5df31b6717039d53762905a2ae21ac53b2d --- /dev/null +++ b/extensions/4-adetailer/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +ci: + autoupdate_branch: "dev" + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-added-large-files + args: [--maxkb=100] + - id: check-merge-conflict + - id: check-case-conflict + - id: check-ast + - id: check-yaml + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: end-of-file-fixer + - id: mixed-line-ending + + - repo: https://github.com/rbubley/mirrors-prettier + rev: v3.3.3 + hooks: + - id: prettier + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.6 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format diff --git a/extensions/4-adetailer/.vscode/extensions.json b/extensions/4-adetailer/.vscode/extensions.json new file mode 100644 index 0000000000000000000000000000000000000000..5284a0f255b2eeaa06e386318a3ba6b7ee5106c6 --- /dev/null +++ b/extensions/4-adetailer/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "ms-python.vscode-pylance", + "ms-python.black-formatter", + "kevinrose.vsc-python-indent", + "charliermarsh.ruff", + "shardulm94.trailing-spaces" + ] +} diff --git a/extensions/4-adetailer/.vscode/settings.json b/extensions/4-adetailer/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..9be5bebf2c13dff44bf11314224d4a9c33889969 --- /dev/null +++ b/extensions/4-adetailer/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "explorer.fileNesting.enabled": true, + "explorer.fileNesting.patterns": { + "pyproject.toml": ".env, .gitignore, .pre-commit-config.yaml, Taskfile.yml", + "README.md": "LICENSE.md, CHANGELOG.md", + "install.py": "preload.py" + } +} diff --git a/extensions/4-adetailer/CHANGELOG.md b/extensions/4-adetailer/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..2095da50ab5f930024c5eb353522b3cde37d22f9 --- /dev/null +++ b/extensions/4-adetailer/CHANGELOG.md @@ -0,0 +1,472 @@ +# Changelog + +## 2024-08-03 + +- v24.8.0 +- 샘플러 선택칸에 Use same sampler 옵션 추가 +- 컨트롤넷 유니온 모델을 선택할 수 있게 함 + +- webui 1.9.0이상에서 기본 스케줄러가 설정되지 않던 문제 수정 +- issus #656의 문제 해결을 위해 v24.4.0에 적용되었던 프롬프트 표시 기능을 되돌림 +- mediapipe에서 에러가 발생하면 추론이 실패한 것으로 처리하고 조용히 넘어감 + +## 2024-06-16 + +- v24.6.0 +- webui 1.6.0 미만 버전을 위한 기능들을 제거하고, 최소 버전을 1.6.0으로 올림 +- 허깅페이스 연결을 체크하는데 1초만 소요되도록 함 + - 허깅페이스 미러 (hf-mirror.com)도 체크함 (합쳐서 2초) +- InputAccordion을 적용함 + +## 2024-05-20 + +- v24.5.1 +- uv를 사용하지 않게 함 +- 모든 허깅페이스 모델을 동시에 다운로드 시도함 +- 기본 탭 수를 2에서 4로 변경 + +## 2024-05-19 + +- v24.5.0 +- 개별 탭 활성화/비활성화 체크박스 추가 +- ad_extra_model_dir 옵션에 |로 구분된 여러 디렉토리를 추가할 수 있게 함 (PR #596) +- `hypertile` 빌트인 확장이 지원되도록 함 +- 항상 cond 캐시를 비움 +- 설치 스크립트에 uv를 사용함 +- mediapipe 최소 버전을 올려 protobuf 버전 4를 사용하게 함 + +## 2024-04-17 + +- v24.4.2 +- `params.txt` 파일이 없을 때 에러가 발생하지 않도록 수정 +- 파이썬 3.9 이하에서 유니온 타입 에러 방지 + +## 2024-04-14 + +- v24.4.1 +- webui 1.9.0에서 발생한 에러 수정 + - extra generation params에 callable이 들어와서 생긴 문제 + - assign_current_image에 None이 들어갈 수 있던 문제 +- webui 1.9.0에서 변경된 scheduler 지원 +- 컨트롤넷 모델을 찾을 때, 대소문자 구분을 하지 않음 (PR #577) +- 몇몇 기능을 스크립트에서 분리하여 별도 파일로 빼냄 + +## 2024-04-10 + +- v24.4.0 +- txt2img에서 hires를 설정했을 때, 이미지의 exif에서 Denoising Strength가 adetailer의 denoisiog stregnth로 덮어 쓰이는 문제 수정 +- ad prompt, ad negative prompt에 프롬프트를 변경하는 기능을 적용했을 때(와일드카드 등), 적용된 프롬프트가 이미지의 exif에 제대로 표시됨 + +## 2024-03-29 + +- v24.3.5 +- 알 수 없는 이유로 인페인팅을 확인하는 과정에서 Txt2Img 인스턴스가 들어오는 문제에 대한 임시 해결 + +## 2024-03-28 + +- v24.3.4 +- 인페인트에서, 이미지 해상도가 16의 배수가 아닐 때 사이즈 불일치로 인한 opencv 에러 방지 + +## 2024-03-25 + +- v24.3.3 +- webui 1.6.0 미만 버전에서 create_binary_mask 함수에 대해 ImportError가 발생하는 것 수정 + +## 2024-03-21 + +- v24.3.2 +- UI를 거치지 않은 입력에 대해, image_mask를 입력했을 때 opencv 에러가 발생하는 것 수정 +- img2img inpaint에서 skip img2img 옵션을 활성화할 경우, adetailer를 비활성화함 + - 마스크 크기에 대해 해결하기 힘든 문제가 있음 + +## 2024-03-16 + +- v24.3.1 +- YOLO World v2, YOLO9 지원가능한 버전으로 ultralytics 업데이트 +- inpaint full res인 경우 인페인트 모드에서 동작하게 변경 +- inpaint full res가 아닌 경우, 사용자가 입력한 마스크와 교차점이 있는 마스크만 선택하여 사용함 + +## 2024-03-01 + +- v24.3.0 +- YOLO World 모델 추가: 가장 큰 yolov8x-world.pt 모델만 기본적으로 선택할 수 있게 함. +- lllyasviel/stable-diffusion-webui-forge에서 컨트롤넷을 사용가능하게 함 (PR #517) +- 기본 스크립트 목록에 soft_inpainting 추가 (https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14208) + + - 기존에 설치한 사람에게 소급적용되지는 않음 + +- 감지모델에 대한 간단한 pytest 추가함 +- xyz grid 컨트롤넷 모델 옵션에 `Passthrough` 추가함 + +## 2024-01-23 + +- v24.1.2 +- controlnet 모델에 `Passthrough` 옵션 추가. 입력으로 들어온 컨트롤넷 옵션을 그대로 사용 +- fastapi 엔드포인트 추가 + +## 2024-01-10 + +- v24.1.1 +- SDNext 호환 업데이트 (issue #466) + - 설정 값 state에 초기값 추가 + - 위젯 값을 변경할 때마다 state도 변경되게 함 (기존에는 생성 버튼을 누를 때 적용되었음) +- `inpaint_depth_hand` 컨트롤넷 모델이 depth 모델로 인식되게 함 (issue #463) + +## 2024-01-04 + +- v24.1.0 +- `depth_hand_refiner` ControlNet 추가 (PR #460) + +## 2023-12-30 + +- v23.12.0 +- 파일을 인자로 추가하는 몇몇 스크립트에 대해 deepcopy의 에러를 피하기 위해 script_args 복사 방법을 변경함 +- skip img2img 기능을 사용할 때 너비, 높이를 128로 고정하여 스킵 과정이 조금 더 나아짐 +- img2img inpainting 모드에서 adetailer 자동 비활성화 +- 처음 생성된 params.txt 파일을 항상 유지하도록 변경함 + +## 2023-11-19 + +- v23.11.1 +- 기본 스크립트 목록에 negpip 추가 + - 기존에 설치한 사람에게 소급적용되지는 않음 +- skip img2img 옵션이 2스텝 이상일 때, 제대로 적용되지 않는 문제 수정 +- SD.Next에서 이미지가 np.ndarray로 입력되는 경우 수정 +- 컨트롤넷 경로를 sys.path에 추가하여 --data-dir등을 지정한 경우에도 임포트 에러가 일어나지 않게 함. + +## 2023-10-30 + +- v23.11.0 +- 이미지의 인덱스 계산방법 변경 + - webui 1.1.0 미만에서 adetailer 실행 불가능하게 함 +- 컨트롤넷 preprocessor 선택지 늘림 +- 추가 yolo 모델 디렉터리를 설정할 수 있는 옵션 추가 +- infotext에 `/`가 있는 항목이 exif에서 복원되지 않는 문제 수정 + - 이전 버전에 생성된 이미지는 여전히 복원안됨 +- 같은 탭에서 항상 같은 시드를 적용하게 하는 옵션 추가 +- 컨트롤넷 1.1.411 (f2aafcf2beb99a03cbdf7db73852228ccd6bd1d6) 버전을 사용중일 경우, + webui 버전 1.6.0 미만에서 사용할 수 없다는 메세지 출력 + +## 2023-10-15 + +- v23.10.1 +- xyz grid에 prompt S/R 추가 +- img2img에서 steps가 1일때 에러가 발생하는 샘플러의 처리를 위해 샘플러 이름도 변경하게 수정 + +## 2023-10-07 + +- v23.10.0 +- 허깅페이스 모델을 다운로드 실패했을 때, 계속 다운로드를 시도하지 않음 +- img2img에서 img2img단계를 건너뛰는 기능 추가 +- live preview에서 감지 단계를 보여줌 (PR #352) + +## 2023-09-20 + +- v23.9.3 +- ultralytics 버전 8.0.181로 업데이트 (https://github.com/ultralytics/ultralytics/pull/4891) +- mediapipe와 ultralytics의 lazy import + +## 2023-09-10 + +- v23.9.2 +- (실험적) VAE 선택 기능 + +## 2023-09-01 + +- v23.9.1 +- webui 1.6.0에 추가된 인자를 사용해서 생긴 하위 호환 문제 수정 + +## 2023-08-31 + +- v23.9.0 +- (실험적) 체크포인트 선택기능 + - 버그가 있어 리프레시 버튼은 구현에서 빠짐 +- 1.6.0 업데이트에 따라 img2img에서 사용불가능한 샘플러를 선택했을 때 더이상 Euler로 변경하지 않음 +- 유효하지 않은 인자가 전달되었을 때, 에러를 일으키지 않고 대신 adetailer를 비활성화함 + +## 2023-08-25 + +- v23.8.1 +- xyz grid에서 model을 `None`으로 설정한 이후에 adetailer가 비활성화 되는 문제 수정 +- skip을 눌렀을 때 진행을 멈춤 +- `--medvram-sdxl`을 설정했을 때에도 cpu를 사용하게 함 + +## 2023-08-14 + +- v23.8.0 +- `[PROMPT]` 키워드 추가. `ad_prompt` 또는 `ad_negative_prompt`에 사용하면 입력 프롬프트로 대체됨 (PR #243) +- Only top k largest 옵션 추가 (PR #264) +- ultralytics 버전 업데이트 + +## 2023-07-31 + +- v23.7.11 +- separate clip skip 옵션 추가 +- install requirements 정리 (ultralytics 새 버전, mediapipe~=3.20) + +## 2023-07-28 + +- v23.7.10 +- ultralytics, mediapipe import문 정리 +- traceback에서 컬러를 없앰 (api 때문), 라이브러리 버전도 보여주게 설정. +- huggingface_hub, pydantic을 install.py에서 없앰 +- 안쓰는 컨트롤넷 관련 코드 삭제 + +## 2023-07-23 + +- v23.7.9 +- `ultralytics.utils` ModuleNotFoundError 해결 (https://github.com/ultralytics/ultralytics/issues/3856) +- `pydantic` 2.0 이상 버전 설치안되도록 함 +- `controlnet_dir` cmd args 문제 수정 (PR #107) + +## 2023-07-20 + +- v23.7.8 +- `paste_field_names` 추가했던 것을 되돌림 + +## 2023-07-19 + +- v23.7.7 +- 인페인팅 단계에서 별도의 샘플러를 선택할 수 있게 옵션을 추가함 (xyz그리드에도 추가) +- webui 1.0.0-pre 이하 버전에서 batch index 문제 수정 +- 스크립트에 `paste_field_names`을 추가함. 사용되는지는 모르겠음 + +## 2023-07-16 + +- v23.7.6 +- `ultralytics 8.0.135`에 추가된 cpuinfo 기능을 위해 `py-cpuinfo`를 미리 설치하게 함. (미리 설치 안하면 cpu나 mps사용할 때 재시작해야함) +- init_image가 RGB 모드가 아닐 때 RGB로 변경. + +## 2023-07-07 + +- v23.7.4 +- batch count > 1일때 프롬프트의 인덱스 문제 수정 + +- v23.7.5 +- i2i의 `cached_uc`와 `cached_c`가 p의 `cached_uc`와 `cached_c`가 다른 인스턴스가 되도록 수정 + +## 2023-07-05 + +- v23.7.3 +- 버그 수정 + - `object()`가 json 직렬화 안되는 문제 + - `process`를 호출함에 따라 배치 카운트가 2이상일 때, all_prompts가 고정되는 문제 + - `ad-before`와 `ad-preview` 이미지 파일명이 실제 파일명과 다른 문제 + - pydantic 2.0 호환성 문제 + +## 2023-07-04 + +- v23.7.2 +- `mediapipe_face_mesh_eyes_only` 모델 추가: `mediapipe_face_mesh`로 감지한 뒤 눈만 사용함. +- 매 배치 시작 전에 `scripts.postprocess`를, 후에 `scripts.process`를 호출함. + - 컨트롤넷을 사용하면 소요 시간이 조금 늘어나지만 몇몇 문제 해결에 도움이 됨. +- `lora_block_weight`를 스크립트 화이트리스트에 추가함. + - 한번이라도 ADetailer를 사용한 사람은 수동으로 추가해야함. + +## 2023-07-03 + +- v23.7.1 +- `process_images`를 진행한 뒤 `StableDiffusionProcessing` 오브젝트의 close를 호출함 +- api 호출로 사용했는지 확인하는 속성 추가 +- `NansException`이 발생했을 때 중지하지 않고 남은 과정 계속 진행함 + +## 2023-07-02 + +- v23.7.0 +- `NansException`이 발생하면 로그에 표시하고 원본 이미지를 반환하게 설정 +- `rich`를 사용한 에러 트레이싱 + - install.py에 `rich` 추가 +- 생성 중에 컴포넌트의 값을 변경하면 args의 값도 함께 변경되는 문제 수정 (issue #180) +- 터미널 로그로 ad_prompt와 ad_negative_prompt에 적용된 실제 프롬프트 확인할 수 있음 (입력과 다를 경우에만) + +## 2023-06-28 + +- v23.6.4 +- 최대 모델 수 5 -> 10개 +- ad_prompt와 ad_negative_prompt에 빈칸으로 놔두면 입력 프롬프트가 사용된다는 문구 추가 +- huggingface 모델 다운로드 실패시 로깅 +- 1st 모델이 `None`일 경우 나머지 입력을 무시하던 문제 수정 +- `--use-cpu` 에 `adetailer` 입력 시 cpu로 yolo모델을 사용함 + +## 2023-06-20 + +- v23.6.3 +- 컨트롤넷 inpaint 모델에 대해, 3가지 모듈을 사용할 수 있도록 함 +- Noise Multiplier 옵션 추가 (PR #149) +- pydantic 최소 버전 1.10.8로 설정 (Issue #146) + +## 2023-06-05 + +- v23.6.2 +- xyz_grid에서 ADetailer를 사용할 수 있게함. + - 8가지 옵션만 1st 탭에 적용되도록 함. + +## 2023-06-01 + +- v23.6.1 +- `inpaint, scribble, lineart, openpose, tile` 5가지 컨트롤넷 모델 지원 (PR #107) +- controlnet guidance start, end 인자 추가 (PR #107) +- `modules.extensions`를 사용하여 컨트롤넷 확장을 불러오고 경로를 알아내로록 변경 +- ui에서 컨트롤넷을 별도 함수로 분리 + +## 2023-05-30 + +- v23.6.0 +- 스크립트의 이름을 `After Detailer`에서 `ADetailer`로 변경 + - API 사용자는 변경 필요함 +- 몇몇 설정 변경 + - `ad_conf` → `ad_confidence`. 0~100 사이의 int → 0.0~1.0 사이의 float + - `ad_inpaint_full_res` → `ad_inpaint_only_masked` + - `ad_inpaint_full_res_padding` → `ad_inpaint_only_masked_padding` +- mediapipe face mesh 모델 추가 + + - mediapipe 최소 버전 `0.10.0` + +- rich traceback 제거함 +- huggingface 다운로드 실패할 때 에러가 나지 않게 하고 해당 모델을 제거함 + +## 2023-05-26 + +- v23.5.19 +- 1번째 탭에도 `None` 옵션을 추가함 +- api로 ad controlnet model에 inpaint가 아닌 다른 컨트롤넷 모델을 사용하지 못하도록 막음 +- adetailer 진행중에 total tqdm 진행바 업데이트를 멈춤 +- state.inturrupted 상태에서 adetailer 과정을 중지함 +- 컨트롤넷 process를 각 batch가 끝난 순간에만 호출하도록 변경 + +### 2023-05-25 + +- v23.5.18 +- 컨트롤넷 관련 수정 + - unit의 `input_mode`를 `SIMPLE`로 모두 변경 + - 컨트롤넷 유넷 훅과 하이잭 함수들을 adetailer를 실행할 때에만 되돌리는 기능 추가 + - adetailer 처리가 끝난 뒤 컨트롤넷 스크립트의 process를 다시 진행함. (batch count 2 이상일때의 문제 해결) +- 기본 활성 스크립트 목록에서 컨트롤넷을 뺌 + +### 2023-05-22 + +- v23.5.17 +- 컨트롤넷 확장이 있으면 컨트롤넷 스크립트를 활성화함. (컨트롤넷 관련 문제 해결) +- 모든 컴포넌트에 elem_id 설정 +- ui에 버전을 표시함 + +### 2023-05-19 + +- v23.5.16 +- 추가한 옵션 + - Mask min/max ratio + - Mask merge mode + - Restore faces after ADetailer +- 옵션들을 Accordion으로 묶음 + +### 2023-05-18 + +- v23.5.15 +- 필요한 것만 임포트하도록 변경 (vae 로딩 오류 없어짐. 로딩 속도 빨라짐) + +### 2023-05-17 + +- v23.5.14 +- `[SKIP]`으로 ad prompt 일부를 건너뛰는 기능 추가 +- bbox 정렬 옵션 추가 +- sd_webui 타입힌트를 만들어냄 +- enable checker와 관련된 api 오류 수정? + +### 2023-05-15 + +- v23.5.13 +- `[SEP]`으로 ad prompt를 분리하여 적용하는 기능 추가 +- enable checker를 다시 pydantic으로 변경함 +- ui 관련 함수를 adetailer.ui 폴더로 분리함 +- controlnet을 사용할 때 모든 controlnet unit 비활성화 +- adetailer 폴더가 없으면 만들게 함 + +### 2023-05-13 + +- v23.5.12 +- `ad_enable`을 제외한 입력이 dict타입으로 들어오도록 변경 + - web api로 사용할 때에 특히 사용하기 쉬움 + - web api breaking change +- `mask_preprocess` 인자를 넣지 않았던 오류 수정 (PR #47) +- huggingface에서 모델을 다운로드하지 않는 옵션 추가 `--ad-no-huggingface` + +### 2023-05-12 + +- v23.5.11 +- `ultralytics` 알람 제거 +- 필요없는 exif 인자 더 제거함 +- `use separate steps` 옵션 추가 +- ui 배치를 조정함 + +### 2023-05-09 + +- v23.5.10 +- 선택한 스크립트만 ADetailer에 적용하는 옵션 추가, 기본값 `True`. 설정 탭에서 지정가능. + - 기본값: `dynamic_prompting,dynamic_thresholding,wildcards,wildcard_recursive` +- `person_yolov8s-seg.pt` 모델 추가 +- `ultralytics`의 최소 버전을 `8.0.97`로 설정 (C:\\ 문제 해결된 버전) + +### 2023-05-08 + +- v23.5.9 +- 2가지 이상의 모델을 사용할 수 있음. 기본값: 2, 최대: 5 +- segment 모델을 사용할 수 있게 함. `person_yolov8n-seg.pt` 추가 + +### 2023-05-07 + +- v23.5.8 +- 프롬프트와 네거티브 프롬프트에 방향키 지원 (PR #24) +- `mask_preprocess`를 추가함. 이전 버전과 시드값이 달라질 가능성 있음! +- 이미지 처리가 일어났을 때에만 before이미지를 저장함 +- 설정창의 레이블을 ADetailer 대신 더 적절하게 수정함 + +### 2023-05-06 + +- v23.5.7 +- `ad_use_cfg_scale` 옵션 추가. cfg 스케일을 따로 사용할지 말지 결정함. +- `ad_enable` 기본값을 `True`에서 `False`로 변경 +- `ad_model`의 기본값을 `None`에서 첫번째 모델로 변경 +- 최소 2개의 입력(ad_enable, ad_model)만 들어오면 작동하게 변경. + +- v23.5.7.post0 +- `init_controlnet_ext`을 controlnet_exists == True일때에만 실행 +- webui를 C드라이브 바로 밑에 설치한 사람들에게 `ultralytics` 경고 표시 + +### 2023-05-05 (어린이날) + +- v23.5.5 +- `Save images before ADetailer` 옵션 추가 +- 입력으로 들어온 인자와 ALL_ARGS의 길이가 다르면 에러메세지 +- README.md에 설치방법 추가 + +- v23.5.6 +- get_args에서 IndexError가 발생하면 자세한 에러메세지를 볼 수 있음 +- AdetailerArgs에 extra_params 내장 +- scripts_args를 딥카피함 +- postprocess_image를 약간 분리함 + +- v23.5.6.post0 +- `init_controlnet_ext`에서 에러메세지를 자세히 볼 수 있음 + +### 2023-05-04 + +- v23.5.4 +- use pydantic for arguments validation +- revert: ad_model to `None` as default +- revert: `__future__` imports +- lazily import yolo and mediapipe + +### 2023-05-03 + +- v23.5.3.post0 +- remove `__future__` imports +- change to copy scripts and scripts args + +- v23.5.3.post1 +- change default ad_model from `None` + +### 2023-05-02 + +- v23.5.3 +- Remove `None` from model list and add `Enable ADetailer` checkbox. +- install.py `skip_install` fix. diff --git a/extensions/4-adetailer/LICENSE.md b/extensions/4-adetailer/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..d091a92e0e35339caf2ce1bd5dee27db9a3877c7 --- /dev/null +++ b/extensions/4-adetailer/LICENSE.md @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +1. Source Code. + +The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/extensions/4-adetailer/README.md b/extensions/4-adetailer/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9b22a6d7378582f9bcbd0b769df0afead2172257 --- /dev/null +++ b/extensions/4-adetailer/README.md @@ -0,0 +1,118 @@ +# ADetailer + +ADetailer is an extension for the stable diffusion webui that does automatic masking and inpainting. It is similar to the Detection Detailer. + +## Install + +You can install it directly from the Extensions tab. + +![image](https://i.imgur.com/qaXtoI6.png) + +Or + +(from Mikubill/sd-webui-controlnet) + +1. Open "Extensions" tab. +2. Open "Install from URL" tab in the tab. +3. Enter `https://github.com/Bing-su/adetailer.git` to "URL for extension's git repository". +4. Press "Install" button. +5. Wait 5 seconds, and you will see the message "Installed into stable-diffusion-webui\extensions\adetailer. Use Installed tab to restart". +6. Go to "Installed" tab, click "Check for updates", and then click "Apply and restart UI". (The next time you can also use this method to update extensions.) +7. Completely restart A1111 webui including your terminal. (If you do not know what is a "terminal", you can reboot your computer: turn your computer off and turn it on again.) + +## Options + +| Model, Prompts | | | +| --------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ADetailer model | Determine what to detect. | `None` = disable | +| ADetailer model classes | Comma separated class names to detect. only available when using YOLO World models | If blank, use default values.
default = [COCO 80 classes](https://github.com/ultralytics/ultralytics/blob/main/ultralytics/cfg/datasets/coco.yaml) | +| ADetailer prompt, negative prompt | Prompts and negative prompts to apply | If left blank, it will use the same as the input. | +| Skip img2img | Skip img2img. In practice, this works by changing the step count of img2img to 1. | img2img only | + +| Detection | | | +| ------------------------------------ | -------------------------------------------------------------------------------------------- | ------------ | +| Detection model confidence threshold | Only objects with a detection model confidence above this threshold are used for inpainting. | | +| Mask min/max ratio | Only use masks whose area is between those ratios for the area of the entire image. | | +| Mask only the top k largest | Only use the k objects with the largest area of the bbox. | 0 to disable | + +If you want to exclude objects in the background, try setting the min ratio to around `0.01`. + +| Mask Preprocessing | | | +| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| Mask x, y offset | Moves the mask horizontally and vertically by | | +| Mask erosion (-) / dilation (+) | Enlarge or reduce the detected mask. | [opencv example](https://docs.opencv.org/4.7.0/db/df6/tutorial_erosion_dilatation.html) | +| Mask merge mode | `None`: Inpaint each mask
`Merge`: Merge all masks and inpaint
`Merge and Invert`: Merge all masks and Invert, then inpaint | | + +Applied in this order: x, y offset → erosion/dilation → merge/invert. + +#### Inpainting + +Each option corresponds to a corresponding option on the inpaint tab. Therefore, please refer to the inpaint tab for usage details on how to use each option. + +## ControlNet Inpainting + +You can use the ControlNet extension if you have ControlNet installed and ControlNet models. + +Support `inpaint, scribble, lineart, openpose, tile, depth` controlnet models. Once you choose a model, the preprocessor is set automatically. It works separately from the model set by the Controlnet extension. + +If you select `Passthrough`, the controlnet settings you set outside of ADetailer will be used. + +## Advanced Options + +API request example: [wiki/REST-API](https://github.com/Bing-su/adetailer/wiki/REST-API) + +`[SEP], [SKIP], [PROMPT]` tokens: [wiki/Advanced](https://github.com/Bing-su/adetailer/wiki/Advanced) + +## Media + +- 🎥 [どこよりも詳しい After Detailer (adetailer)の使い方 ① 【Stable Diffusion】](https://youtu.be/sF3POwPUWCE) +- 🎥 [どこよりも詳しい After Detailer (adetailer)の使い方 ② 【Stable Diffusion】](https://youtu.be/urNISRdbIEg) + +- 📜 [ADetailer Installation and 5 Usage Methods](https://kindanai.com/en/manual-adetailer/) + +## Model + +| Model | Target | mAP 50 | mAP 50-95 | +| --------------------- | --------------------- | ----------------------------- | ----------------------------- | +| face_yolov8n.pt | 2D / realistic face | 0.660 | 0.366 | +| face_yolov8s.pt | 2D / realistic face | 0.713 | 0.404 | +| hand_yolov8n.pt | 2D / realistic hand | 0.767 | 0.505 | +| person_yolov8n-seg.pt | 2D / realistic person | 0.782 (bbox)
0.761 (mask) | 0.555 (bbox)
0.460 (mask) | +| person_yolov8s-seg.pt | 2D / realistic person | 0.824 (bbox)
0.809 (mask) | 0.605 (bbox)
0.508 (mask) | +| mediapipe_face_full | realistic face | - | - | +| mediapipe_face_short | realistic face | - | - | +| mediapipe_face_mesh | realistic face | - | - | + +The YOLO models can be found on huggingface [Bingsu/adetailer](https://huggingface.co/Bingsu/adetailer). + +For a detailed description of the YOLO8 model, see: https://docs.ultralytics.com/models/yolov8/#overview + +YOLO World model: https://docs.ultralytics.com/models/yolo-world/ + +### Additional Model + +Put your [ultralytics](https://github.com/ultralytics/ultralytics) yolo model in `models/adetailer`. The model name should end with `.pt`. + +It must be a bbox detection or segment model and use all label. + +## How it works + +ADetailer works in three simple steps. + +1. Create an image. +2. Detect object with a detection model and create a mask image. +3. Inpaint using the image from 1 and the mask from 2. + +## Development + +ADetailer is developed and tested using the stable-diffusion 1.5 model, for the latest version of [AUTOMATIC1111/stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) repository only. + +## License + +ADetailer is a derivative work that uses two AGPL-licensed works (stable-diffusion-webui, ultralytics) and is therefore distributed under the AGPL license. + +## See Also + +- https://github.com/ototadana/sd-face-editor +- https://github.com/continue-revolution/sd-webui-segment-anything +- https://github.com/portu-sim/sd-webui-bmab diff --git a/extensions/4-adetailer/Taskfile.yml b/extensions/4-adetailer/Taskfile.yml new file mode 100644 index 0000000000000000000000000000000000000000..03a4e8adff609e3d5278f2a1b97ea6eeca0327de --- /dev/null +++ b/extensions/4-adetailer/Taskfile.yml @@ -0,0 +1,32 @@ +# https://taskfile.dev + +version: "3" + +dotenv: + - .env + +tasks: + default: + cmds: + - echo "$PYTHON" + - echo "$WEBUI" + - echo "$UV_PYTHON" + silent: true + + launch: + dir: "{{.WEBUI}}" + cmds: + - "{{.PYTHON}} launch.py --xformers --api" + silent: true + + lint: + cmds: + - pre-commit run -a + + update: + cmds: + - "{{.PYTHON}} -m uv pip install -U ultralytics mediapipe ruff pre-commit black devtools pytest" + + update-torch: + cmds: + - "{{.PYTHON}} -m uv pip install -U torch torchvision torchaudio -f https://download.pytorch.org/whl/torch_stable.html" diff --git a/extensions/4-adetailer/__pycache__/preload.cpython-310.pyc b/extensions/4-adetailer/__pycache__/preload.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37a94c52a22e263f90d4c1e11edbb739df80b56e Binary files /dev/null and b/extensions/4-adetailer/__pycache__/preload.cpython-310.pyc differ diff --git a/extensions/4-adetailer/aaaaaa/__init__.py b/extensions/4-adetailer/aaaaaa/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/extensions/4-adetailer/aaaaaa/__pycache__/__init__.cpython-310.pyc b/extensions/4-adetailer/aaaaaa/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c1f39fe4726b2416ababd6c4cc07d33cca35ee9 Binary files /dev/null and b/extensions/4-adetailer/aaaaaa/__pycache__/__init__.cpython-310.pyc differ diff --git a/extensions/4-adetailer/aaaaaa/__pycache__/conditional.cpython-310.pyc b/extensions/4-adetailer/aaaaaa/__pycache__/conditional.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfc3f83f0a9aaefccdb06e3f4a096e03f95dfd15 Binary files /dev/null and b/extensions/4-adetailer/aaaaaa/__pycache__/conditional.cpython-310.pyc differ diff --git a/extensions/4-adetailer/aaaaaa/__pycache__/helper.cpython-310.pyc b/extensions/4-adetailer/aaaaaa/__pycache__/helper.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28804f004a4b5a438b7230b443c1745fb72fb189 Binary files /dev/null and b/extensions/4-adetailer/aaaaaa/__pycache__/helper.cpython-310.pyc differ diff --git a/extensions/4-adetailer/aaaaaa/__pycache__/p_method.cpython-310.pyc b/extensions/4-adetailer/aaaaaa/__pycache__/p_method.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4767d937f769266e70b95874eeff5b6da52cb2a Binary files /dev/null and b/extensions/4-adetailer/aaaaaa/__pycache__/p_method.cpython-310.pyc differ diff --git a/extensions/4-adetailer/aaaaaa/__pycache__/traceback.cpython-310.pyc b/extensions/4-adetailer/aaaaaa/__pycache__/traceback.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d184ca4aa1b33b6677bd0f8118af355ef3dbb9b Binary files /dev/null and b/extensions/4-adetailer/aaaaaa/__pycache__/traceback.cpython-310.pyc differ diff --git a/extensions/4-adetailer/aaaaaa/__pycache__/ui.cpython-310.pyc b/extensions/4-adetailer/aaaaaa/__pycache__/ui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c9e25b9e1d2190a1f380f3cddcde9d1ad4b937f Binary files /dev/null and b/extensions/4-adetailer/aaaaaa/__pycache__/ui.cpython-310.pyc differ diff --git a/extensions/4-adetailer/aaaaaa/conditional.py b/extensions/4-adetailer/aaaaaa/conditional.py new file mode 100644 index 0000000000000000000000000000000000000000..22afb10140871d19cc858b9ff8887ad43b2a4932 --- /dev/null +++ b/extensions/4-adetailer/aaaaaa/conditional.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +msg = "[-] ADetailer: WebUI versions below 1.6.0 are not supported." + +try: + from modules.processing import create_binary_mask # noqa: F401 +except ImportError as e: + raise RuntimeError(msg) from e + + +try: + from modules.ui_components import InputAccordion # noqa: F401 +except ImportError as e: + raise RuntimeError(msg) from e + + +try: + from modules.sd_schedulers import schedulers +except ImportError: + # webui < 1.9.0 + schedulers = [] diff --git a/extensions/4-adetailer/aaaaaa/helper.py b/extensions/4-adetailer/aaaaaa/helper.py new file mode 100644 index 0000000000000000000000000000000000000000..c9c50c9d9dd7e62c33733efde41618e39e10cf4e --- /dev/null +++ b/extensions/4-adetailer/aaaaaa/helper.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from contextlib import contextmanager +from copy import copy +from typing import TYPE_CHECKING, Any, Union + +import torch + +from modules import safe +from modules.shared import opts + +if TYPE_CHECKING: + # 타입 체커가 빨간 줄을 긋지 않게 하는 편법 + from types import SimpleNamespace + + StableDiffusionProcessingTxt2Img = SimpleNamespace + StableDiffusionProcessingImg2Img = SimpleNamespace +else: + from modules.processing import ( + StableDiffusionProcessingImg2Img, + StableDiffusionProcessingTxt2Img, + ) + +PT = Union[StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img] + + +@contextmanager +def change_torch_load(): + orig = torch.load + try: + torch.load = safe.unsafe_torch_load + yield + finally: + torch.load = orig + + +@contextmanager +def pause_total_tqdm(): + orig = opts.data.get("multiple_tqdm", True) + try: + opts.data["multiple_tqdm"] = False + yield + finally: + opts.data["multiple_tqdm"] = orig + + +@contextmanager +def preserve_prompts(p: PT): + all_pt = copy(p.all_prompts) + all_ng = copy(p.all_negative_prompts) + try: + yield + finally: + p.all_prompts = all_pt + p.all_negative_prompts = all_ng + + +def copy_extra_params(extra_params: dict[str, Any]) -> dict[str, Any]: + return {k: v for k, v in extra_params.items() if not callable(v)} diff --git a/extensions/4-adetailer/aaaaaa/p_method.py b/extensions/4-adetailer/aaaaaa/p_method.py new file mode 100644 index 0000000000000000000000000000000000000000..a9dfd146bbf8c4c499ccc4ba87eb34da4b745d28 --- /dev/null +++ b/extensions/4-adetailer/aaaaaa/p_method.py @@ -0,0 +1,34 @@ +from __future__ import annotations + + +def need_call_process(p) -> bool: + if p.scripts is None: + return False + i = p.batch_index + bs = p.batch_size + return i == bs - 1 + + +def need_call_postprocess(p) -> bool: + if p.scripts is None: + return False + return p.batch_index == 0 + + +def is_img2img_inpaint(p) -> bool: + return hasattr(p, "image_mask") and p.image_mask is not None + + +def is_inpaint_only_masked(p) -> bool: + return hasattr(p, "inpaint_full_res") and p.inpaint_full_res + + +def get_i(p) -> int: + it = p.iteration + bs = p.batch_size + i = p.batch_index + return it * bs + i + + +def is_skip_img2img(p) -> bool: + return getattr(p, "_ad_skip_img2img", False) diff --git a/extensions/4-adetailer/aaaaaa/traceback.py b/extensions/4-adetailer/aaaaaa/traceback.py new file mode 100644 index 0000000000000000000000000000000000000000..3f7e44a9b6c89b39efbb825c4a65e5d126d02a48 --- /dev/null +++ b/extensions/4-adetailer/aaaaaa/traceback.py @@ -0,0 +1,175 @@ +from __future__ import annotations + +import io +import platform +import sys +from collections.abc import Callable +from importlib.metadata import version +from typing import Any, TypeVar + +from rich.console import Console, Group +from rich.panel import Panel +from rich.table import Table +from rich.traceback import Traceback +from typing_extensions import ParamSpec + +from adetailer.__version__ import __version__ +from adetailer.args import ADetailerArgs + + +def processing(*args: Any) -> dict[str, Any]: + try: + from modules.processing import ( + StableDiffusionProcessingImg2Img, + StableDiffusionProcessingTxt2Img, + ) + except ImportError: + return {} + + p = None + for arg in args: + if isinstance( + arg, (StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img) + ): + p = arg + break + + if p is None: + return {} + + info = { + "prompt": p.prompt, + "negative_prompt": p.negative_prompt, + "n_iter": p.n_iter, + "batch_size": p.batch_size, + "width": p.width, + "height": p.height, + "sampler_name": p.sampler_name, + "enable_hr": getattr(p, "enable_hr", False), + "hr_upscaler": getattr(p, "hr_upscaler", ""), + } + + info.update(sd_models()) + return info + + +def sd_models() -> dict[str, str]: + try: + from modules import shared + + opts = shared.opts + except Exception: + return {} + + return { + "checkpoint": getattr(opts, "sd_model_checkpoint", "------"), + "vae": getattr(opts, "sd_vae", "------"), + "unet": getattr(opts, "sd_unet", "------"), + } + + +def ad_args(*args: Any) -> dict[str, Any]: + ad_args = [] + for arg in args: + if not isinstance(arg, dict): + continue + + try: + a = ADetailerArgs(**arg) + except ValueError: + continue + + if not a.need_skip(): + ad_args.append(a) + + if not ad_args: + return {} + + arg0 = ad_args[0] + return { + "version": __version__, + "ad_model": arg0.ad_model, + "ad_prompt": arg0.ad_prompt, + "ad_negative_prompt": arg0.ad_negative_prompt, + "ad_controlnet_model": arg0.ad_controlnet_model, + "is_api": arg0.is_api, + } + + +def library_version(): + libraries = ["torch", "torchvision", "ultralytics", "mediapipe"] + d = {} + for lib in libraries: + try: + d[lib] = version(lib) + except Exception: # noqa: PERF203 + d[lib] = "Unknown" + return d + + +def sys_info() -> dict[str, Any]: + try: + import launch + + version = launch.git_tag() + commit = launch.commit_hash() + except Exception: + version = "Unknown (too old or vladmandic)" + commit = "Unknown" + + return { + "Platform": platform.platform(), + "Python": sys.version, + "Version": version, + "Commit": commit, + "Commandline": sys.argv, + "Libraries": library_version(), + } + + +def get_table(title: str, data: dict[str, Any]) -> Table: + table = Table(title=title, highlight=True) + table.add_column(" ", justify="right", style="dim") + table.add_column("Value") + for key, value in data.items(): + if not isinstance(value, str): + value = repr(value) + table.add_row(key, value) + + return table + + +P = ParamSpec("P") +T = TypeVar("T") + + +def rich_traceback(func: Callable[P, T]) -> Callable[P, T]: + def wrapper(*args, **kwargs): + string = io.StringIO() + width = Console().width + width = width - 4 if width > 4 else None + console = Console(file=string, width=width) + try: + return func(*args, **kwargs) + except Exception as e: + tables = [ + get_table(title, data) + for title, data in [ + ("System info", sys_info()), + ("Inputs", processing(*args)), + ("ADetailer", ad_args(*args)), + ] + if data + ] + tables.append(Traceback(extra_lines=1)) + + console.print(Panel(Group(*tables))) + output = "\n" + string.getvalue() + + try: + error = e.__class__(output) + except Exception: + error = RuntimeError(output) + raise error from None + + return wrapper diff --git a/extensions/4-adetailer/aaaaaa/ui.py b/extensions/4-adetailer/aaaaaa/ui.py new file mode 100644 index 0000000000000000000000000000000000000000..e4efb5450345766ef095eb248ec8d6809b94e30f --- /dev/null +++ b/extensions/4-adetailer/aaaaaa/ui.py @@ -0,0 +1,710 @@ +from __future__ import annotations + +from dataclasses import dataclass +from functools import partial +from itertools import chain +from types import SimpleNamespace +from typing import Any + +import gradio as gr + +from aaaaaa.conditional import InputAccordion +from adetailer import ADETAILER, __version__ +from adetailer.args import ALL_ARGS, MASK_MERGE_INVERT +from controlnet_ext import controlnet_exists, controlnet_type, get_cn_models + +if controlnet_type == "forge": + from lib_controlnet import global_state + + cn_module_choices = { + "inpaint": list(global_state.get_filtered_preprocessors("Inpaint")), + "lineart": list(global_state.get_filtered_preprocessors("Lineart")), + "openpose": list(global_state.get_filtered_preprocessors("OpenPose")), + "tile": list(global_state.get_filtered_preprocessors("Tile")), + "scribble": list(global_state.get_filtered_preprocessors("Scribble")), + "depth": list(global_state.get_filtered_preprocessors("Depth")), + } +else: + cn_module_choices = { + "inpaint": [ + "inpaint_global_harmonious", + "inpaint_only", + "inpaint_only+lama", + ], + "lineart": [ + "lineart_coarse", + "lineart_realistic", + "lineart_anime", + "lineart_anime_denoise", + ], + "openpose": ["openpose_full", "dw_openpose_full"], + "tile": ["tile_resample", "tile_colorfix", "tile_colorfix+sharp"], + "scribble": ["t2ia_sketch_pidi"], + "depth": ["depth_midas", "depth_hand_refiner"], + } + +union = list(chain.from_iterable(cn_module_choices.values())) +cn_module_choices["union"] = union + + +class Widgets(SimpleNamespace): + def tolist(self): + return [getattr(self, attr) for attr in ALL_ARGS.attrs] + + +@dataclass +class WebuiInfo: + ad_model_list: list[str] + sampler_names: list[str] + scheduler_names: list[str] + t2i_button: gr.Button + i2i_button: gr.Button + checkpoints_list: list[str] + vae_list: list[str] + + +def gr_interactive(value: bool = True): + return gr.update(interactive=value) + + +def ordinal(n: int) -> str: + d = {1: "st", 2: "nd", 3: "rd"} + return str(n) + ("th" if 11 <= n % 100 <= 13 else d.get(n % 10, "th")) + + +def suffix(n: int, c: str = " ") -> str: + return "" if n == 0 else c + ordinal(n + 1) + + +def on_widget_change(state: dict, value: Any, *, attr: str): + if "is_api" in state: + state = state.copy() + state.pop("is_api") + state[attr] = value + return state + + +def on_generate_click(state: dict, *values: Any): + for attr, value in zip(ALL_ARGS.attrs, values): + state[attr] = value # noqa: PERF403 + state["is_api"] = () + return state + + +def on_ad_model_update(model: str): + if "-world" in model: + return gr.update( + visible=True, + placeholder="Comma separated class names to detect, ex: 'person,cat'. default: COCO 80 classes", + ) + return gr.update(visible=False, placeholder="") + + +def on_cn_model_update(cn_model_name: str): + cn_model_name = cn_model_name.replace("inpaint_depth", "depth") + for t in cn_module_choices: + if t in cn_model_name: + choices = cn_module_choices[t] + return gr.update(visible=True, choices=choices, value=choices[0]) + return gr.update(visible=False, choices=["None"], value="None") + + +def elem_id(item_id: str, n: int, is_img2img: bool) -> str: + tab = "img2img" if is_img2img else "txt2img" + suf = suffix(n, "_") + return f"script_{tab}_adetailer_{item_id}{suf}" + + +def state_init(w: Widgets) -> dict[str, Any]: + return {attr: getattr(w, attr).value for attr in ALL_ARGS.attrs} + + +def adui( + num_models: int, + is_img2img: bool, + webui_info: WebuiInfo, +): + states = [] + infotext_fields = [] + eid = partial(elem_id, n=0, is_img2img=is_img2img) + + with InputAccordion( + value=False, + elem_id=eid("ad_main_accordion"), + label=ADETAILER, + visible=True, + ) as ad_enable: + with gr.Row(): + with gr.Column(scale=8): + ad_skip_img2img = gr.Checkbox( + label="Skip img2img", + value=False, + visible=is_img2img, + elem_id=eid("ad_skip_img2img"), + ) + + with gr.Column(scale=1, min_width=180): + gr.Markdown( + f"v{__version__}", + elem_id=eid("ad_version"), + ) + + infotext_fields.append((ad_enable, "ADetailer enable")) + infotext_fields.append((ad_skip_img2img, "ADetailer skip img2img")) + + with gr.Group(), gr.Tabs(): + for n in range(num_models): + with gr.Tab(ordinal(n + 1)): + state, infofields = one_ui_group( + n=n, + is_img2img=is_img2img, + webui_info=webui_info, + ) + + states.append(state) + infotext_fields.extend(infofields) + + # components: [bool, bool, dict, dict, ...] + components = [ad_enable, ad_skip_img2img, *states] + return components, infotext_fields + + +def one_ui_group(n: int, is_img2img: bool, webui_info: WebuiInfo): + w = Widgets() + eid = partial(elem_id, n=n, is_img2img=is_img2img) + + model_choices = ( + [*webui_info.ad_model_list, "None"] + if n == 0 + else ["None", *webui_info.ad_model_list] + ) + + with gr.Group(): + with gr.Row(variant="compact"): + w.ad_tab_enable = gr.Checkbox( + label=f"Enable this tab ({ordinal(n + 1)})", + value=True, + visible=True, + elem_id=eid("ad_tab_enable"), + ) + + with gr.Row(): + w.ad_model = gr.Dropdown( + label="ADetailer detector" + suffix(n), + choices=model_choices, + value=model_choices[0], + visible=True, + type="value", + elem_id=eid("ad_model"), + info="Select a model to use for detection.", + ) + + with gr.Row(): + w.ad_model_classes = gr.Textbox( + label="ADetailer detector classes" + suffix(n), + value="", + visible=False, + elem_id=eid("ad_classes"), + ) + + w.ad_model.change( + on_ad_model_update, + inputs=w.ad_model, + outputs=w.ad_model_classes, + queue=False, + ) + + gr.HTML("
") + + with gr.Group(): + with gr.Row(elem_id=eid("ad_toprow_prompt")): + w.ad_prompt = gr.Textbox( + label="ad_prompt" + suffix(n), + show_label=False, + lines=3, + placeholder="ADetailer prompt" + + suffix(n) + + "\nIf blank, the main prompt is used.", + elem_id=eid("ad_prompt"), + ) + + with gr.Row(elem_id=eid("ad_toprow_negative_prompt")): + w.ad_negative_prompt = gr.Textbox( + label="ad_negative_prompt" + suffix(n), + show_label=False, + lines=2, + placeholder="ADetailer negative prompt" + + suffix(n) + + "\nIf blank, the main negative prompt is used.", + elem_id=eid("ad_negative_prompt"), + ) + + with gr.Group(): + with gr.Accordion( + "Detection", open=False, elem_id=eid("ad_detection_accordion") + ): + detection(w, n, is_img2img) + + with gr.Accordion( + "Mask Preprocessing", + open=False, + elem_id=eid("ad_mask_preprocessing_accordion"), + ): + mask_preprocessing(w, n, is_img2img) + + with gr.Accordion( + "Inpainting", open=False, elem_id=eid("ad_inpainting_accordion") + ): + inpainting(w, n, is_img2img, webui_info) + + with gr.Group(): + controlnet(w, n, is_img2img) + + state = gr.State(lambda: state_init(w)) + + for attr in ALL_ARGS.attrs: + widget = getattr(w, attr) + on_change = partial(on_widget_change, attr=attr) + widget.change(fn=on_change, inputs=[state, widget], outputs=state, queue=False) + + all_inputs = [state, *w.tolist()] + target_button = webui_info.i2i_button if is_img2img else webui_info.t2i_button + target_button.click( + fn=on_generate_click, inputs=all_inputs, outputs=state, queue=False + ) + + infotext_fields = [(getattr(w, attr), name + suffix(n)) for attr, name in ALL_ARGS] + + return state, infotext_fields + + +def detection(w: Widgets, n: int, is_img2img: bool): + eid = partial(elem_id, n=n, is_img2img=is_img2img) + + with gr.Row(): + with gr.Column(variant="compact"): + w.ad_confidence = gr.Slider( + label="Detection model confidence threshold" + suffix(n), + minimum=0.0, + maximum=1.0, + step=0.01, + value=0.3, + visible=True, + elem_id=eid("ad_confidence"), + ) + w.ad_mask_k_largest = gr.Slider( + label="Mask only the top k largest (0 to disable)" + suffix(n), + minimum=0, + maximum=10, + step=1, + value=0, + visible=True, + elem_id=eid("ad_mask_k_largest"), + ) + + with gr.Column(variant="compact"): + w.ad_mask_min_ratio = gr.Slider( + label="Mask min area ratio" + suffix(n), + minimum=0.0, + maximum=1.0, + step=0.001, + value=0.0, + visible=True, + elem_id=eid("ad_mask_min_ratio"), + ) + w.ad_mask_max_ratio = gr.Slider( + label="Mask max area ratio" + suffix(n), + minimum=0.0, + maximum=1.0, + step=0.001, + value=1.0, + visible=True, + elem_id=eid("ad_mask_max_ratio"), + ) + + +def mask_preprocessing(w: Widgets, n: int, is_img2img: bool): + eid = partial(elem_id, n=n, is_img2img=is_img2img) + + with gr.Group(): + with gr.Row(): + with gr.Column(variant="compact"): + w.ad_x_offset = gr.Slider( + label="Mask x(→) offset" + suffix(n), + minimum=-200, + maximum=200, + step=1, + value=0, + visible=True, + elem_id=eid("ad_x_offset"), + ) + w.ad_y_offset = gr.Slider( + label="Mask y(↑) offset" + suffix(n), + minimum=-200, + maximum=200, + step=1, + value=0, + visible=True, + elem_id=eid("ad_y_offset"), + ) + + with gr.Column(variant="compact"): + w.ad_dilate_erode = gr.Slider( + label="Mask erosion (-) / dilation (+)" + suffix(n), + minimum=-128, + maximum=128, + step=4, + value=4, + visible=True, + elem_id=eid("ad_dilate_erode"), + ) + + with gr.Row(): + w.ad_mask_merge_invert = gr.Radio( + label="Mask merge mode" + suffix(n), + choices=MASK_MERGE_INVERT, + value="None", + elem_id=eid("ad_mask_merge_invert"), + info="None: do nothing, Merge: merge masks, Merge and Invert: merge all masks and invert", + ) + + +def inpainting(w: Widgets, n: int, is_img2img: bool, webui_info: WebuiInfo): + eid = partial(elem_id, n=n, is_img2img=is_img2img) + + with gr.Group(): + with gr.Row(): + w.ad_mask_blur = gr.Slider( + label="Inpaint mask blur" + suffix(n), + minimum=0, + maximum=64, + step=1, + value=4, + visible=True, + elem_id=eid("ad_mask_blur"), + ) + + w.ad_denoising_strength = gr.Slider( + label="Inpaint denoising strength" + suffix(n), + minimum=0.0, + maximum=1.0, + step=0.01, + value=0.4, + visible=True, + elem_id=eid("ad_denoising_strength"), + ) + + with gr.Row(): + with gr.Column(variant="compact"): + w.ad_inpaint_only_masked = gr.Checkbox( + label="Inpaint only masked" + suffix(n), + value=True, + visible=True, + elem_id=eid("ad_inpaint_only_masked"), + ) + w.ad_inpaint_only_masked_padding = gr.Slider( + label="Inpaint only masked padding, pixels" + suffix(n), + minimum=0, + maximum=256, + step=4, + value=32, + visible=True, + elem_id=eid("ad_inpaint_only_masked_padding"), + ) + + w.ad_inpaint_only_masked.change( + gr_interactive, + inputs=w.ad_inpaint_only_masked, + outputs=w.ad_inpaint_only_masked_padding, + queue=False, + ) + + with gr.Column(variant="compact"): + w.ad_use_inpaint_width_height = gr.Checkbox( + label="Use separate width/height" + suffix(n), + value=False, + visible=True, + elem_id=eid("ad_use_inpaint_width_height"), + ) + + w.ad_inpaint_width = gr.Slider( + label="inpaint width" + suffix(n), + minimum=64, + maximum=2048, + step=4, + value=512, + visible=True, + elem_id=eid("ad_inpaint_width"), + ) + + w.ad_inpaint_height = gr.Slider( + label="inpaint height" + suffix(n), + minimum=64, + maximum=2048, + step=4, + value=512, + visible=True, + elem_id=eid("ad_inpaint_height"), + ) + + w.ad_use_inpaint_width_height.change( + lambda value: (gr_interactive(value), gr_interactive(value)), + inputs=w.ad_use_inpaint_width_height, + outputs=[w.ad_inpaint_width, w.ad_inpaint_height], + queue=False, + ) + + with gr.Row(): + with gr.Column(variant="compact"): + w.ad_use_steps = gr.Checkbox( + label="Use separate steps" + suffix(n), + value=False, + visible=True, + elem_id=eid("ad_use_steps"), + ) + + w.ad_steps = gr.Slider( + label="ADetailer steps" + suffix(n), + minimum=1, + maximum=150, + step=1, + value=28, + visible=True, + elem_id=eid("ad_steps"), + ) + + w.ad_use_steps.change( + gr_interactive, + inputs=w.ad_use_steps, + outputs=w.ad_steps, + queue=False, + ) + + with gr.Column(variant="compact"): + w.ad_use_cfg_scale = gr.Checkbox( + label="Use separate CFG scale" + suffix(n), + value=False, + visible=True, + elem_id=eid("ad_use_cfg_scale"), + ) + + w.ad_cfg_scale = gr.Slider( + label="ADetailer CFG scale" + suffix(n), + minimum=0.0, + maximum=30.0, + step=0.5, + value=7.0, + visible=True, + elem_id=eid("ad_cfg_scale"), + ) + + w.ad_use_cfg_scale.change( + gr_interactive, + inputs=w.ad_use_cfg_scale, + outputs=w.ad_cfg_scale, + queue=False, + ) + + with gr.Row(): + with gr.Column(variant="compact"): + w.ad_use_checkpoint = gr.Checkbox( + label="Use separate checkpoint" + suffix(n), + value=False, + visible=True, + elem_id=eid("ad_use_checkpoint"), + ) + + ckpts = ["Use same checkpoint", *webui_info.checkpoints_list] + + w.ad_checkpoint = gr.Dropdown( + label="ADetailer checkpoint" + suffix(n), + choices=ckpts, + value=ckpts[0], + visible=True, + elem_id=eid("ad_checkpoint"), + ) + + with gr.Column(variant="compact"): + w.ad_use_vae = gr.Checkbox( + label="Use separate VAE" + suffix(n), + value=False, + visible=True, + elem_id=eid("ad_use_vae"), + ) + + vaes = ["Use same VAE", *webui_info.vae_list] + + w.ad_vae = gr.Dropdown( + label="ADetailer VAE" + suffix(n), + choices=vaes, + value=vaes[0], + visible=True, + elem_id=eid("ad_vae"), + ) + + with gr.Row(), gr.Column(variant="compact"): + w.ad_use_sampler = gr.Checkbox( + label="Use separate sampler" + suffix(n), + value=False, + visible=True, + elem_id=eid("ad_use_sampler"), + ) + + sampler_names = [ + "Use same sampler", + *webui_info.sampler_names, + ] + + with gr.Row(): + w.ad_sampler = gr.Dropdown( + label="ADetailer sampler" + suffix(n), + choices=sampler_names, + value=sampler_names[1], + visible=True, + elem_id=eid("ad_sampler"), + ) + + scheduler_names = [ + "Use same scheduler", + *webui_info.scheduler_names, + ] + w.ad_scheduler = gr.Dropdown( + label="ADetailer scheduler" + suffix(n), + choices=scheduler_names, + value=scheduler_names[0], + visible=len(scheduler_names) > 1, + elem_id=eid("ad_scheduler"), + ) + + w.ad_use_sampler.change( + lambda value: (gr_interactive(value), gr_interactive(value)), + inputs=w.ad_use_sampler, + outputs=[w.ad_sampler, w.ad_scheduler], + queue=False, + ) + + with gr.Row(): + with gr.Column(variant="compact"): + w.ad_use_noise_multiplier = gr.Checkbox( + label="Use separate noise multiplier" + suffix(n), + value=False, + visible=True, + elem_id=eid("ad_use_noise_multiplier"), + ) + + w.ad_noise_multiplier = gr.Slider( + label="Noise multiplier for img2img" + suffix(n), + minimum=0.5, + maximum=1.5, + step=0.01, + value=1.0, + visible=True, + elem_id=eid("ad_noise_multiplier"), + ) + + w.ad_use_noise_multiplier.change( + gr_interactive, + inputs=w.ad_use_noise_multiplier, + outputs=w.ad_noise_multiplier, + queue=False, + ) + + with gr.Column(variant="compact"): + w.ad_use_clip_skip = gr.Checkbox( + label="Use separate CLIP skip" + suffix(n), + value=False, + visible=True, + elem_id=eid("ad_use_clip_skip"), + ) + + w.ad_clip_skip = gr.Slider( + label="ADetailer CLIP skip" + suffix(n), + minimum=1, + maximum=12, + step=1, + value=1, + visible=True, + elem_id=eid("ad_clip_skip"), + ) + + w.ad_use_clip_skip.change( + gr_interactive, + inputs=w.ad_use_clip_skip, + outputs=w.ad_clip_skip, + queue=False, + ) + + with gr.Row(), gr.Column(variant="compact"): + w.ad_restore_face = gr.Checkbox( + label="Restore faces after ADetailer" + suffix(n), + value=False, + elem_id=eid("ad_restore_face"), + ) + + +def controlnet(w: Widgets, n: int, is_img2img: bool): + eid = partial(elem_id, n=n, is_img2img=is_img2img) + cn_models = ["None", "Passthrough", *get_cn_models()] + + with gr.Row(variant="panel"): + with gr.Column(variant="compact"): + w.ad_controlnet_model = gr.Dropdown( + label="ControlNet model" + suffix(n), + choices=cn_models, + value="None", + visible=True, + type="value", + interactive=controlnet_exists, + elem_id=eid("ad_controlnet_model"), + ) + + w.ad_controlnet_module = gr.Dropdown( + label="ControlNet module" + suffix(n), + choices=["None"], + value="None", + visible=False, + type="value", + interactive=controlnet_exists, + elem_id=eid("ad_controlnet_module"), + ) + + w.ad_controlnet_weight = gr.Slider( + label="ControlNet weight" + suffix(n), + minimum=0.0, + maximum=1.0, + step=0.01, + value=1.0, + visible=True, + interactive=controlnet_exists, + elem_id=eid("ad_controlnet_weight"), + ) + + w.ad_controlnet_model.change( + on_cn_model_update, + inputs=w.ad_controlnet_model, + outputs=w.ad_controlnet_module, + queue=False, + ) + + with gr.Column(variant="compact"): + w.ad_controlnet_guidance_start = gr.Slider( + label="ControlNet guidance start" + suffix(n), + minimum=0.0, + maximum=1.0, + step=0.01, + value=0.0, + visible=True, + interactive=controlnet_exists, + elem_id=eid("ad_controlnet_guidance_start"), + ) + + w.ad_controlnet_guidance_end = gr.Slider( + label="ControlNet guidance end" + suffix(n), + minimum=0.0, + maximum=1.0, + step=0.01, + value=1.0, + visible=True, + interactive=controlnet_exists, + elem_id=eid("ad_controlnet_guidance_end"), + ) diff --git a/extensions/4-adetailer/adetailer/__init__.py b/extensions/4-adetailer/adetailer/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6e6721e455cd9172c62b3a44c44a30fabaea12af --- /dev/null +++ b/extensions/4-adetailer/adetailer/__init__.py @@ -0,0 +1,18 @@ +from .__version__ import __version__ +from .args import ALL_ARGS, ADetailerArgs +from .common import PredictOutput, get_models +from .mediapipe import mediapipe_predict +from .ultralytics import ultralytics_predict + +ADETAILER = "ADetailer" + +__all__ = [ + "__version__", + "ADetailerArgs", + "ADETAILER", + "ALL_ARGS", + "PredictOutput", + "get_models", + "mediapipe_predict", + "ultralytics_predict", +] diff --git a/extensions/4-adetailer/adetailer/__pycache__/__init__.cpython-310.pyc b/extensions/4-adetailer/adetailer/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..607020ae6ee00e9c8aa48b71267de6aa54f9a3a5 Binary files /dev/null and b/extensions/4-adetailer/adetailer/__pycache__/__init__.cpython-310.pyc differ diff --git a/extensions/4-adetailer/adetailer/__pycache__/__version__.cpython-310.pyc b/extensions/4-adetailer/adetailer/__pycache__/__version__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0f727ad3b562681fc33ac2ad32e21c3275ed520 Binary files /dev/null and b/extensions/4-adetailer/adetailer/__pycache__/__version__.cpython-310.pyc differ diff --git a/extensions/4-adetailer/adetailer/__pycache__/args.cpython-310.pyc b/extensions/4-adetailer/adetailer/__pycache__/args.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a74a52f4e8cfe74d109dbc1fdd417b1d3742c5b4 Binary files /dev/null and b/extensions/4-adetailer/adetailer/__pycache__/args.cpython-310.pyc differ diff --git a/extensions/4-adetailer/adetailer/__pycache__/common.cpython-310.pyc b/extensions/4-adetailer/adetailer/__pycache__/common.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4404ec202753987a7f1c5234def7bd1cfc0e0a63 Binary files /dev/null and b/extensions/4-adetailer/adetailer/__pycache__/common.cpython-310.pyc differ diff --git a/extensions/4-adetailer/adetailer/__pycache__/mask.cpython-310.pyc b/extensions/4-adetailer/adetailer/__pycache__/mask.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b7bbf329540dc73f612713c6ea2d00a0c6ce9e7 Binary files /dev/null and b/extensions/4-adetailer/adetailer/__pycache__/mask.cpython-310.pyc differ diff --git a/extensions/4-adetailer/adetailer/__pycache__/mediapipe.cpython-310.pyc b/extensions/4-adetailer/adetailer/__pycache__/mediapipe.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4823d86faded365023168e930988f7b062a99afc Binary files /dev/null and b/extensions/4-adetailer/adetailer/__pycache__/mediapipe.cpython-310.pyc differ diff --git a/extensions/4-adetailer/adetailer/__pycache__/ultralytics.cpython-310.pyc b/extensions/4-adetailer/adetailer/__pycache__/ultralytics.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a66e88cc82b9e09a8aecae7e4f52aca589896ce Binary files /dev/null and b/extensions/4-adetailer/adetailer/__pycache__/ultralytics.cpython-310.pyc differ diff --git a/extensions/4-adetailer/adetailer/__version__.py b/extensions/4-adetailer/adetailer/__version__.py new file mode 100644 index 0000000000000000000000000000000000000000..45adadaa041a479fe286d7f09643f19e29be9bfa --- /dev/null +++ b/extensions/4-adetailer/adetailer/__version__.py @@ -0,0 +1 @@ +__version__ = "24.8.0" diff --git a/extensions/4-adetailer/adetailer/args.py b/extensions/4-adetailer/adetailer/args.py new file mode 100644 index 0000000000000000000000000000000000000000..ebfd2c020005a8308e1f9de4f37229d568682065 --- /dev/null +++ b/extensions/4-adetailer/adetailer/args.py @@ -0,0 +1,278 @@ +from __future__ import annotations + +from collections import UserList +from dataclasses import dataclass +from functools import cached_property, partial +from typing import Any, Literal, NamedTuple, Optional + +try: + from pydantic.v1 import ( + BaseModel, + Extra, + NonNegativeFloat, + NonNegativeInt, + PositiveInt, + confloat, + conint, + validator, + ) +except ImportError: + from pydantic import ( + BaseModel, + Extra, + NonNegativeFloat, + NonNegativeInt, + PositiveInt, + confloat, + conint, + validator, + ) + + +@dataclass +class SkipImg2ImgOrig: + steps: int + sampler_name: str + width: int + height: int + + +class Arg(NamedTuple): + attr: str + name: str + + +class ArgsList(UserList): + @cached_property + def attrs(self) -> tuple[str, ...]: + return tuple(attr for attr, _ in self) + + @cached_property + def names(self) -> tuple[str, ...]: + return tuple(name for _, name in self) + + +class ADetailerArgs(BaseModel, extra=Extra.forbid): + ad_model: str = "None" + ad_model_classes: str = "" + ad_tab_enable: bool = True + ad_prompt: str = "" + ad_negative_prompt: str = "" + ad_confidence: confloat(ge=0.0, le=1.0) = 0.3 + ad_mask_k_largest: NonNegativeInt = 0 + ad_mask_min_ratio: confloat(ge=0.0, le=1.0) = 0.0 + ad_mask_max_ratio: confloat(ge=0.0, le=1.0) = 1.0 + ad_dilate_erode: int = 4 + ad_x_offset: int = 0 + ad_y_offset: int = 0 + ad_mask_merge_invert: Literal["None", "Merge", "Merge and Invert"] = "None" + ad_mask_blur: NonNegativeInt = 4 + ad_denoising_strength: confloat(ge=0.0, le=1.0) = 0.4 + ad_inpaint_only_masked: bool = True + ad_inpaint_only_masked_padding: NonNegativeInt = 32 + ad_use_inpaint_width_height: bool = False + ad_inpaint_width: PositiveInt = 512 + ad_inpaint_height: PositiveInt = 512 + ad_use_steps: bool = False + ad_steps: PositiveInt = 28 + ad_use_cfg_scale: bool = False + ad_cfg_scale: NonNegativeFloat = 7.0 + ad_use_checkpoint: bool = False + ad_checkpoint: Optional[str] = None + ad_use_vae: bool = False + ad_vae: Optional[str] = None + ad_use_sampler: bool = False + ad_sampler: str = "DPM++ 2M Karras" + ad_scheduler: str = "Use same scheduler" + ad_use_noise_multiplier: bool = False + ad_noise_multiplier: confloat(ge=0.5, le=1.5) = 1.0 + ad_use_clip_skip: bool = False + ad_clip_skip: conint(ge=1, le=12) = 1 + ad_restore_face: bool = False + ad_controlnet_model: str = "None" + ad_controlnet_module: str = "None" + ad_controlnet_weight: confloat(ge=0.0, le=1.0) = 1.0 + ad_controlnet_guidance_start: confloat(ge=0.0, le=1.0) = 0.0 + ad_controlnet_guidance_end: confloat(ge=0.0, le=1.0) = 1.0 + is_api: bool = True + + @validator("is_api", pre=True) + def is_api_validator(cls, v: Any): # noqa: N805 + "tuple is json serializable but cannot be made with json deserialize." + return type(v) is not tuple + + @staticmethod + def ppop( + p: dict[str, Any], + key: str, + pops: list[str] | None = None, + cond: Any = None, + ) -> None: + if pops is None: + pops = [key] + if key not in p: + return + value = p[key] + cond = (not bool(value)) if cond is None else value == cond + + if cond: + for k in pops: + p.pop(k, None) + + def extra_params(self, suffix: str = "") -> dict[str, Any]: + if self.need_skip(): + return {} + + p = {name: getattr(self, attr) for attr, name in ALL_ARGS} + ppop = partial(self.ppop, p) + + ppop("ADetailer model classes") + ppop("ADetailer prompt") + ppop("ADetailer negative prompt") + p.pop("ADetailer tab enable", None) # always pop + ppop("ADetailer mask only top k largest", cond=0) + ppop("ADetailer mask min ratio", cond=0.0) + ppop("ADetailer mask max ratio", cond=1.0) + ppop("ADetailer x offset", cond=0) + ppop("ADetailer y offset", cond=0) + ppop("ADetailer mask merge invert", cond="None") + ppop("ADetailer inpaint only masked", ["ADetailer inpaint padding"]) + ppop( + "ADetailer use inpaint width height", + [ + "ADetailer use inpaint width height", + "ADetailer inpaint width", + "ADetailer inpaint height", + ], + ) + ppop( + "ADetailer use separate steps", + ["ADetailer use separate steps", "ADetailer steps"], + ) + ppop( + "ADetailer use separate CFG scale", + ["ADetailer use separate CFG scale", "ADetailer CFG scale"], + ) + ppop( + "ADetailer use separate checkpoint", + ["ADetailer use separate checkpoint", "ADetailer checkpoint"], + ) + ppop( + "ADetailer use separate VAE", + ["ADetailer use separate VAE", "ADetailer VAE"], + ) + ppop( + "ADetailer use separate sampler", + [ + "ADetailer use separate sampler", + "ADetailer sampler", + "ADetailer scheduler", + ], + ) + ppop("ADetailer scheduler", cond="Use same scheduler") + ppop( + "ADetailer use separate noise multiplier", + ["ADetailer use separate noise multiplier", "ADetailer noise multiplier"], + ) + + ppop( + "ADetailer use separate CLIP skip", + ["ADetailer use separate CLIP skip", "ADetailer CLIP skip"], + ) + + ppop("ADetailer restore face") + ppop( + "ADetailer ControlNet model", + [ + "ADetailer ControlNet model", + "ADetailer ControlNet module", + "ADetailer ControlNet weight", + "ADetailer ControlNet guidance start", + "ADetailer ControlNet guidance end", + ], + cond="None", + ) + ppop("ADetailer ControlNet module", cond="None") + ppop("ADetailer ControlNet weight", cond=1.0) + ppop("ADetailer ControlNet guidance start", cond=0.0) + ppop("ADetailer ControlNet guidance end", cond=1.0) + + if suffix: + p = {k + suffix: v for k, v in p.items()} + + return p + + def is_mediapipe(self) -> bool: + return self.ad_model.lower().startswith("mediapipe") + + def need_skip(self) -> bool: + return self.ad_model == "None" or self.ad_tab_enable is False + + +_all_args = [ + ("ad_model", "ADetailer model"), + ("ad_model_classes", "ADetailer model classes"), + ("ad_tab_enable", "ADetailer tab enable"), + ("ad_prompt", "ADetailer prompt"), + ("ad_negative_prompt", "ADetailer negative prompt"), + ("ad_confidence", "ADetailer confidence"), + ("ad_mask_k_largest", "ADetailer mask only top k largest"), + ("ad_mask_min_ratio", "ADetailer mask min ratio"), + ("ad_mask_max_ratio", "ADetailer mask max ratio"), + ("ad_x_offset", "ADetailer x offset"), + ("ad_y_offset", "ADetailer y offset"), + ("ad_dilate_erode", "ADetailer dilate erode"), + ("ad_mask_merge_invert", "ADetailer mask merge invert"), + ("ad_mask_blur", "ADetailer mask blur"), + ("ad_denoising_strength", "ADetailer denoising strength"), + ("ad_inpaint_only_masked", "ADetailer inpaint only masked"), + ("ad_inpaint_only_masked_padding", "ADetailer inpaint padding"), + ("ad_use_inpaint_width_height", "ADetailer use inpaint width height"), + ("ad_inpaint_width", "ADetailer inpaint width"), + ("ad_inpaint_height", "ADetailer inpaint height"), + ("ad_use_steps", "ADetailer use separate steps"), + ("ad_steps", "ADetailer steps"), + ("ad_use_cfg_scale", "ADetailer use separate CFG scale"), + ("ad_cfg_scale", "ADetailer CFG scale"), + ("ad_use_checkpoint", "ADetailer use separate checkpoint"), + ("ad_checkpoint", "ADetailer checkpoint"), + ("ad_use_vae", "ADetailer use separate VAE"), + ("ad_vae", "ADetailer VAE"), + ("ad_use_sampler", "ADetailer use separate sampler"), + ("ad_sampler", "ADetailer sampler"), + ("ad_scheduler", "ADetailer scheduler"), + ("ad_use_noise_multiplier", "ADetailer use separate noise multiplier"), + ("ad_noise_multiplier", "ADetailer noise multiplier"), + ("ad_use_clip_skip", "ADetailer use separate CLIP skip"), + ("ad_clip_skip", "ADetailer CLIP skip"), + ("ad_restore_face", "ADetailer restore face"), + ("ad_controlnet_model", "ADetailer ControlNet model"), + ("ad_controlnet_module", "ADetailer ControlNet module"), + ("ad_controlnet_weight", "ADetailer ControlNet weight"), + ("ad_controlnet_guidance_start", "ADetailer ControlNet guidance start"), + ("ad_controlnet_guidance_end", "ADetailer ControlNet guidance end"), +] + +_args = [Arg(*args) for args in _all_args] +ALL_ARGS = ArgsList(_args) + +BBOX_SORTBY = [ + "None", + "Position (left to right)", + "Position (center to edge)", + "Area (large to small)", +] +MASK_MERGE_INVERT = ["None", "Merge", "Merge and Invert"] + +_script_default = ( + "dynamic_prompting", + "dynamic_thresholding", + "wildcard_recursive", + "wildcards", + "lora_block_weight", + "negpip", +) +SCRIPT_DEFAULT = ",".join(sorted(_script_default)) + +_builtin_script = ("soft_inpainting", "hypertile_script") +BUILTIN_SCRIPT = ",".join(sorted(_builtin_script)) diff --git a/extensions/4-adetailer/adetailer/common.py b/extensions/4-adetailer/adetailer/common.py new file mode 100644 index 0000000000000000000000000000000000000000..f9e42fcc688cee5aacde3aba400542268513e662 --- /dev/null +++ b/extensions/4-adetailer/adetailer/common.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +import os +from collections import OrderedDict +from concurrent.futures import ThreadPoolExecutor +from contextlib import suppress +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Generic, Optional, TypeVar + +from huggingface_hub import hf_hub_download +from PIL import Image, ImageDraw +from rich import print +from torchvision.transforms.functional import to_pil_image + +REPO_ID = "Bingsu/adetailer" + +T = TypeVar("T", int, float) + + +@dataclass +class PredictOutput(Generic[T]): + bboxes: list[list[T]] = field(default_factory=list) + masks: list[Image.Image] = field(default_factory=list) + preview: Optional[Image.Image] = None + + +def hf_download(file: str, repo_id: str = REPO_ID, check_remote: bool = True) -> str: + if check_remote: + with suppress(Exception): + return hf_hub_download(repo_id, file, etag_timeout=1) + + with suppress(Exception): + return hf_hub_download( + repo_id, file, etag_timeout=1, endpoint="https://hf-mirror.com" + ) + + with suppress(Exception): + return hf_hub_download(repo_id, file, local_files_only=True) + + msg = f"[-] ADetailer: Failed to load model {file!r} from huggingface" + print(msg) + return "INVALID" + + +def safe_mkdir(path: str | os.PathLike[str]) -> None: + path = Path(path) + if not path.exists() and path.parent.exists() and os.access(path.parent, os.W_OK): + path.mkdir() + + +def scan_model_dir(path: Path) -> list[Path]: + if not path.is_dir(): + return [] + return [p for p in path.rglob("*") if p.is_file() and p.suffix == ".pt"] + + +def download_models(*names: str, check_remote: bool = True) -> dict[str, str]: + models = OrderedDict() + with ThreadPoolExecutor() as executor: + for name in names: + if "-world" in name: + models[name] = executor.submit( + hf_download, + name, + repo_id="Bingsu/yolo-world-mirror", + check_remote=check_remote, + ) + else: + models[name] = executor.submit( + hf_download, + name, + check_remote=check_remote, + ) + return {name: future.result() for name, future in models.items()} + + +def get_models( + *dirs: str | os.PathLike[str], huggingface: bool = True +) -> OrderedDict[str, str]: + model_paths = [] + + for dir_ in dirs: + if not dir_: + continue + model_paths.extend(scan_model_dir(Path(dir_))) + + models = OrderedDict() + to_download = [ + "face_yolov8n.pt", + "face_yolov8s.pt", + "hand_yolov8n.pt", + "person_yolov8n-seg.pt", + "person_yolov8s-seg.pt", + "yolov8x-worldv2.pt", + ] + models.update(download_models(*to_download, check_remote=huggingface)) + + models.update( + { + "mediapipe_face_full": "mediapipe_face_full", + "mediapipe_face_short": "mediapipe_face_short", + "mediapipe_face_mesh": "mediapipe_face_mesh", + "mediapipe_face_mesh_eyes_only": "mediapipe_face_mesh_eyes_only", + } + ) + + invalid_keys = [k for k, v in models.items() if v == "INVALID"] + for key in invalid_keys: + models.pop(key) + + for path in model_paths: + if path.name in models: + continue + models[path.name] = str(path) + + return models + + +def create_mask_from_bbox( + bboxes: list[list[float]], shape: tuple[int, int] +) -> list[Image.Image]: + """ + Parameters + ---------- + bboxes: list[list[float]] + list of [x1, y1, x2, y2] + bounding boxes + shape: tuple[int, int] + shape of the image (width, height) + + Returns + ------- + masks: list[Image.Image] + A list of masks + + """ + masks = [] + for bbox in bboxes: + mask = Image.new("L", shape, 0) + mask_draw = ImageDraw.Draw(mask) + mask_draw.rectangle(bbox, fill=255) + masks.append(mask) + return masks + + +def create_bbox_from_mask( + masks: list[Image.Image], shape: tuple[int, int] +) -> list[list[int]]: + """ + Parameters + ---------- + masks: list[Image.Image] + A list of masks + shape: tuple[int, int] + shape of the image (width, height) + + Returns + ------- + bboxes: list[list[float]] + A list of bounding boxes + + """ + bboxes = [] + for mask in masks: + mask = mask.resize(shape) + bbox = mask.getbbox() + if bbox is not None: + bboxes.append(list(bbox)) + return bboxes + + +def ensure_pil_image(image: Any, mode: str = "RGB") -> Image.Image: + if not isinstance(image, Image.Image): + image = to_pil_image(image) + if image.mode != mode: + image = image.convert(mode) + return image diff --git a/extensions/4-adetailer/adetailer/mask.py b/extensions/4-adetailer/adetailer/mask.py new file mode 100644 index 0000000000000000000000000000000000000000..9496aa4450df0088da512ad26fda1a471152e197 --- /dev/null +++ b/extensions/4-adetailer/adetailer/mask.py @@ -0,0 +1,269 @@ +from __future__ import annotations + +from enum import IntEnum +from functools import partial, reduce +from math import dist +from typing import Any, TypeVar + +import cv2 +import numpy as np +from PIL import Image, ImageChops + +from adetailer.args import MASK_MERGE_INVERT +from adetailer.common import PredictOutput, ensure_pil_image + + +class SortBy(IntEnum): + NONE = 0 + LEFT_TO_RIGHT = 1 + CENTER_TO_EDGE = 2 + AREA = 3 + + +class MergeInvert(IntEnum): + NONE = 0 + MERGE = 1 + MERGE_INVERT = 2 + + +T = TypeVar("T", int, float) + + +def _dilate(arr: np.ndarray, value: int) -> np.ndarray: + kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (value, value)) + return cv2.dilate(arr, kernel, iterations=1) + + +def _erode(arr: np.ndarray, value: int) -> np.ndarray: + kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (value, value)) + return cv2.erode(arr, kernel, iterations=1) + + +def dilate_erode(img: Image.Image, value: int) -> Image.Image: + """ + The dilate_erode function takes an image and a value. + If the value is positive, it dilates the image by that amount. + If the value is negative, it erodes the image by that amount. + + Parameters + ---------- + img: PIL.Image.Image + the image to be processed + value: int + kernel size of dilation or erosion + + Returns + ------- + PIL.Image.Image + The image that has been dilated or eroded + """ + if value == 0: + return img + + arr = np.array(img) + arr = _dilate(arr, value) if value > 0 else _erode(arr, -value) + + return Image.fromarray(arr) + + +def offset(img: Image.Image, x: int = 0, y: int = 0) -> Image.Image: + """ + The offset function takes an image and offsets it by a given x(→) and y(↑) value. + + Parameters + ---------- + mask: Image.Image + Pass the mask image to the function + x: int + → + y: int + ↑ + + Returns + ------- + PIL.Image.Image + A new image that is offset by x and y + """ + return ImageChops.offset(img, x, -y) + + +def is_all_black(img: Image.Image | np.ndarray) -> bool: + if isinstance(img, Image.Image): + img = np.array(ensure_pil_image(img, "L")) + return cv2.countNonZero(img) == 0 + + +def has_intersection(im1: Any, im2: Any) -> bool: + arr1 = np.array(ensure_pil_image(im1, "L")) + arr2 = np.array(ensure_pil_image(im2, "L")) + return not is_all_black(cv2.bitwise_and(arr1, arr2)) + + +def bbox_area(bbox: list[T]) -> T: + return (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]) + + +def mask_preprocess( + masks: list[Image.Image], + kernel: int = 0, + x_offset: int = 0, + y_offset: int = 0, + merge_invert: int | MergeInvert | str = MergeInvert.NONE, +) -> list[Image.Image]: + """ + The mask_preprocess function takes a list of masks and preprocesses them. + It dilates and erodes the masks, and offsets them by x_offset and y_offset. + + Parameters + ---------- + masks: list[Image.Image] + A list of masks + kernel: int + kernel size of dilation or erosion + x_offset: int + → + y_offset: int + ↑ + + Returns + ------- + list[Image.Image] + A list of processed masks + """ + if not masks: + return [] + + if x_offset != 0 or y_offset != 0: + masks = [offset(m, x_offset, y_offset) for m in masks] + + if kernel != 0: + masks = [dilate_erode(m, kernel) for m in masks] + masks = [m for m in masks if not is_all_black(m)] + + return mask_merge_invert(masks, mode=merge_invert) + + +# Bbox sorting +def _key_left_to_right(bbox: list[T]) -> T: + """ + Left to right + + Parameters + ---------- + bbox: list[int] | list[float] + list of [x1, y1, x2, y2] + """ + return bbox[0] + + +def _key_center_to_edge(bbox: list[T], *, center: tuple[float, float]) -> float: + """ + Center to edge + + Parameters + ---------- + bbox: list[int] | list[float] + list of [x1, y1, x2, y2] + image: Image.Image + the image + """ + bbox_center = ((bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2) + return dist(center, bbox_center) + + +def _key_area(bbox: list[T]) -> T: + """ + Large to small + + Parameters + ---------- + bbox: list[int] | list[float] + list of [x1, y1, x2, y2] + """ + return -bbox_area(bbox) + + +def sort_bboxes( + pred: PredictOutput[T], order: int | SortBy = SortBy.NONE +) -> PredictOutput[T]: + if order == SortBy.NONE or len(pred.bboxes) <= 1: + return pred + + if order == SortBy.LEFT_TO_RIGHT: + key = _key_left_to_right + elif order == SortBy.CENTER_TO_EDGE: + width, height = pred.preview.size + center = (width / 2, height / 2) + key = partial(_key_center_to_edge, center=center) + elif order == SortBy.AREA: + key = _key_area + else: + raise RuntimeError + + items = len(pred.bboxes) + idx = sorted(range(items), key=lambda i: key(pred.bboxes[i])) + pred.bboxes = [pred.bboxes[i] for i in idx] + pred.masks = [pred.masks[i] for i in idx] + return pred + + +# Filter by ratio +def is_in_ratio(bbox: list[T], low: float, high: float, orig_area: int) -> bool: + area = bbox_area(bbox) + return low <= area / orig_area <= high + + +def filter_by_ratio( + pred: PredictOutput[T], low: float, high: float +) -> PredictOutput[T]: + if not pred.bboxes: + return pred + + w, h = pred.preview.size + orig_area = w * h + items = len(pred.bboxes) + idx = [i for i in range(items) if is_in_ratio(pred.bboxes[i], low, high, orig_area)] + pred.bboxes = [pred.bboxes[i] for i in idx] + pred.masks = [pred.masks[i] for i in idx] + return pred + + +def filter_k_largest(pred: PredictOutput[T], k: int = 0) -> PredictOutput[T]: + if not pred.bboxes or k == 0: + return pred + areas = [bbox_area(bbox) for bbox in pred.bboxes] + idx = np.argsort(areas)[-k:] + idx = idx[::-1] + pred.bboxes = [pred.bboxes[i] for i in idx] + pred.masks = [pred.masks[i] for i in idx] + return pred + + +# Merge / Invert +def mask_merge(masks: list[Image.Image]) -> list[Image.Image]: + arrs = [np.array(m) for m in masks] + arr = reduce(cv2.bitwise_or, arrs) + return [Image.fromarray(arr)] + + +def mask_invert(masks: list[Image.Image]) -> list[Image.Image]: + return [ImageChops.invert(m) for m in masks] + + +def mask_merge_invert( + masks: list[Image.Image], mode: int | MergeInvert | str +) -> list[Image.Image]: + if isinstance(mode, str): + mode = MASK_MERGE_INVERT.index(mode) + + if mode == MergeInvert.NONE or not masks: + return masks + + if mode == MergeInvert.MERGE: + return mask_merge(masks) + + if mode == MergeInvert.MERGE_INVERT: + merged = mask_merge(masks) + return mask_invert(merged) + + raise RuntimeError diff --git a/extensions/4-adetailer/adetailer/mediapipe.py b/extensions/4-adetailer/adetailer/mediapipe.py new file mode 100644 index 0000000000000000000000000000000000000000..b05fa0080d9d53367bf8ab0f8e0787953ef33b8b --- /dev/null +++ b/extensions/4-adetailer/adetailer/mediapipe.py @@ -0,0 +1,177 @@ +from __future__ import annotations + +from functools import partial + +import cv2 +import numpy as np +from PIL import Image, ImageDraw + +from adetailer import PredictOutput +from adetailer.common import create_bbox_from_mask, create_mask_from_bbox + + +def mediapipe_predict( + model_type: str, image: Image.Image, confidence: float = 0.3 +) -> PredictOutput: + mapping = { + "mediapipe_face_short": partial(mediapipe_face_detection, 0), + "mediapipe_face_full": partial(mediapipe_face_detection, 1), + "mediapipe_face_mesh": mediapipe_face_mesh, + "mediapipe_face_mesh_eyes_only": mediapipe_face_mesh_eyes_only, + } + if model_type in mapping: + func = mapping[model_type] + try: + return func(image, confidence) + except Exception: + return PredictOutput() + msg = f"[-] ADetailer: Invalid mediapipe model type: {model_type}, Available: {list(mapping.keys())!r}" + raise RuntimeError(msg) + + +def mediapipe_face_detection( + model_type: int, image: Image.Image, confidence: float = 0.3 +) -> PredictOutput[float]: + import mediapipe as mp + + img_width, img_height = image.size + + mp_face_detection = mp.solutions.face_detection + draw_util = mp.solutions.drawing_utils + + img_array = np.array(image) + + with mp_face_detection.FaceDetection( + model_selection=model_type, min_detection_confidence=confidence + ) as face_detector: + pred = face_detector.process(img_array) + + if pred.detections is None: + return PredictOutput() + + preview_array = img_array.copy() + + bboxes = [] + for detection in pred.detections: + draw_util.draw_detection(preview_array, detection) + + bbox = detection.location_data.relative_bounding_box + x1 = bbox.xmin * img_width + y1 = bbox.ymin * img_height + w = bbox.width * img_width + h = bbox.height * img_height + x2 = x1 + w + y2 = y1 + h + + bboxes.append([x1, y1, x2, y2]) + + masks = create_mask_from_bbox(bboxes, image.size) + preview = Image.fromarray(preview_array) + + return PredictOutput(bboxes=bboxes, masks=masks, preview=preview) + + +def mediapipe_face_mesh( + image: Image.Image, confidence: float = 0.3 +) -> PredictOutput[int]: + import mediapipe as mp + + mp_face_mesh = mp.solutions.face_mesh + draw_util = mp.solutions.drawing_utils + drawing_styles = mp.solutions.drawing_styles + + w, h = image.size + + with mp_face_mesh.FaceMesh( + static_image_mode=True, max_num_faces=20, min_detection_confidence=confidence + ) as face_mesh: + arr = np.array(image) + pred = face_mesh.process(arr) + + if pred.multi_face_landmarks is None: + return PredictOutput() + + preview = arr.copy() + masks = [] + + for landmarks in pred.multi_face_landmarks: + draw_util.draw_landmarks( + image=preview, + landmark_list=landmarks, + connections=mp_face_mesh.FACEMESH_TESSELATION, + landmark_drawing_spec=None, + connection_drawing_spec=drawing_styles.get_default_face_mesh_tesselation_style(), + ) + + points = np.array( + [[land.x * w, land.y * h] for land in landmarks.landmark], dtype=int + ) + outline = cv2.convexHull(points).reshape(-1).tolist() + + mask = Image.new("L", image.size, "black") + draw = ImageDraw.Draw(mask) + draw.polygon(outline, fill="white") + masks.append(mask) + + bboxes = create_bbox_from_mask(masks, image.size) + preview = Image.fromarray(preview) + return PredictOutput(bboxes=bboxes, masks=masks, preview=preview) + + +def mediapipe_face_mesh_eyes_only( + image: Image.Image, confidence: float = 0.3 +) -> PredictOutput[int]: + import mediapipe as mp + + mp_face_mesh = mp.solutions.face_mesh + + left_idx = np.array(list(mp_face_mesh.FACEMESH_LEFT_EYE)).flatten() + right_idx = np.array(list(mp_face_mesh.FACEMESH_RIGHT_EYE)).flatten() + + w, h = image.size + + with mp_face_mesh.FaceMesh( + static_image_mode=True, max_num_faces=20, min_detection_confidence=confidence + ) as face_mesh: + arr = np.array(image) + pred = face_mesh.process(arr) + + if pred.multi_face_landmarks is None: + return PredictOutput() + + preview = image.copy() + masks = [] + + for landmarks in pred.multi_face_landmarks: + points = np.array( + [[land.x * w, land.y * h] for land in landmarks.landmark], dtype=int + ) + left_eyes = points[left_idx] + right_eyes = points[right_idx] + left_outline = cv2.convexHull(left_eyes).reshape(-1).tolist() + right_outline = cv2.convexHull(right_eyes).reshape(-1).tolist() + + mask = Image.new("L", image.size, "black") + draw = ImageDraw.Draw(mask) + for outline in (left_outline, right_outline): + draw.polygon(outline, fill="white") + masks.append(mask) + + bboxes = create_bbox_from_mask(masks, image.size) + preview = draw_preview(preview, bboxes, masks) + return PredictOutput(bboxes=bboxes, masks=masks, preview=preview) + + +def draw_preview( + preview: Image.Image, bboxes: list[list[int]], masks: list[Image.Image] +) -> Image.Image: + red = Image.new("RGB", preview.size, "red") + for mask in masks: + masked = Image.composite(red, preview, mask) + preview = Image.blend(preview, masked, 0.25) + + draw = ImageDraw.Draw(preview) + for bbox in bboxes: + draw.rectangle(bbox, outline="red", width=2) + + return preview diff --git a/extensions/4-adetailer/adetailer/ultralytics.py b/extensions/4-adetailer/adetailer/ultralytics.py new file mode 100644 index 0000000000000000000000000000000000000000..dc93482447b90ac1b0e8027c02cacc357c6cb52d --- /dev/null +++ b/extensions/4-adetailer/adetailer/ultralytics.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +import cv2 +from PIL import Image +from torchvision.transforms.functional import to_pil_image + +from adetailer import PredictOutput +from adetailer.common import create_mask_from_bbox + +if TYPE_CHECKING: + import torch + from ultralytics import YOLO, YOLOWorld + + +def ultralytics_predict( + model_path: str | Path, + image: Image.Image, + confidence: float = 0.3, + device: str = "", + classes: str = "", +) -> PredictOutput[float]: + from ultralytics import YOLO + + model = YOLO(model_path) + apply_classes(model, model_path, classes) + pred = model(image, conf=confidence, device=device) + + bboxes = pred[0].boxes.xyxy.cpu().numpy() + if bboxes.size == 0: + return PredictOutput() + bboxes = bboxes.tolist() + + if pred[0].masks is None: + masks = create_mask_from_bbox(bboxes, image.size) + else: + masks = mask_to_pil(pred[0].masks.data, image.size) + preview = pred[0].plot() + preview = cv2.cvtColor(preview, cv2.COLOR_BGR2RGB) + preview = Image.fromarray(preview) + + return PredictOutput(bboxes=bboxes, masks=masks, preview=preview) + + +def apply_classes(model: YOLO | YOLOWorld, model_path: str | Path, classes: str): + if not classes or "-world" not in Path(model_path).stem: + return + parsed = [c.strip() for c in classes.split(",") if c.strip()] + if parsed: + model.set_classes(parsed) + + +def mask_to_pil(masks: torch.Tensor, shape: tuple[int, int]) -> list[Image.Image]: + """ + Parameters + ---------- + masks: torch.Tensor, dtype=torch.float32, shape=(N, H, W). + The device can be CUDA, but `to_pil_image` takes care of that. + + shape: tuple[int, int] + (W, H) of the original image + """ + n = masks.shape[0] + return [to_pil_image(masks[i], mode="L").resize(shape) for i in range(n)] diff --git a/extensions/4-adetailer/controlnet_ext/__init__.py b/extensions/4-adetailer/controlnet_ext/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6da28d4e1dd2bd6858a55da111c6aa96de69496a --- /dev/null +++ b/extensions/4-adetailer/controlnet_ext/__init__.py @@ -0,0 +1,25 @@ +try: + from .controlnet_ext_forge import ( + ControlNetExt, + controlnet_exists, + controlnet_type, + get_cn_models, + ) +except ImportError: + from .controlnet_ext import ( + ControlNetExt, + controlnet_exists, + controlnet_type, + get_cn_models, + ) + +from .restore import CNHijackRestore, cn_allow_script_control + +__all__ = [ + "ControlNetExt", + "CNHijackRestore", + "cn_allow_script_control", + "controlnet_exists", + "controlnet_type", + "get_cn_models", +] diff --git a/extensions/4-adetailer/controlnet_ext/__pycache__/__init__.cpython-310.pyc b/extensions/4-adetailer/controlnet_ext/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..324aa608d8cb29e568d505af2a653b068b8265ff Binary files /dev/null and b/extensions/4-adetailer/controlnet_ext/__pycache__/__init__.cpython-310.pyc differ diff --git a/extensions/4-adetailer/controlnet_ext/__pycache__/common.cpython-310.pyc b/extensions/4-adetailer/controlnet_ext/__pycache__/common.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..314e18a15791bb7c283268f1107241f5b8c37076 Binary files /dev/null and b/extensions/4-adetailer/controlnet_ext/__pycache__/common.cpython-310.pyc differ diff --git a/extensions/4-adetailer/controlnet_ext/__pycache__/controlnet_ext.cpython-310.pyc b/extensions/4-adetailer/controlnet_ext/__pycache__/controlnet_ext.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..201e2857dbd5fb879097cfdbccfd211763df6ab1 Binary files /dev/null and b/extensions/4-adetailer/controlnet_ext/__pycache__/controlnet_ext.cpython-310.pyc differ diff --git a/extensions/4-adetailer/controlnet_ext/__pycache__/controlnet_ext_forge.cpython-310.pyc b/extensions/4-adetailer/controlnet_ext/__pycache__/controlnet_ext_forge.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6020325471bbd0b87cb45fb0742e2b073f69de22 Binary files /dev/null and b/extensions/4-adetailer/controlnet_ext/__pycache__/controlnet_ext_forge.cpython-310.pyc differ diff --git a/extensions/4-adetailer/controlnet_ext/__pycache__/restore.cpython-310.pyc b/extensions/4-adetailer/controlnet_ext/__pycache__/restore.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c801397c7cdb49e80c8f71ed7a84d578b6b7cf1 Binary files /dev/null and b/extensions/4-adetailer/controlnet_ext/__pycache__/restore.cpython-310.pyc differ diff --git a/extensions/4-adetailer/controlnet_ext/common.py b/extensions/4-adetailer/controlnet_ext/common.py new file mode 100644 index 0000000000000000000000000000000000000000..f485da54d79c4252801dc788b32b16f706b66b23 --- /dev/null +++ b/extensions/4-adetailer/controlnet_ext/common.py @@ -0,0 +1,12 @@ +import re + +cn_model_module = { + "inpaint": "inpaint_global_harmonious", + "scribble": "t2ia_sketch_pidi", + "lineart": "lineart_coarse", + "openpose": "openpose_full", + "tile": "tile_resample", + "depth": "depth_midas", +} +_names = [*cn_model_module, "union"] +cn_model_regex = re.compile("|".join(_names), flags=re.IGNORECASE) diff --git a/extensions/4-adetailer/controlnet_ext/controlnet_ext.py b/extensions/4-adetailer/controlnet_ext/controlnet_ext.py new file mode 100644 index 0000000000000000000000000000000000000000..bcf7130c479ab009f2c5a4de8c81bec2ae9c55e6 --- /dev/null +++ b/extensions/4-adetailer/controlnet_ext/controlnet_ext.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +import importlib +import sys +from functools import lru_cache +from pathlib import Path + +from modules import extensions, sd_models, shared +from modules.paths import extensions_builtin_dir, extensions_dir, models_path + +from .common import cn_model_module, cn_model_regex + +ext_path = Path(extensions_dir) +ext_builtin_path = Path(extensions_builtin_dir) +controlnet_exists = False +controlnet_type = "standard" +controlnet_path = None +cn_base_path = "" + +for extension in extensions.active(): + if not extension.enabled: + continue + # For cases like sd-webui-controlnet-master + if "sd-webui-controlnet" in extension.name: + controlnet_exists = True + controlnet_path = Path(extension.path) + cn_base_path = ".".join(controlnet_path.parts[-2:]) + break + +if controlnet_path is not None: + sd_webui_controlnet_path = controlnet_path.resolve().parent + if sd_webui_controlnet_path.stem in ("extensions", "extensions-builtin"): + target_path = str(sd_webui_controlnet_path.parent) + if target_path not in sys.path: + sys.path.append(target_path) + + +class ControlNetExt: + def __init__(self): + self.cn_models = ["None"] + self.cn_available = False + self.external_cn = None + + def init_controlnet(self): + import_path = cn_base_path + ".scripts.external_code" + + self.external_cn = importlib.import_module(import_path, "external_code") + self.cn_available = True + models = self.external_cn.get_models() + self.cn_models.extend(m for m in models if cn_model_regex.search(m)) + + def update_scripts_args( + self, + p, + model: str, + module: str | None, + weight: float, + guidance_start: float, + guidance_end: float, + ): + if (not self.cn_available) or model == "None": + return + + if module == "None": + module = None + if module is None: + for m, v in cn_model_module.items(): + if m in model: + module = v + break + + cn_units = [ + self.external_cn.ControlNetUnit( + model=model, + weight=weight, + control_mode=self.external_cn.ControlMode.BALANCED, + module=module, + guidance_start=guidance_start, + guidance_end=guidance_end, + pixel_perfect=True, + ) + ] + + try: + self.external_cn.update_cn_script_in_processing(p, cn_units) + except AttributeError as e: + if "script_args_value" not in str(e): + raise + msg = "[-] Adetailer: ControlNet option not available in WEBUI version lower than 1.6.0 due to updates in ControlNet" + raise RuntimeError(msg) from e + + +def get_cn_model_dirs() -> list[Path]: + cn_model_dir = Path(models_path, "ControlNet") + if controlnet_path is not None: + cn_model_dir_old = controlnet_path.joinpath("models") + else: + cn_model_dir_old = None + ext_dir1 = shared.opts.data.get("control_net_models_path", "") + ext_dir2 = getattr(shared.cmd_opts, "controlnet_dir", "") + + dirs = [cn_model_dir] + dirs += [ + Path(ext_dir) for ext_dir in [cn_model_dir_old, ext_dir1, ext_dir2] if ext_dir + ] + + return dirs + + +@lru_cache +def _get_cn_models() -> list[str]: + """ + Since we can't import ControlNet, we use a function that does something like + controlnet's `list(global_state.cn_models_names.values())`. + """ + cn_model_exts = (".pt", ".pth", ".ckpt", ".safetensors") + dirs = get_cn_model_dirs() + name_filter = shared.opts.data.get("control_net_models_name_filter", "") + name_filter = name_filter.strip(" ").lower() + + model_paths = [] + + for base in dirs: + if not base.exists(): + continue + + for p in base.rglob("*"): + if ( + p.is_file() + and p.suffix in cn_model_exts + and cn_model_regex.search(p.name) + ): + if name_filter and name_filter not in p.name.lower(): + continue + model_paths.append(p) + model_paths.sort(key=lambda p: p.name) + + models = [] + for p in model_paths: + model_hash = sd_models.model_hash(p) + name = f"{p.stem} [{model_hash}]" + models.append(name) + return models + + +def get_cn_models() -> list[str]: + if controlnet_exists: + return _get_cn_models() + return [] diff --git a/extensions/4-adetailer/controlnet_ext/controlnet_ext_forge.py b/extensions/4-adetailer/controlnet_ext/controlnet_ext_forge.py new file mode 100644 index 0000000000000000000000000000000000000000..ef59e32e5d0e4a2b7b7ae32919275b9e60465cf4 --- /dev/null +++ b/extensions/4-adetailer/controlnet_ext/controlnet_ext_forge.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +import copy + +import numpy as np +from lib_controlnet import external_code, global_state +from lib_controlnet.external_code import ControlNetUnit + +from modules import scripts +from modules.processing import StableDiffusionProcessing + +from .common import cn_model_regex + +controlnet_exists = True +controlnet_type = "forge" + + +def find_script(p: StableDiffusionProcessing, script_title: str) -> scripts.Script: + script = next((s for s in p.scripts.scripts if s.title() == script_title), None) + if not script: + msg = f"Script not found: {script_title!r}" + raise RuntimeError(msg) + return script + + +def add_forge_script_to_adetailer_run( + p: StableDiffusionProcessing, script_title: str, script_args: list +): + p.scripts = copy.copy(scripts.scripts_img2img) + p.scripts.alwayson_scripts = [] + p.script_args_value = [] + + script = copy.copy(find_script(p, script_title)) + script.args_from = len(p.script_args_value) + script.args_to = len(p.script_args_value) + len(script_args) + p.scripts.alwayson_scripts.append(script) + p.script_args_value.extend(script_args) + + +class ControlNetExt: + def __init__(self): + self.cn_available = False + self.external_cn = external_code + + def init_controlnet(self): + self.cn_available = True + + def update_scripts_args( + self, + p, + model: str, + module: str | None, + weight: float, + guidance_start: float, + guidance_end: float, + ): + if (not self.cn_available) or model == "None": + return + + image = np.asarray(p.init_images[0]) + mask = np.full_like(image, fill_value=255) + + cnet_image = {"image": image, "mask": mask} + + pres = external_code.pixel_perfect_resolution( + image, + target_H=p.height, + target_W=p.width, + resize_mode=external_code.resize_mode_from_value(p.resize_mode), + ) + + add_forge_script_to_adetailer_run( + p, + "ControlNet", + [ + ControlNetUnit( + enabled=True, + image=cnet_image, + model=model, + module=module, + weight=weight, + guidance_start=guidance_start, + guidance_end=guidance_end, + processor_res=pres, + ) + ], + ) + + +def get_cn_models() -> list[str]: + models = global_state.get_all_controlnet_names() + return [m for m in models if cn_model_regex.search(m)] diff --git a/extensions/4-adetailer/controlnet_ext/restore.py b/extensions/4-adetailer/controlnet_ext/restore.py new file mode 100644 index 0000000000000000000000000000000000000000..152ffd0a9732b5512e4440b795a1ad89bcb27cab --- /dev/null +++ b/extensions/4-adetailer/controlnet_ext/restore.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from contextlib import contextmanager + +from modules import img2img, processing, shared + + +class CNHijackRestore: + def __init__(self): + self.process = hasattr(processing, "__controlnet_original_process_images_inner") + self.img2img = hasattr(img2img, "__controlnet_original_process_batch") + + def __enter__(self): + if self.process: + self.orig_process = processing.process_images_inner + processing.process_images_inner = getattr( + processing, "__controlnet_original_process_images_inner" + ) + if self.img2img: + self.orig_img2img = img2img.process_batch + img2img.process_batch = getattr( + img2img, "__controlnet_original_process_batch" + ) + + def __exit__(self, *args, **kwargs): + if self.process: + processing.process_images_inner = self.orig_process + if self.img2img: + img2img.process_batch = self.orig_img2img + + +@contextmanager +def cn_allow_script_control(): + orig = False + if "control_net_allow_script_control" in shared.opts.data: + try: + orig = shared.opts.data["control_net_allow_script_control"] + shared.opts.data["control_net_allow_script_control"] = True + yield + finally: + shared.opts.data["control_net_allow_script_control"] = orig + else: + yield diff --git a/extensions/4-adetailer/install.py b/extensions/4-adetailer/install.py new file mode 100644 index 0000000000000000000000000000000000000000..6afd916654d39b4500b6eb0a7543b00510f55916 --- /dev/null +++ b/extensions/4-adetailer/install.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import importlib.util +import subprocess +import sys +from importlib.metadata import version # python >= 3.8 + +from packaging.version import parse + +import_name = {"py-cpuinfo": "cpuinfo", "protobuf": "google.protobuf"} + + +def is_installed( + package: str, min_version: str | None = None, max_version: str | None = None +): + name = import_name.get(package, package) + try: + spec = importlib.util.find_spec(name) + except ModuleNotFoundError: + return False + + if spec is None: + return False + + if not min_version and not max_version: + return True + + if not min_version: + min_version = "0.0.0" + if not max_version: + max_version = "99999999.99999999.99999999" + + try: + pkg_version = version(package) + return parse(min_version) <= parse(pkg_version) <= parse(max_version) + except Exception: + return False + + +def run_pip(*args): + subprocess.run([sys.executable, "-m", "pip", "install", *args], check=True) + + +def install(): + deps = [ + # requirements + ("ultralytics", "8.2.0", None), + ("mediapipe", "0.10.13", None), + ("rich", "13.0.0", None), + ] + + pkgs = [] + for pkg, low, high in deps: + if not is_installed(pkg, low, high): + if low and high: + cmd = f"{pkg}>={low},<={high}" + elif low: + cmd = f"{pkg}>={low}" + elif high: + cmd = f"{pkg}<={high}" + else: + cmd = pkg + pkgs.append(cmd) + + if pkgs: + run_pip(*pkgs) + + +try: + import launch + + skip_install = launch.args.skip_install +except Exception: + skip_install = False + +if not skip_install: + install() diff --git a/extensions/4-adetailer/preload.py b/extensions/4-adetailer/preload.py new file mode 100644 index 0000000000000000000000000000000000000000..10be161f22b0a5ef7083609829a21b547eae9aea --- /dev/null +++ b/extensions/4-adetailer/preload.py @@ -0,0 +1,9 @@ +import argparse + + +def preload(parser: argparse.ArgumentParser): + parser.add_argument( + "--ad-no-huggingface", + action="store_true", + help="Don't use adetailer models from huggingface", + ) diff --git a/extensions/4-adetailer/pyproject.toml b/extensions/4-adetailer/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..87f609f943c688d857a761f24dd103b37b5f78ac --- /dev/null +++ b/extensions/4-adetailer/pyproject.toml @@ -0,0 +1,77 @@ +[project] +name = "adetailer" +description = "An object detection and auto-mask extension for stable diffusion webui." +authors = [{ name = "dowon", email = "ks2515@naver.com" }] +requires-python = ">=3.9" +readme = "README.md" +license = { text = "AGPL-3.0" } +dependencies = [ + "ultralytics>=8.2", + "mediapipe>=0.10.13", + "pydantic<3", + "rich>=13", + "huggingface_hub", +] +keywords = [ + "stable-diffusion", + "stable-diffusion-webui", + "adetailer", + "ultralytics", +] +classifiers = [ + "License :: OSI Approved :: GNU Affero General Public License v3", + "Topic :: Scientific/Engineering :: Image Recognition", +] +dynamic = ["version"] + +[project.urls] +repository = "https://github.com/Bing-su/adetailer" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.version] +path = "adetailer/__version__.py" + +[tool.isort] +profile = "black" +known_first_party = ["launch", "modules"] + +[tool.ruff] +target-version = "py39" + +[tool.ruff.lint] +select = [ + "A", + "B", + "C4", + "C90", + "E", + "EM", + "F", + "FA", + "I001", + "ISC", + "N", + "PD", + "PERF", + "PIE", + "PT", + "PTH", + "RET", + "RUF", + "SIM", + "T20", + "TRY", + "UP", + "W", +] +ignore = ["B905", "E501"] +unfixable = ["F401"] + +[tool.ruff.lint.isort] +known-first-party = ["launch", "modules"] + +[tool.ruff.lint.pyupgrade] +keep-runtime-typing = true diff --git a/extensions/4-adetailer/scripts/!adetailer.py b/extensions/4-adetailer/scripts/!adetailer.py new file mode 100644 index 0000000000000000000000000000000000000000..b655e6e57741ba6ef03779135793004fee6503c1 --- /dev/null +++ b/extensions/4-adetailer/scripts/!adetailer.py @@ -0,0 +1,1055 @@ +from __future__ import annotations + +import platform +import re +import sys +import traceback +from copy import copy +from functools import partial +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Any, NamedTuple, cast + +import gradio as gr +from PIL import Image, ImageChops +from rich import print + +import modules +from aaaaaa.conditional import create_binary_mask, schedulers +from aaaaaa.helper import ( + change_torch_load, + copy_extra_params, + pause_total_tqdm, + preserve_prompts, +) +from aaaaaa.p_method import ( + get_i, + is_img2img_inpaint, + is_inpaint_only_masked, + is_skip_img2img, + need_call_postprocess, + need_call_process, +) +from aaaaaa.traceback import rich_traceback +from aaaaaa.ui import WebuiInfo, adui, ordinal, suffix +from adetailer import ( + ADETAILER, + __version__, + get_models, + mediapipe_predict, + ultralytics_predict, +) +from adetailer.args import ( + BBOX_SORTBY, + BUILTIN_SCRIPT, + SCRIPT_DEFAULT, + ADetailerArgs, + SkipImg2ImgOrig, +) +from adetailer.common import PredictOutput, ensure_pil_image, safe_mkdir +from adetailer.mask import ( + filter_by_ratio, + filter_k_largest, + has_intersection, + is_all_black, + mask_preprocess, + sort_bboxes, +) +from controlnet_ext import ( + CNHijackRestore, + ControlNetExt, + cn_allow_script_control, + controlnet_exists, + controlnet_type, + get_cn_models, +) +from modules import images, paths, script_callbacks, scripts, shared +from modules.devices import NansException +from modules.processing import ( + Processed, + StableDiffusionProcessingImg2Img, + create_infotext, + process_images, +) +from modules.sd_samplers import all_samplers +from modules.shared import cmd_opts, opts, state + +if TYPE_CHECKING: + from fastapi import FastAPI + +PARAMS_TXT = "params.txt" + +no_huggingface = getattr(cmd_opts, "ad_no_huggingface", False) +adetailer_dir = Path(paths.models_path, "adetailer") +safe_mkdir(adetailer_dir) + +extra_models_dirs = shared.opts.data.get("ad_extra_models_dir", "") +model_mapping = get_models( + adetailer_dir, + *extra_models_dirs.split("|"), + huggingface=not no_huggingface, +) + +txt2img_submit_button = img2img_submit_button = None +txt2img_submit_button = cast(gr.Button, txt2img_submit_button) +img2img_submit_button = cast(gr.Button, img2img_submit_button) + +print( + f"[-] ADetailer initialized. version: {__version__}, num models: {len(model_mapping)}" +) + + +class AfterDetailerScript(scripts.Script): + def __init__(self): + super().__init__() + self.ultralytics_device = self.get_ultralytics_device() + + self.controlnet_ext = None + + def __repr__(self): + return f"{self.__class__.__name__}(version={__version__})" + + def title(self): + return ADETAILER + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + num_models = opts.data.get("ad_max_models", 2) + ad_model_list = list(model_mapping.keys()) + sampler_names = [sampler.name for sampler in all_samplers] + scheduler_names = [x.label for x in schedulers] + + checkpoint_list = modules.sd_models.checkpoint_tiles(use_short=True) + vae_list = modules.shared_items.sd_vae_items() + + webui_info = WebuiInfo( + ad_model_list=ad_model_list, + sampler_names=sampler_names, + scheduler_names=scheduler_names, + t2i_button=txt2img_submit_button, + i2i_button=img2img_submit_button, + checkpoints_list=checkpoint_list, + vae_list=vae_list, + ) + + components, infotext_fields = adui(num_models, is_img2img, webui_info) + + self.infotext_fields = infotext_fields + return components + + def init_controlnet_ext(self) -> None: + if self.controlnet_ext is not None: + return + self.controlnet_ext = ControlNetExt() + + if controlnet_exists: + try: + self.controlnet_ext.init_controlnet() + except ImportError: + error = traceback.format_exc() + print( + f"[-] ADetailer: ControlNetExt init failed:\n{error}", + file=sys.stderr, + ) + + def update_controlnet_args(self, p, args: ADetailerArgs) -> None: + if self.controlnet_ext is None: + self.init_controlnet_ext() + + if ( + self.controlnet_ext is not None + and self.controlnet_ext.cn_available + and args.ad_controlnet_model != "None" + ): + self.controlnet_ext.update_scripts_args( + p, + model=args.ad_controlnet_model, + module=args.ad_controlnet_module, + weight=args.ad_controlnet_weight, + guidance_start=args.ad_controlnet_guidance_start, + guidance_end=args.ad_controlnet_guidance_end, + ) + + def is_ad_enabled(self, *args_) -> bool: + arg_list = [arg for arg in args_ if isinstance(arg, dict)] + if not args_ or not arg_list: + message = f""" + [-] ADetailer: Invalid arguments passed to ADetailer. + input: {args_!r} + ADetailer disabled. + """ + print(dedent(message), file=sys.stderr) + return False + + ad_enabled = args_[0] if isinstance(args_[0], bool) else True + pydantic_args = [] + for arg in arg_list: + try: + pydantic_args.append(ADetailerArgs(**arg)) + except ValueError: # noqa: PERF203 + continue + not_none = not all(arg.need_skip() for arg in pydantic_args) + return ad_enabled and not_none + + def set_skip_img2img(self, p, *args_) -> None: + if ( + hasattr(p, "_ad_skip_img2img") + or not hasattr(p, "init_images") + or not p.init_images + ): + return + + if len(args_) >= 2 and isinstance(args_[1], bool): + p._ad_skip_img2img = args_[1] + else: + p._ad_skip_img2img = False + + if not p._ad_skip_img2img: + return + + if is_img2img_inpaint(p): + p._ad_disabled = True + msg = "[-] ADetailer: img2img inpainting with skip img2img is not supported. (because it's buggy)" + print(msg) + return + + p._ad_orig = SkipImg2ImgOrig( + steps=p.steps, + sampler_name=p.sampler_name, + width=p.width, + height=p.height, + ) + p.steps = 1 + p.sampler_name = "Euler" + p.width = 128 + p.height = 128 + + def get_args(self, p, *args_) -> list[ADetailerArgs]: + """ + `args_` is at least 1 in length by `is_ad_enabled` immediately above + """ + args = [arg for arg in args_ if isinstance(arg, dict)] + + if not args: + message = f"[-] ADetailer: Invalid arguments passed to ADetailer: {args_!r}" + raise ValueError(message) + + if hasattr(p, "_ad_xyz"): + args[0] = {**args[0], **p._ad_xyz} + + all_inputs = [] + + for n, arg_dict in enumerate(args, 1): + try: + inp = ADetailerArgs(**arg_dict) + except ValueError as e: + msg = f"[-] ADetailer: ValidationError when validating {ordinal(n)} arguments" + if hasattr(e, "add_note"): + e.add_note(msg) + else: + print(msg, file=sys.stderr) + raise + + all_inputs.append(inp) + + return all_inputs + + def extra_params(self, arg_list: list[ADetailerArgs]) -> dict: + params = {} + for n, args in enumerate(arg_list): + params.update(args.extra_params(suffix=suffix(n))) + params["ADetailer version"] = __version__ + return params + + @staticmethod + def get_ultralytics_device() -> str: + if "adetailer" in shared.cmd_opts.use_cpu: + return "cpu" + + if platform.system() == "Darwin": + return "" + + vram_args = ["lowvram", "medvram", "medvram_sdxl"] + if any(getattr(cmd_opts, vram, False) for vram in vram_args): + return "cpu" + + return "" + + def prompt_blank_replacement( + self, all_prompts: list[str], i: int, default: str + ) -> str: + if not all_prompts: + return default + if i < len(all_prompts): + return all_prompts[i] + j = i % len(all_prompts) + return all_prompts[j] + + def _get_prompt( + self, + ad_prompt: str, + all_prompts: list[str], + i: int, + default: str, + replacements: list[PromptSR], + ) -> list[str]: + prompts = re.split(r"\s*\[SEP\]\s*", ad_prompt) + blank_replacement = self.prompt_blank_replacement(all_prompts, i, default) + for n in range(len(prompts)): + if not prompts[n]: + prompts[n] = blank_replacement + elif "[PROMPT]" in prompts[n]: + prompts[n] = prompts[n].replace("[PROMPT]", blank_replacement) + + for pair in replacements: + prompts[n] = prompts[n].replace(pair.s, pair.r) + return prompts + + def get_prompt(self, p, args: ADetailerArgs) -> tuple[list[str], list[str]]: + i = get_i(p) + prompt_sr = p._ad_xyz_prompt_sr if hasattr(p, "_ad_xyz_prompt_sr") else [] + + prompt = self._get_prompt( + ad_prompt=args.ad_prompt, + all_prompts=p.all_prompts, + i=i, + default=p.prompt, + replacements=prompt_sr, + ) + negative_prompt = self._get_prompt( + ad_prompt=args.ad_negative_prompt, + all_prompts=p.all_negative_prompts, + i=i, + default=p.negative_prompt, + replacements=prompt_sr, + ) + + return prompt, negative_prompt + + def get_seed(self, p) -> tuple[int, int]: + i = get_i(p) + + if not p.all_seeds: + seed = p.seed + elif i < len(p.all_seeds): + seed = p.all_seeds[i] + else: + j = i % len(p.all_seeds) + seed = p.all_seeds[j] + + if not p.all_subseeds: + subseed = p.subseed + elif i < len(p.all_subseeds): + subseed = p.all_subseeds[i] + else: + j = i % len(p.all_subseeds) + subseed = p.all_subseeds[j] + + return seed, subseed + + def get_width_height(self, p, args: ADetailerArgs) -> tuple[int, int]: + if args.ad_use_inpaint_width_height: + width = args.ad_inpaint_width + height = args.ad_inpaint_height + elif hasattr(p, "_ad_orig"): + width = p._ad_orig.width + height = p._ad_orig.height + else: + width = p.width + height = p.height + + return width, height + + def get_steps(self, p, args: ADetailerArgs) -> int: + if args.ad_use_steps: + return args.ad_steps + if hasattr(p, "_ad_orig"): + return p._ad_orig.steps + return p.steps + + def get_cfg_scale(self, p, args: ADetailerArgs) -> float: + return args.ad_cfg_scale if args.ad_use_cfg_scale else p.cfg_scale + + def get_sampler(self, p, args: ADetailerArgs) -> str: + if args.ad_use_sampler: + if args.ad_sampler == "Use same sampler": + return p.sampler_name + return args.ad_sampler + + if hasattr(p, "_ad_orig"): + return p._ad_orig.sampler_name + return p.sampler_name + + def get_scheduler(self, p, args: ADetailerArgs) -> dict[str, str]: + "webui >= 1.9.0" + if not args.ad_use_sampler: + return {"scheduler": getattr(p, "scheduler", "Automatic")} + + if args.ad_scheduler == "Use same scheduler": + value = getattr(p, "scheduler", "Automatic") + else: + value = args.ad_scheduler + return {"scheduler": value} + + def get_override_settings(self, p, args: ADetailerArgs) -> dict[str, Any]: + d = {} + + if args.ad_use_clip_skip: + d["CLIP_stop_at_last_layers"] = args.ad_clip_skip + + if ( + args.ad_use_checkpoint + and args.ad_checkpoint + and args.ad_checkpoint not in ("None", "Use same checkpoint") + ): + d["sd_model_checkpoint"] = args.ad_checkpoint + + if ( + args.ad_use_vae + and args.ad_vae + and args.ad_vae not in ("None", "Use same VAE") + ): + d["sd_vae"] = args.ad_vae + return d + + def get_initial_noise_multiplier(self, p, args: ADetailerArgs) -> float | None: + return args.ad_noise_multiplier if args.ad_use_noise_multiplier else None + + @staticmethod + def infotext(p) -> str: + return create_infotext( + p, p.all_prompts, p.all_seeds, p.all_subseeds, None, 0, 0 + ) + + def read_params_txt(self) -> str: + params_txt = Path(paths.data_path, PARAMS_TXT) + if params_txt.exists(): + return params_txt.read_text(encoding="utf-8") + return "" + + def write_params_txt(self, content: str) -> None: + params_txt = Path(paths.data_path, PARAMS_TXT) + if params_txt.exists() and content: + params_txt.write_text(content, encoding="utf-8") + + @staticmethod + def script_args_copy(script_args): + type_: type[list] | type[tuple] = type(script_args) + result = [] + for arg in script_args: + try: + a = copy(arg) + except TypeError: + a = arg + result.append(a) + return type_(result) + + def script_filter(self, p, args: ADetailerArgs): + script_runner = copy(p.scripts) + script_args = self.script_args_copy(p.script_args) + + ad_only_selected_scripts = opts.data.get("ad_only_selected_scripts", True) + if not ad_only_selected_scripts: + return script_runner, script_args + + ad_script_names_string: str = opts.data.get("ad_script_names", SCRIPT_DEFAULT) + ad_script_names = ad_script_names_string.split(",") + BUILTIN_SCRIPT.split(",") + script_names_set = { + name + for script_name in ad_script_names + for name in (script_name, script_name.strip()) + } + + if args.ad_controlnet_model != "None": + script_names_set.add("controlnet") + + filtered_alwayson = [] + for script_object in script_runner.alwayson_scripts: + filepath = script_object.filename + filename = Path(filepath).stem + if filename in script_names_set: + filtered_alwayson.append(script_object) + + script_runner.alwayson_scripts = filtered_alwayson + return script_runner, script_args + + def disable_controlnet_units( + self, script_args: list[Any] | tuple[Any, ...] + ) -> None: + for obj in script_args: + if "controlnet" in obj.__class__.__name__.lower(): + if hasattr(obj, "enabled"): + obj.enabled = False + if hasattr(obj, "input_mode"): + obj.input_mode = getattr(obj.input_mode, "SIMPLE", "simple") + + elif isinstance(obj, dict) and "module" in obj: + obj["enabled"] = False + + def get_i2i_p(self, p, args: ADetailerArgs, image): + seed, subseed = self.get_seed(p) + width, height = self.get_width_height(p, args) + steps = self.get_steps(p, args) + cfg_scale = self.get_cfg_scale(p, args) + initial_noise_multiplier = self.get_initial_noise_multiplier(p, args) + sampler_name = self.get_sampler(p, args) + override_settings = self.get_override_settings(p, args) + + version_args = {} + if schedulers: + version_args.update(self.get_scheduler(p, args)) + + i2i = StableDiffusionProcessingImg2Img( + init_images=[image], + resize_mode=0, + denoising_strength=args.ad_denoising_strength, + mask=None, + mask_blur=args.ad_mask_blur, + inpainting_fill=1, + inpaint_full_res=args.ad_inpaint_only_masked, + inpaint_full_res_padding=args.ad_inpaint_only_masked_padding, + inpainting_mask_invert=0, + initial_noise_multiplier=initial_noise_multiplier, + sd_model=p.sd_model, + outpath_samples=p.outpath_samples, + outpath_grids=p.outpath_grids, + prompt="", # replace later + negative_prompt="", + styles=p.styles, + seed=seed, + subseed=subseed, + subseed_strength=p.subseed_strength, + seed_resize_from_h=p.seed_resize_from_h, + seed_resize_from_w=p.seed_resize_from_w, + sampler_name=sampler_name, + batch_size=1, + n_iter=1, + steps=steps, + cfg_scale=cfg_scale, + width=width, + height=height, + restore_faces=args.ad_restore_face, + tiling=p.tiling, + extra_generation_params=copy_extra_params(p.extra_generation_params), + do_not_save_samples=True, + do_not_save_grid=True, + override_settings=override_settings, + **version_args, + ) + + i2i.cached_c = [None, None] + i2i.cached_uc = [None, None] + i2i.scripts, i2i.script_args = self.script_filter(p, args) + i2i._ad_disabled = True + i2i._ad_inner = True + + if args.ad_controlnet_model != "Passthrough" and controlnet_type != "forge": + self.disable_controlnet_units(i2i.script_args) + + if args.ad_controlnet_model not in ["None", "Passthrough"]: + self.update_controlnet_args(i2i, args) + elif args.ad_controlnet_model == "None": + i2i.control_net_enabled = False + + return i2i + + def save_image(self, p, image, *, condition: str, suffix: str) -> None: + i = get_i(p) + if p.all_prompts: + i %= len(p.all_prompts) + save_prompt = p.all_prompts[i] + else: + save_prompt = p.prompt + seed, _ = self.get_seed(p) + + if opts.data.get(condition, False): + images.save_image( + image=image, + path=p.outpath_samples, + basename="", + seed=seed, + prompt=save_prompt, + extension=opts.samples_format, + info=self.infotext(p), + p=p, + suffix=suffix, + ) + + def get_ad_model(self, name: str): + if name not in model_mapping: + msg = f"[-] ADetailer: Model {name!r} not found. Available models: {list(model_mapping.keys())}" + raise ValueError(msg) + return model_mapping[name] + + def sort_bboxes(self, pred: PredictOutput) -> PredictOutput: + sortby = opts.data.get("ad_bbox_sortby", BBOX_SORTBY[0]) + sortby_idx = BBOX_SORTBY.index(sortby) + return sort_bboxes(pred, sortby_idx) + + def pred_preprocessing(self, p, pred: PredictOutput, args: ADetailerArgs): + pred = filter_by_ratio( + pred, low=args.ad_mask_min_ratio, high=args.ad_mask_max_ratio + ) + pred = filter_k_largest(pred, k=args.ad_mask_k_largest) + pred = self.sort_bboxes(pred) + masks = mask_preprocess( + pred.masks, + kernel=args.ad_dilate_erode, + x_offset=args.ad_x_offset, + y_offset=args.ad_y_offset, + merge_invert=args.ad_mask_merge_invert, + ) + + if is_img2img_inpaint(p) and not is_inpaint_only_masked(p): + image_mask = self.get_image_mask(p) + masks = self.inpaint_mask_filter(image_mask, masks) + return masks + + @staticmethod + def i2i_prompts_replace( + i2i, prompts: list[str], negative_prompts: list[str], j: int + ) -> None: + i1 = min(j, len(prompts) - 1) + i2 = min(j, len(negative_prompts) - 1) + prompt = prompts[i1] + negative_prompt = negative_prompts[i2] + i2i.prompt = prompt + i2i.negative_prompt = negative_prompt + + @staticmethod + def compare_prompt(extra_params: dict[str, Any], processed, n: int = 0): + pt = "ADetailer prompt" + suffix(n) + if pt in extra_params and extra_params[pt] != processed.all_prompts[0]: + print( + f"[-] ADetailer: applied {ordinal(n + 1)} ad_prompt: {processed.all_prompts[0]!r}" + ) + + ng = "ADetailer negative prompt" + suffix(n) + if ng in extra_params and extra_params[ng] != processed.all_negative_prompts[0]: + print( + f"[-] ADetailer: applied {ordinal(n + 1)} ad_negative_prompt: {processed.all_negative_prompts[0]!r}" + ) + + @staticmethod + def get_i2i_init_image(p, pp): + if is_skip_img2img(p): + return p.init_images[0] + return pp.image + + @staticmethod + def get_each_tab_seed(seed: int, i: int): + use_same_seed = shared.opts.data.get("ad_same_seed_for_each_tab", False) + return seed if use_same_seed else seed + i + + @staticmethod + def inpaint_mask_filter( + img2img_mask: Image.Image, ad_mask: list[Image.Image] + ) -> list[Image.Image]: + if ad_mask and img2img_mask.size != ad_mask[0].size: + img2img_mask = img2img_mask.resize(ad_mask[0].size, resample=images.LANCZOS) + return [mask for mask in ad_mask if has_intersection(img2img_mask, mask)] + + @staticmethod + def get_image_mask(p) -> Image.Image: + mask = p.image_mask + if getattr(p, "inpainting_mask_invert", False): + mask = ImageChops.invert(mask) + mask = create_binary_mask(mask) + + if is_skip_img2img(p): + if hasattr(p, "init_images") and p.init_images: + width, height = p.init_images[0].size + else: + msg = "[-] ADetailer: no init_images." + raise RuntimeError(msg) + else: + width, height = p.width, p.height + return images.resize_image(p.resize_mode, mask, width, height) + + @rich_traceback + def process(self, p, *args_): + if getattr(p, "_ad_disabled", False): + return + + if is_img2img_inpaint(p) and is_all_black(self.get_image_mask(p)): + p._ad_disabled = True + msg = ( + "[-] ADetailer: img2img inpainting with no mask -- adetailer disabled." + ) + print(msg) + return + + if not self.is_ad_enabled(*args_): + p._ad_disabled = True + return + + self.set_skip_img2img(p, *args_) + if getattr(p, "_ad_disabled", False): + # case when img2img inpainting with skip img2img + return + + arg_list = self.get_args(p, *args_) + + if hasattr(p, "_ad_xyz_prompt_sr"): + replaced_positive_prompt, replaced_negative_prompt = self.get_prompt( + p, arg_list[0] + ) + arg_list[0].ad_prompt = replaced_positive_prompt[0] + arg_list[0].ad_negative_prompt = replaced_negative_prompt[0] + + extra_params = self.extra_params(arg_list) + p.extra_generation_params.update(extra_params) + + def _postprocess_image_inner( + self, p, pp, args: ADetailerArgs, *, n: int = 0 + ) -> bool: + """ + Returns + ------- + bool + + `True` if image was processed, `False` otherwise. + """ + if state.interrupted or state.skipped: + return False + + i = get_i(p) + + i2i = self.get_i2i_p(p, args, pp.image) + seed, subseed = self.get_seed(p) + ad_prompts, ad_negatives = self.get_prompt(p, args) + + is_mediapipe = args.is_mediapipe() + + kwargs = {} + if is_mediapipe: + predictor = mediapipe_predict + ad_model = args.ad_model + else: + predictor = ultralytics_predict + ad_model = self.get_ad_model(args.ad_model) + kwargs["device"] = self.ultralytics_device + kwargs["classes"] = args.ad_model_classes + + with change_torch_load(): + pred = predictor(ad_model, pp.image, args.ad_confidence, **kwargs) + + if pred.preview is None: + print( + f"[-] ADetailer: nothing detected on image {i + 1} with {ordinal(n + 1)} settings." + ) + return False + + masks = self.pred_preprocessing(p, pred, args) + shared.state.assign_current_image(pred.preview) + + self.save_image( + p, + pred.preview, + condition="ad_save_previews", + suffix="-ad-preview" + suffix(n, "-"), + ) + + steps = len(masks) + processed = None + state.job_count += steps + + if is_mediapipe: + print(f"mediapipe: {steps} detected.") + + p2 = copy(i2i) + for j in range(steps): + p2.image_mask = masks[j] + p2.init_images[0] = ensure_pil_image(p2.init_images[0], "RGB") + self.i2i_prompts_replace(p2, ad_prompts, ad_negatives, j) + + if re.match(r"^\s*\[SKIP\]\s*$", p2.prompt): + continue + + p2.seed = self.get_each_tab_seed(seed, j) + p2.subseed = self.get_each_tab_seed(subseed, j) + + p2.cached_c = [None, None] + p2.cached_uc = [None, None] + try: + processed = process_images(p2) + except NansException as e: + msg = f"[-] ADetailer: 'NansException' occurred with {ordinal(n + 1)} settings.\n{e}" + print(msg, file=sys.stderr) + continue + finally: + p2.close() + + self.compare_prompt(p.extra_generation_params, processed, n=n) + p2 = copy(i2i) + p2.init_images = [processed.images[0]] + + if processed is not None: + pp.image = processed.images[0] + return True + + return False + + @rich_traceback + def postprocess_image(self, p, pp, *args_): + if getattr(p, "_ad_disabled", False) or not self.is_ad_enabled(*args_): + return + + pp.image = self.get_i2i_init_image(p, pp) + pp.image = ensure_pil_image(pp.image, "RGB") + init_image = copy(pp.image) + arg_list = self.get_args(p, *args_) + params_txt_content = self.read_params_txt() + + if need_call_postprocess(p): + dummy = Processed(p, [], p.seed, "") + with preserve_prompts(p): + p.scripts.postprocess(copy(p), dummy) + + is_processed = False + with CNHijackRestore(), pause_total_tqdm(), cn_allow_script_control(): + for n, args in enumerate(arg_list): + if args.need_skip(): + continue + is_processed |= self._postprocess_image_inner(p, pp, args, n=n) + + if is_processed and not is_skip_img2img(p): + self.save_image( + p, init_image, condition="ad_save_images_before", suffix="-ad-before" + ) + + if need_call_process(p): + with preserve_prompts(p): + copy_p = copy(p) + p.scripts.before_process(copy_p) + p.scripts.process(copy_p) + + self.write_params_txt(params_txt_content) + + +def on_after_component(component, **_kwargs): + global txt2img_submit_button, img2img_submit_button + if getattr(component, "elem_id", None) == "txt2img_generate": + txt2img_submit_button = component + return + + if getattr(component, "elem_id", None) == "img2img_generate": + img2img_submit_button = component + + +def on_ui_settings(): + section = ("ADetailer", ADETAILER) + shared.opts.add_option( + "ad_max_models", + shared.OptionInfo( + default=4, + label="Max tabs", + component=gr.Slider, + component_args={"minimum": 1, "maximum": 15, "step": 1}, + section=section, + ).needs_reload_ui(), + ) + + shared.opts.add_option( + "ad_extra_models_dir", + shared.OptionInfo( + default="", + label="Extra paths to scan adetailer models separated by vertical bars(|)", + component=gr.Textbox, + section=section, + ) + .info("eg. path\\to\\models|C:\\path\\to\\models|another/path/to/models") + .needs_reload_ui(), + ) + + shared.opts.add_option( + "ad_save_previews", + shared.OptionInfo(False, "Save mask previews", section=section), + ) + + shared.opts.add_option( + "ad_save_images_before", + shared.OptionInfo(False, "Save images before ADetailer", section=section), + ) + + shared.opts.add_option( + "ad_only_selected_scripts", + shared.OptionInfo( + True, "Apply only selected scripts to ADetailer", section=section + ), + ) + + textbox_args = { + "placeholder": "comma-separated list of script names", + "interactive": True, + } + + shared.opts.add_option( + "ad_script_names", + shared.OptionInfo( + default=SCRIPT_DEFAULT, + label="Script names to apply to ADetailer (separated by comma)", + component=gr.Textbox, + component_args=textbox_args, + section=section, + ), + ) + + shared.opts.add_option( + "ad_bbox_sortby", + shared.OptionInfo( + default="None", + label="Sort bounding boxes by", + component=gr.Radio, + component_args={"choices": BBOX_SORTBY}, + section=section, + ), + ) + + shared.opts.add_option( + "ad_same_seed_for_each_tab", + shared.OptionInfo( + False, "Use same seed for each tab in adetailer", section=section + ), + ) + + +# xyz_grid + + +class PromptSR(NamedTuple): + s: str + r: str + + +def set_value(p, x: Any, xs: Any, *, field: str): + if not hasattr(p, "_ad_xyz"): + p._ad_xyz = {} + p._ad_xyz[field] = x + + +def search_and_replace_prompt(p, x: Any, xs: Any, replace_in_main_prompt: bool): + if replace_in_main_prompt: + p.prompt = p.prompt.replace(xs[0], x) + p.negative_prompt = p.negative_prompt.replace(xs[0], x) + + if not hasattr(p, "_ad_xyz_prompt_sr"): + p._ad_xyz_prompt_sr = [] + p._ad_xyz_prompt_sr.append(PromptSR(s=xs[0], r=x)) + + +def make_axis_on_xyz_grid(): + xyz_grid = None + for script in scripts.scripts_data: + if script.script_class.__module__ == "xyz_grid.py": + xyz_grid = script.module + break + + if xyz_grid is None: + return + + model_list = ["None", *model_mapping.keys()] + samplers = [sampler.name for sampler in all_samplers] + + axis = [ + xyz_grid.AxisOption( + "[ADetailer] ADetailer model 1st", + str, + partial(set_value, field="ad_model"), + choices=lambda: model_list, + ), + xyz_grid.AxisOption( + "[ADetailer] ADetailer prompt 1st", + str, + partial(set_value, field="ad_prompt"), + ), + xyz_grid.AxisOption( + "[ADetailer] ADetailer negative prompt 1st", + str, + partial(set_value, field="ad_negative_prompt"), + ), + xyz_grid.AxisOption( + "[ADetailer] Prompt S/R (AD 1st)", + str, + partial(search_and_replace_prompt, replace_in_main_prompt=False), + ), + xyz_grid.AxisOption( + "[ADetailer] Prompt S/R (AD 1st and main prompt)", + str, + partial(search_and_replace_prompt, replace_in_main_prompt=True), + ), + xyz_grid.AxisOption( + "[ADetailer] Mask erosion / dilation 1st", + int, + partial(set_value, field="ad_dilate_erode"), + ), + xyz_grid.AxisOption( + "[ADetailer] Inpaint denoising strength 1st", + float, + partial(set_value, field="ad_denoising_strength"), + ), + xyz_grid.AxisOption( + "[ADetailer] Inpaint only masked 1st", + str, + partial(set_value, field="ad_inpaint_only_masked"), + choices=lambda: ["True", "False"], + ), + xyz_grid.AxisOption( + "[ADetailer] Inpaint only masked padding 1st", + int, + partial(set_value, field="ad_inpaint_only_masked_padding"), + ), + xyz_grid.AxisOption( + "[ADetailer] ADetailer sampler 1st", + str, + partial(set_value, field="ad_sampler"), + choices=lambda: samplers, + ), + xyz_grid.AxisOption( + "[ADetailer] ControlNet model 1st", + str, + partial(set_value, field="ad_controlnet_model"), + choices=lambda: ["None", "Passthrough", *get_cn_models()], + ), + ] + + if not any(x.label.startswith("[ADetailer]") for x in xyz_grid.axis_options): + xyz_grid.axis_options.extend(axis) + + +def on_before_ui(): + try: + make_axis_on_xyz_grid() + except Exception: + error = traceback.format_exc() + print( + f"[-] ADetailer: xyz_grid error:\n{error}", + file=sys.stderr, + ) + + +# api + + +def add_api_endpoints(_: gr.Blocks, app: FastAPI): + @app.get("/adetailer/v1/version") + async def version(): + return {"version": __version__} + + @app.get("/adetailer/v1/schema") + async def schema(): + if hasattr(ADetailerArgs, "model_json_schema"): + return ADetailerArgs.model_json_schema() + return ADetailerArgs.schema() + + @app.get("/adetailer/v1/ad_model") + async def ad_model(): + return {"ad_model": list(model_mapping)} + + +script_callbacks.on_ui_settings(on_ui_settings) +script_callbacks.on_after_component(on_after_component) +script_callbacks.on_app_started(add_api_endpoints) +script_callbacks.on_before_ui(on_before_ui) diff --git a/extensions/4-adetailer/scripts/__pycache__/!adetailer.cpython-310.pyc b/extensions/4-adetailer/scripts/__pycache__/!adetailer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d034c9eb2f1ddbf06d0bbc93a868550eb8e5cba1 Binary files /dev/null and b/extensions/4-adetailer/scripts/__pycache__/!adetailer.cpython-310.pyc differ diff --git a/extensions/4-adetailer/tests/__init__.py b/extensions/4-adetailer/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/extensions/4-adetailer/tests/conftest.py b/extensions/4-adetailer/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..eb33c236a6cdc7a72cde7f4175796badc87fc512 --- /dev/null +++ b/extensions/4-adetailer/tests/conftest.py @@ -0,0 +1,18 @@ +import pytest +import requests +from PIL import Image + + +def get_image(url: str) -> Image.Image: + resp = requests.get(url, stream=True, headers={"User-Agent": "Mozilla/5.0"}) + return Image.open(resp.raw) + + +@pytest.fixture(scope="session") +def sample_image(): + return get_image("https://i.imgur.com/E5OVXvn.png") + + +@pytest.fixture(scope="session") +def sample_image2(): + return get_image("https://i.imgur.com/px5UT7T.png") diff --git a/extensions/4-adetailer/tests/test_args.py b/extensions/4-adetailer/tests/test_args.py new file mode 100644 index 0000000000000000000000000000000000000000..e427162b99a14b5aaa36335fb1020a94d3f600e4 --- /dev/null +++ b/extensions/4-adetailer/tests/test_args.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import pytest + +from adetailer.args import ALL_ARGS, ADetailerArgs + + +def test_all_args() -> None: + args = ADetailerArgs() + for attr, _ in ALL_ARGS: + assert hasattr(args, attr), attr + + for attr, _ in args: + if attr == "is_api": + continue + assert attr in ALL_ARGS.attrs, attr + + +@pytest.mark.parametrize( + ("ad_model", "expect"), + [("mediapipe_face_full", True), ("face_yolov8n.pt", False)], +) +def test_is_mediapipe(ad_model: str, expect: bool) -> None: + args = ADetailerArgs(ad_model=ad_model) + assert args.is_mediapipe() is expect + + +@pytest.mark.parametrize( + ("ad_model", "expect"), + [("mediapipe_face_full", False), ("face_yolov8n.pt", False), ("None", True)], +) +def test_need_skip(ad_model: str, expect: bool) -> None: + args = ADetailerArgs(ad_model=ad_model) + assert args.need_skip() is expect + + +@pytest.mark.parametrize( + ("ad_model", "ad_tab_enable", "expect"), + [ + ("face_yolov8n.pt", False, True), + ("mediapipe_face_full", False, True), + ("None", True, True), + ("ace_yolov8s.pt", True, False), + ], +) +def test_need_skip_tab_enable(ad_model: str, ad_tab_enable: bool, expect: bool) -> None: + args = ADetailerArgs(ad_model=ad_model, ad_tab_enable=ad_tab_enable) + assert args.need_skip() is expect diff --git a/extensions/4-adetailer/tests/test_common.py b/extensions/4-adetailer/tests/test_common.py new file mode 100644 index 0000000000000000000000000000000000000000..c162990790357c5ee580070e56bcdb1e9640305e --- /dev/null +++ b/extensions/4-adetailer/tests/test_common.py @@ -0,0 +1,69 @@ +import numpy as np +from PIL import Image, ImageDraw + +from adetailer.common import create_bbox_from_mask, create_mask_from_bbox + + +def test_create_mask_from_bbox(): + img = Image.new("L", (10, 10), color="black") + bbox = [[1.0, 1.0, 2.0, 2.0], [7.0, 7.0, 8.0, 8.0]] + masks = create_mask_from_bbox(bbox, img.size) + expect1 = np.array( + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 255, 255, 0, 0, 0, 0, 0, 0, 0], + [0, 255, 255, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ], + dtype=np.uint8, + ) + expect2 = np.array( + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 255, 255, 0], + [0, 0, 0, 0, 0, 0, 0, 255, 255, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ], + dtype=np.uint8, + ) + assert len(masks) == len(bbox) + arr1 = np.array(masks[0]) + arr2 = np.array(masks[1]) + assert arr1.shape == expect1.shape + assert arr2.shape == expect2.shape + assert arr1.shape == (10, 10) + assert arr1.dtype == expect1.dtype + assert arr2.dtype == expect2.dtype + assert np.array_equal(arr1, expect1) + assert np.array_equal(arr2, expect2) + + # The function correctly receives a list of masks and the shape of the image. + + +def test_create_bbox_from_mask(): + mask = Image.new("L", (10, 10), color="black") + draw = ImageDraw.Draw(mask) + draw.rectangle((2, 2, 5, 5), fill="white") + + result = create_bbox_from_mask([mask], (10, 10)) + + assert isinstance(result, list) + assert len(result) == 1 + assert all(isinstance(bbox, list) for bbox in result) + assert all(len(bbox) == 4 for bbox in result) + assert result[0] == [2, 2, 6, 6] + + result = create_bbox_from_mask([mask], (256, 256)) + assert result[0] == [38, 38, 166, 166] diff --git a/extensions/4-adetailer/tests/test_mask.py b/extensions/4-adetailer/tests/test_mask.py new file mode 100644 index 0000000000000000000000000000000000000000..37191fd244b681141a7cd37d8d486c7709870f88 --- /dev/null +++ b/extensions/4-adetailer/tests/test_mask.py @@ -0,0 +1,236 @@ +import cv2 +import numpy as np +import pytest +from PIL import Image, ImageDraw + +from adetailer.mask import ( + bbox_area, + dilate_erode, + has_intersection, + is_all_black, + mask_invert, + mask_merge, + offset, +) + + +def test_dilate_positive_value(): + img = Image.new("L", (10, 10), color="black") + draw = ImageDraw.Draw(img) + draw.rectangle((3, 3, 5, 5), fill="white") + value = 3 + + result = dilate_erode(img, value) + + assert isinstance(result, Image.Image) + assert result.size == (10, 10) + + expect = np.array( + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 255, 255, 255, 255, 255, 0, 0, 0], + [0, 0, 255, 255, 255, 255, 255, 0, 0, 0], + [0, 0, 255, 255, 255, 255, 255, 0, 0, 0], + [0, 0, 255, 255, 255, 255, 255, 0, 0, 0], + [0, 0, 255, 255, 255, 255, 255, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ], + dtype=np.uint8, + ) + assert np.array_equal(np.array(result), expect) + + +def test_offset(): + img = Image.new("L", (10, 10), color="black") + draw = ImageDraw.Draw(img) + draw.rectangle((4, 4, 5, 5), fill="white") + + result = offset(img, x=1, y=2) + + assert isinstance(result, Image.Image) + assert result.size == (10, 10) + + expect = np.array( + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 255, 255, 0, 0, 0], + [0, 0, 0, 0, 0, 255, 255, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ], + dtype=np.uint8, + ) + assert np.array_equal(np.array(result), expect) + + +class TestIsAllBlack: + def test_is_all_black_1(self): + img = Image.new("L", (10, 10), color="black") + assert is_all_black(img) + + draw = ImageDraw.Draw(img) + draw.rectangle((4, 4, 5, 5), fill="white") + assert not is_all_black(img) + + def test_is_all_black_2(self): + img = np.zeros((10, 10), dtype=np.uint8) + assert is_all_black(img) + + img[4:6, 4:6] = 255 + assert not is_all_black(img) + + def test_is_all_black_rgb_image_pil(self): + img = Image.new("RGB", (10, 10), color="red") + assert not is_all_black(img) + + img = Image.new("RGBA", (10, 10), color="red") + assert not is_all_black(img) + + def test_is_all_black_rgb_image_numpy(self): + img = np.full((10, 10, 4), 127, dtype=np.uint8) + with pytest.raises(cv2.error): + is_all_black(img) + + img = np.full((4, 10, 10), 0.5, dtype=np.float32) + with pytest.raises(cv2.error): + is_all_black(img) + + +class TestHasIntersection: + def test_has_intersection_1(self): + arr1 = np.array( + [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ], + dtype=np.uint8, + ) + arr2 = arr1.copy() + assert not has_intersection(arr1, arr2) + + def test_has_intersection_2(self): + arr1 = np.array( + [ + [0, 0, 0, 0], + [0, 255, 255, 0], + [0, 255, 255, 0], + [0, 0, 0, 0], + ], + dtype=np.uint8, + ) + arr2 = np.array( + [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 255, 255], + [0, 0, 255, 255], + ], + dtype=np.uint8, + ) + assert has_intersection(arr1, arr2) + + arr3 = np.array( + [ + [255, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 255], + [0, 0, 255, 255], + ], + dtype=np.uint8, + ) + assert not has_intersection(arr1, arr3) + + def test_has_intersection_3(self): + img1 = Image.new("L", (10, 10), color="black") + draw1 = ImageDraw.Draw(img1) + draw1.rectangle((3, 3, 5, 5), fill="white") + img2 = Image.new("L", (10, 10), color="black") + draw2 = ImageDraw.Draw(img2) + draw2.rectangle((6, 6, 8, 8), fill="white") + assert not has_intersection(img1, img2) + + img3 = Image.new("L", (10, 10), color="black") + draw3 = ImageDraw.Draw(img3) + draw3.rectangle((2, 2, 8, 8), fill="white") + assert has_intersection(img1, img3) + + def test_has_intersection_4(self): + img1 = Image.new("RGB", (10, 10), color="black") + draw1 = ImageDraw.Draw(img1) + draw1.rectangle((3, 3, 5, 5), fill="white") + img2 = Image.new("RGBA", (10, 10), color="black") + draw2 = ImageDraw.Draw(img2) + draw2.rectangle((2, 2, 8, 8), fill="white") + assert has_intersection(img1, img2) + + def test_has_intersection_5(self): + img1 = Image.new("RGB", (10, 10), color="black") + draw1 = ImageDraw.Draw(img1) + draw1.rectangle((4, 4, 5, 5), fill="white") + img2 = np.full((10, 10, 4), 255, dtype=np.uint8) + assert has_intersection(img1, img2) + + +def test_bbox_area(): + bbox = [0.0, 0.0, 10.0, 10.0] + assert bbox_area(bbox) == 100 + + +class TestMaskMerge: + def test_mask_merge(self): + img1 = Image.new("L", (10, 10), color="black") + draw1 = ImageDraw.Draw(img1) + draw1.rectangle((3, 3, 5, 5), fill="white") + + img2 = Image.new("L", (10, 10), color="black") + draw2 = ImageDraw.Draw(img2) + draw2.rectangle((6, 6, 8, 8), fill="white") + + merged = mask_merge([img1, img2]) + assert len(merged) == 1 + + expect = Image.new("L", (10, 10), color="black") + draw3 = ImageDraw.Draw(expect) + draw3.rectangle((3, 3, 5, 5), fill="white") + draw3.rectangle((6, 6, 8, 8), fill="white") + + assert np.array_equal(np.array(merged[0]), np.array(expect)) + + def test_merge_mask_different_size(self): + img1 = Image.new("L", (10, 10), color="black") + draw1 = ImageDraw.Draw(img1) + draw1.rectangle((3, 3, 5, 5), fill="white") + + img2 = Image.new("L", (20, 20), color="black") + draw2 = ImageDraw.Draw(img2) + draw2.rectangle((6, 6, 8, 8), fill="white") + + with pytest.raises( + cv2.error, match="-209:Sizes of input arguments do not match" + ): + mask_merge([img1, img2]) + + +def test_mask_invert(): + img = Image.new("L", (10, 10), color="black") + draw = ImageDraw.Draw(img) + draw.rectangle((3, 3, 5, 5), fill="white") + + inverted = mask_invert([img]) + assert len(inverted) == 1 + + expect = Image.new("L", (10, 10), color="white") + draw = ImageDraw.Draw(expect) + draw.rectangle((3, 3, 5, 5), fill="black") + + assert np.array_equal(np.array(inverted[0]), np.array(expect)) diff --git a/extensions/4-adetailer/tests/test_mediapipe.py b/extensions/4-adetailer/tests/test_mediapipe.py new file mode 100644 index 0000000000000000000000000000000000000000..7ddcdfe7c23e8a33c8be4c3ed889e2debd3bb484 --- /dev/null +++ b/extensions/4-adetailer/tests/test_mediapipe.py @@ -0,0 +1,18 @@ +import pytest +from PIL import Image + +from adetailer.mediapipe import mediapipe_predict + + +@pytest.mark.parametrize( + "model_name", + [ + "mediapipe_face_short", + "mediapipe_face_full", + "mediapipe_face_mesh", + "mediapipe_face_mesh_eyes_only", + ], +) +def test_mediapipe(sample_image2: Image.Image, model_name: str): + result = mediapipe_predict(model_name, sample_image2) + assert result.preview is not None diff --git a/extensions/4-adetailer/tests/test_ultralytics.py b/extensions/4-adetailer/tests/test_ultralytics.py new file mode 100644 index 0000000000000000000000000000000000000000..c7726078fcf6401c9c7f45e8c04ed57f91c759a6 --- /dev/null +++ b/extensions/4-adetailer/tests/test_ultralytics.py @@ -0,0 +1,50 @@ +import pytest +from huggingface_hub import hf_hub_download +from PIL import Image + +from adetailer.ultralytics import ultralytics_predict + + +@pytest.mark.parametrize( + "model_name", + [ + "face_yolov8n.pt", + "face_yolov8n_v2.pt", + "face_yolov8s.pt", + "face_yolov9c.pt", + "hand_yolov8n.pt", + "hand_yolov8s.pt", + "hand_yolov9c.pt", + "person_yolov8n-seg.pt", + "person_yolov8s-seg.pt", + "person_yolov8m-seg.pt", + "deepfashion2_yolov8s-seg.pt", + ], +) +def test_ultralytics_hf_models(sample_image: Image.Image, model_name: str): + model_path = hf_hub_download("Bingsu/adetailer", model_name) + result = ultralytics_predict(model_path, sample_image) + assert result.preview is not None + + +def test_yolo_world_default(sample_image: Image.Image): + model_path = hf_hub_download("Bingsu/yolo-world-mirror", "yolov8x-worldv2.pt") + result = ultralytics_predict(model_path, sample_image) + assert result.preview is not None + + +@pytest.mark.parametrize( + "klass", + [ + "person", + "bird", + "yellow bird", + "person,glasses,headphone", + "person,bird", + "glasses,yellow bird", + ], +) +def test_yolo_world(sample_image2: Image.Image, klass: str): + model_path = hf_hub_download("Bingsu/yolo-world-mirror", "yolov8x-worldv2.pt") + result = ultralytics_predict(model_path, sample_image2, classes=klass) + assert result.preview is not None diff --git a/extensions/CFG_Rescale_webui/.gitignore b/extensions/CFG_Rescale_webui/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..68bc17f9ff2104a9d7b6777058bb4c343ca72609 --- /dev/null +++ b/extensions/CFG_Rescale_webui/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/extensions/CFG_Rescale_webui/LICENSE b/extensions/CFG_Rescale_webui/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..58c0507582ba181710a386bc9134d238627fa3fb --- /dev/null +++ b/extensions/CFG_Rescale_webui/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 CartUniverse + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/CFG_Rescale_webui/README.md b/extensions/CFG_Rescale_webui/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b11c1902c227e557baf20d0fc472c467b0f2643c --- /dev/null +++ b/extensions/CFG_Rescale_webui/README.md @@ -0,0 +1,10 @@ +# CFG_Rescale_webui +Adds a CFG rescale option to A1111 webui for vpred models. Implements features described in https://arxiv.org/pdf/2305.08891.pdf + +When installed, you will see a CFG slider below appear below the seed selection. + +The recommended settings from the paper is 0.7 CFG rescale. The paper recommends using it at 7.5 CFG, but I find that the default 7.0 CFG works just fine as well. +If you notice your images are dull or muddy, try setting CFG Rescale to 0.5 and/or turning on color fix. + +# Extra Features +Auto color fix: CFG rescale can result in the color palette of the image becoming smaller, resulting in muddy or brown images. Turning this on attempts to return the image colors to full range again, making it appear more vibrant and natural. diff --git a/extensions/CFG_Rescale_webui/scripts/CFGRescale.py b/extensions/CFG_Rescale_webui/scripts/CFGRescale.py new file mode 100644 index 0000000000000000000000000000000000000000..2b7b793649ff117ac798a780570bf7ff1eb02ecf --- /dev/null +++ b/extensions/CFG_Rescale_webui/scripts/CFGRescale.py @@ -0,0 +1,225 @@ +import math +import torch +import re +import gradio as gr +import numpy as np +import modules.scripts as scripts +import modules.images as saving +from modules import devices, processing, shared, sd_samplers_kdiffusion, sd_samplers_compvis, script_callbacks +from modules.processing import Processed +from modules.shared import opts, state +from ldm.models.diffusion import ddim +from PIL import Image + +from ldm.modules.diffusionmodules.util import make_ddim_sampling_parameters, noise_like + +re_prompt_cfgr = re.compile(r"]+)>") + +class Script(scripts.Script): + + def __init__(self): + self.old_denoising = sd_samplers_kdiffusion.CFGDenoiser.combine_denoised + self.old_schedule = ddim.DDIMSampler.make_schedule + self.old_sample = ddim.DDIMSampler.p_sample_ddim + globals()['enable_furry_cocks'] = True + + def find_module(module_names): + if isinstance(module_names, str): + module_names = [s.strip() for s in module_names.split(",")] + for data in scripts.scripts_data: + if data.script_class.__module__ in module_names and hasattr(data, "module"): + return data.module + return None + + def rescale_opt(p, x, xs): + globals()['cfg_rescale_fi'] = x + globals()['enable_furry_cocks'] = False + + xyz_grid = find_module("xyz_grid.py, xy_grid.py") + if xyz_grid: + extra_axis_options = [xyz_grid.AxisOption("Rescale CFG", float, rescale_opt)] + xyz_grid.axis_options.extend(extra_axis_options) + + def title(self): + return "CFG Rescale Extension" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with gr.Accordion("CFG Rescale", open=True, elem_id="cfg_rescale"): + rescale = gr.Slider(label="CFG Rescale", show_label=False, minimum=0.0, maximum=1.0, step=0.01, value=0.0) + with gr.Row(): + recolor = gr.Checkbox(label="Auto Color Fix", default=False) + rec_strength = gr.Slider(label="Fix Strength", interactive=True, visible=False, + elem_id=self.elem_id("rec_strength"), minimum=0.1, maximum=10.0, step=0.1, + value=1.0) + show_original = gr.Checkbox(label="Keep Original Images", elem_id=self.elem_id("show_original"), visible=False, default=False) + + def show_recolor_strength(rec_checked): + return [gr.update(visible=rec_checked), gr.update(visible=rec_checked)] + + recolor.change( + fn=show_recolor_strength, + inputs=recolor, + outputs=[rec_strength, show_original] + ) + + self.infotext_fields = [ + (rescale, "CFG Rescale"), + (recolor, "Auto Color Fix") + ] + self.paste_field_names = [] + for _, field_name in self.infotext_fields: + self.paste_field_names.append(field_name) + return [rescale, recolor, rec_strength, show_original] + + def cfg_replace(self, x_out, conds_list, uncond, cond_scale): + denoised_uncond = x_out[-uncond.shape[0]:] + denoised = torch.clone(denoised_uncond) + fi = globals()['cfg_rescale_fi'] + + for i, conds in enumerate(conds_list): + for cond_index, weight in conds: + if fi == 0: + denoised[i] += (x_out[cond_index] - denoised_uncond[i]) * (weight * cond_scale) + else: + xcfg = (denoised_uncond[i] + (x_out[cond_index] - denoised_uncond[i]) * (cond_scale * weight)) + xrescaled = (torch.std(x_out[cond_index]) / torch.std(xcfg)) + xfinal = fi * xrescaled + (1.0 - fi) + denoised[i] = xfinal * xcfg + + return denoised + + def process(self, p, rescale, recolor, rec_strength, show_original): + + if globals()['enable_furry_cocks']: + # parse from prompt for override + rescale_override = None + def found(m): + nonlocal rescale_override + try: + rescale_override = float(m.group(1)) + except ValueError: + rescale_override = None + return "" + p.prompt = re.sub(re_prompt_cfgr, found, p.prompt) + if rescale_override is not None: + rescale = rescale_override + + globals()['cfg_rescale_fi'] = rescale + else: + # rescale value is being set from xyz_grid + rescale = globals()['cfg_rescale_fi'] + globals()['enable_furry_cocks'] = True + + sd_samplers_kdiffusion.CFGDenoiser.combine_denoised = self.cfg_replace + + if rescale > 0: + p.extra_generation_params["CFG Rescale"] = rescale + + if recolor: + p.extra_generation_params["Auto Color Fix Strength"] = rec_strength + p.do_not_save_samples = True + + def postprocess_batch_list(self, p, pp, rescale, recolor, rec_strength, show_original, batch_number): + if recolor and show_original: + num = len(pp.images) + for i in range(num): + pp.images.append(pp.images[i]) + p.prompts.append(p.prompts[i]) + p.negative_prompts.append(p.negative_prompts[i]) + p.seeds.append(p.seeds[i]) + p.subseeds.append(p.subseeds[i]) + + def postprocess(self, p, processed, rescale, recolor, rec_strength, show_original): + sd_samplers_kdiffusion.CFGDenoiser.combine_denoised = self.old_denoising + + def postfix(img, rec_strength): + prec = 0.0005 * rec_strength + r, g, b = img.split() + + # softer effect + # r_min, r_max = np.percentile(r, p), np.percentile(r, 100.0 - p) + # g_min, g_max = np.percentile(g, p), np.percentile(g, 100.0 - p) + # b_min, b_max = np.percentile(b, p), np.percentile(b, 100.0 - p) + + rh, rbins = np.histogram(r, 256, (0, 256)) + tmp = np.where(rh > rh.sum() * prec)[0] + r_min = tmp.min() + r_max = tmp.max() + + gh, gbins = np.histogram(g, 256, (0, 256)) + tmp = np.where(gh > gh.sum() * prec)[0] + g_min = tmp.min() + g_max = tmp.max() + + bh, bbins = np.histogram(b, 256, (0, 256)) + tmp = np.where(bh > bh.sum() * prec)[0] + b_min = tmp.min() + b_max = tmp.max() + + r = r.point(lambda i: int(255 * (min(max(i, r_min), r_max) - r_min) / (r_max - r_min))) + g = g.point(lambda i: int(255 * (min(max(i, g_min), g_max) - g_min) / (g_max - g_min))) + b = b.point(lambda i: int(255 * (min(max(i, b_min), b_max) - b_min) / (b_max - b_min))) + + new_img = Image.merge("RGB", (r, g, b)) + + return new_img + + if recolor: + grab = 0 + n_img = len(processed.images) + for i in range(n_img): + doit = False + + if show_original: + check = i + if opts.return_grid: + if i == 0: + continue + else: + check = check - 1 + doit = check % (p.batch_size * 2) >= p.batch_size + else: + if n_img > 1 and i != 0: + doit = True + elif n_img == 1 or not opts.return_grid: + doit = True + + if doit: + res_img = postfix(processed.images[i], rec_strength) + if opts.samples_save: + ind = grab + grab += 1 + prompt_infotext = processing.create_infotext(p, p.all_prompts, p.all_seeds, p.all_subseeds, + index=ind) + # Save images to disk + if opts.samples_save: + saving.save_image(processed.images[i], p.outpath_samples, "", seed=p.all_seeds[ind], + prompt=p.all_prompts[ind], + info=prompt_infotext, p=p, suffix="colorfix") + saving.save_image(res_img, p.outpath_samples, "", seed=p.all_seeds[ind], + prompt=p.all_prompts[ind], + info=prompt_infotext, p=p, suffix="colorfix") + + processed.images[i] = res_img + + +def on_infotext_pasted(infotext, params): + if "CFG Rescale" not in params: + params["CFG Rescale"] = 0 + + if "CFG Rescale φ" in params: + params["CFG Rescale"] = params["CFG Rescale φ"] + del params["CFG Rescale φ"] + + if "CFG Rescale phi" in params and scripts.scripts_txt2img.script("Neutral Prompt") is None: + params["CFG Rescale"] = params["CFG Rescale phi"] + del params["CFG Rescale phi"] + + if "DDIM Trailing" not in params: + params["DDIM Trailing"] = False + + +script_callbacks.on_infotext_pasted(on_infotext_pasted) diff --git a/extensions/CFG_Rescale_webui/scripts/__pycache__/CFGRescale.cpython-310.pyc b/extensions/CFG_Rescale_webui/scripts/__pycache__/CFGRescale.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e78cc503cd34a0666c2f590afee7aaf3e1f981b5 Binary files /dev/null and b/extensions/CFG_Rescale_webui/scripts/__pycache__/CFGRescale.cpython-310.pyc differ diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/.github/FUNDING.yml b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..5c141bdcbeeae0648a0b899230e4c7616775dc46 --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: pkuliyi2015 # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/.gitignore b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5a98b2c74de04a2007afac44ac4af35661c101e6 --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/.gitignore @@ -0,0 +1,13 @@ +# meta +.vscode/ +__pycache__/ +.DS_Store + +# settings +region_configs/ + +# test images +deflicker/input_frames/* + +# test features +deflicker/* diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/LICENSE b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..bfef380bf7d9cb74ec9ba533b37c3fbeef3bdc09 --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/LICENSE @@ -0,0 +1,437 @@ +Attribution-NonCommercial-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International +Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial-ShareAlike 4.0 International Public License +("Public License"). To the extent this Public License may be +interpreted as a contract, You are granted the Licensed Rights in +consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the +Licensor receives from making the Licensed Material available under +these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-NC-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution, NonCommercial, and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + l. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + m. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + n. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-NC-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. \ No newline at end of file diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/README.md b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d3f5f9e8df49ff261953adcb37b84f82cf523ec7 --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/README.md @@ -0,0 +1,95 @@ +# Tiled Diffusion & VAE extension for sd-webui + +[![CC BY-NC-SA 4.0][cc-by-nc-sa-shield]][cc-by-nc-sa] + +This extension is licensed under [CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/), everyone is FREE of charge to access, use, modify and redistribute with the same license. +**You cannot use versions after AOE 2023.3.28 for commercial sales (only refers to code of this repo, the derived artworks are NOT restricted).** + +由于部分无良商家销售WebUI,捆绑本插件做卖点收取智商税,本仓库的许可证已修改为 [CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/),任何人都可以自由获取、使用、修改、以相同协议重分发本插件。 +**自许可证修改之日(AOE 2023.3.28)起,之后的版本禁止用于商业贩售 (不可贩售本仓库代码,但衍生的艺术创作内容物不受此限制)。** + +If you like the project, please give me a star! ⭐ + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/pkuliyi2015) + +**** + + +The extension helps you to **generate or upscale large images (≥2K) with limited VRAM (≤6GB)** via the following techniques: + +- Reproduced SOTA Tiled Diffusion methods + - [Mixture of Diffusers](https://github.com/albarji/mixture-of-diffusers) + - [MultiDiffusion](https://multidiffusion.github.io) + - [Demofusion](https://github.com/PRIS-CV/DemoFusion) +- Our original Tiled VAE method +- My original Tiled Noise Inversion method + + +### Features + +- Core + - [x] [Tiled VAE](#tiled-vae) + - [x] [Tiled Diffusion: txt2img generation for ultra-large image](#tiled-diff-txt2img) + - [x] [Tiled Diffusion: img2img upscaling for image detail enhancement](#tiled-diff-img2img) + - [x] [Regional Prompt Control](#region-prompt-control) + - [x] [Tiled Noise Inversion](#tiled-noise-inversion) +- Advanced + - [x] [ControlNet support]() + - [x] [StableSR support](https://github.com/pkuliyi2015/sd-webui-stablesr) + - [x] [SDXL support](experimental) + - [x] [Demofusion support]() + +👉 在 [wiki](https://github.com/pkuliyi2015/multidiffusion-upscaler-for-automatic1111/wiki) 页面查看详细的文档和样例,以及由 [@PotatoBananaApple](https://github.com/pkuliyi2015/multidiffusion-upscaler-for-automatic1111/discussions/120) 制作的 [快速入门教程](https://civitai.com/models/34726) +👉 Find detailed documentation & examples at our [wiki](https://github.com/pkuliyi2015/multidiffusion-upscaler-for-automatic1111/wiki), and quickstart [Tutorial](https://civitai.com/models/34726) by [@PotatoBananaApple](https://github.com/pkuliyi2015/multidiffusion-upscaler-for-automatic1111/discussions/120) 🎉 + + +### Examples + +⚪ Txt2img: generating ultra-large images + +`prompt: masterpiece, best quality, highres, city skyline, night.` + +![panorama](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/city_panorama.jpeg?raw=true) + +⚪ Img2img: upcaling for detail enhancement + +| original | x4 upscale | +| :-: | :-: | +| ![lowres](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/lowres.jpg?raw=true) | ![highres](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/highres.jpeg?raw=true) | + +⚪ Regional Prompt Control + +| region setting | output1 | output2 | +| :-: | :-: | :-: | +| ![MultiCharacterRegions](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/multicharacter.png?raw=true) | ![MultiCharacter](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/multicharacter.jpeg?raw=true) | ![MultiCharacter](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/multicharacter2.jpeg?raw=true) | +| ![FullBodyRegions](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/fullbody_regions.png?raw=true) | ![FullBody](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/fullbody.jpeg?raw=true) | ![FullBody2](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/fullbody2.jpeg?raw=true) | + +⚪ ControlNet support + +| original | with canny | +| :-: | :-: | +| ![Your Name](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/yourname_canny.jpeg?raw=true) | ![Your Name](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/yourname.jpeg?raw=true) + +| | 重绘 “清明上河图” | +| :-: | :-: | +| original | ![ancient city](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/ancient_city_origin_compressed.jpeg?raw=true) | +| processed | ![ancient city](https://github.com/pkuliyi2015/multidiffusion-img-demo/blob/master/ancient_city_compressed.jpeg?raw=true) | + +⚪ DemoFusion support + +| original | x3 upscale | +| :-: | :-: | +| ![demo-example](https://github.com/Jaylen-Lee/image-demo/blob/main/example.png?raw=true) | ![demo-result](https://github.com/Jaylen-Lee/image-demo/blob/main/3.png?raw=true) | + + +### License + +Great thanks to all the contributors! 🎉🎉🎉 +This work is licensed under [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License][cc-by-nc-sa]. + +[![CC BY-NC-SA 4.0][cc-by-nc-sa-image]][cc-by-nc-sa] +[![CC BY-NC-SA 4.0][cc-by-nc-sa-shield]][cc-by-nc-sa] + +[cc-by-nc-sa]: http://creativecommons.org/licenses/by-nc-sa/4.0/ +[cc-by-nc-sa-image]: https://licensebuttons.net/l/by-nc-sa/4.0/88x31.png +[cc-by-nc-sa-shield]: https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/javascript/bboxHint.js b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/javascript/bboxHint.js new file mode 100644 index 0000000000000000000000000000000000000000..a3a53d1f801b8ae699f403409fc26a92a06e91fb --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/javascript/bboxHint.js @@ -0,0 +1,540 @@ +const BBOX_MAX_NUM = 16; +const BBOX_WARNING_SIZE = 1280; +const DEFAULT_X = 0.4; +const DEFAULT_Y = 0.4; +const DEFAULT_H = 0.2; +const DEFAULT_W = 0.2; + +// ref: https://html-color.codes/ +const COLOR_MAP = [ + ['#ff0000', 'rgba(255, 0, 0, 0.3)'], // red + ['#ff9900', 'rgba(255, 153, 0, 0.3)'], // orange + ['#ffff00', 'rgba(255, 255, 0, 0.3)'], // yellow + ['#33cc33', 'rgba(51, 204, 51, 0.3)'], // green + ['#33cccc', 'rgba(51, 204, 204, 0.3)'], // indigo + ['#0066ff', 'rgba(0, 102, 255, 0.3)'], // blue + ['#6600ff', 'rgba(102, 0, 255, 0.3)'], // purple + ['#cc00cc', 'rgba(204, 0, 204, 0.3)'], // dark pink + ['#ff6666', 'rgba(255, 102, 102, 0.3)'], // light red + ['#ffcc66', 'rgba(255, 204, 102, 0.3)'], // light orange + ['#99cc00', 'rgba(153, 204, 0, 0.3)'], // lime green + ['#00cc99', 'rgba(0, 204, 153, 0.3)'], // teal + ['#0099cc', 'rgba(0, 153, 204, 0.3)'], // steel blue + ['#9933cc', 'rgba(153, 51, 204, 0.3)'], // lavender + ['#ff3399', 'rgba(255, 51, 153, 0.3)'], // hot pink + ['#996633', 'rgba(153, 102, 51, 0.3)'], // brown +]; + +const RESIZE_BORDER = 5; +const MOVE_BORDER = 5; + +const t2i_bboxes = new Array(BBOX_MAX_NUM).fill(null); +const i2i_bboxes = new Array(BBOX_MAX_NUM).fill(null); + +// ↓↓↓ called from gradio ↓↓↓ + +function onCreateT2IRefClick(overwrite) { + let width, height; + if (overwrite) { + const overwriteInputs = gradioApp().querySelectorAll('#MD-overwrite-width-t2i input, #MD-overwrite-height-t2i input'); + width = parseInt(overwriteInputs[0].value); + height = parseInt(overwriteInputs[2].value); + } else { + const sizeInputs = gradioApp().querySelectorAll('#txt2img_width input, #txt2img_height input'); + width = parseInt(sizeInputs[0].value); + height = parseInt(sizeInputs[2].value); + } + + if (isNaN(width)) width = 512; + if (isNaN(height)) height = 512; + + // Concat it to string to bypass the gradio bug + // 向黑恶势力低头 + return width.toString() + 'x' + height.toString(); +} + +function onCreateI2IRefClick() { + const canvas = gradioApp().querySelector('#img2img_image img'); + return canvas.src; +} + +function onBoxEnableClick(is_t2i, idx, enable) { + let canvas = null; + let bboxes = null; + let locator = null; + if (is_t2i) { + locator = () => gradioApp().querySelector('#MD-bbox-ref-t2i'); + bboxes = t2i_bboxes; + } else { + locator = () => gradioApp().querySelector('#MD-bbox-ref-i2i'); + bboxes = i2i_bboxes; + } + ref_div = locator(); + canvas = ref_div.querySelector('img'); + if (!canvas) { return false; } + + if (enable) { + // Check if the bounding box already exists + if (!bboxes[idx]) { + // Initialize bounding box + const bbox = [DEFAULT_X, DEFAULT_Y, DEFAULT_W, DEFAULT_H]; + const colorMap = COLOR_MAP[idx % COLOR_MAP.length]; + const div = document.createElement('div'); + div.id = 'MD-bbox-' + (is_t2i ? 't2i-' : 'i2i-') + idx; + div.style.left = '0px'; + div.style.top = '0px'; + div.style.width = '0px'; + div.style.height = '0px'; + div.style.position = 'absolute'; + div.style.border = '2px solid ' + colorMap[0]; + div.style.background = colorMap[1]; + div.style.zIndex = '900'; + div.style.display = 'none'; + // A text tip to warn the user if bbox is too large + const tip = document.createElement('span'); + tip.id = 'MD-tip-' + (is_t2i ? 't2i-' : 'i2i-') + idx; + tip.style.left = '50%'; + tip.style.top = '50%'; + tip.style.position = 'absolute'; + tip.style.transform = 'translate(-50%, -50%)'; + tip.style.fontSize = '12px'; + tip.style.fontWeight = 'bold'; + tip.style.textAlign = 'center'; + tip.style.color = colorMap[0]; + tip.style.zIndex = '901'; + tip.style.display = 'none'; + tip.innerHTML = 'Warning: Region very large!
Take care of VRAM usage!'; + div.appendChild(tip); + + div.addEventListener('mousedown', function (e) { + if (e.button === 0) { onBoxMouseDown(e, is_t2i, idx); } + }); + div.addEventListener('mousemove', function (e) { + updateCursorStyle(e, is_t2i, idx); + }); + + const shower = function() { // insert to DOM if necessary + if (!gradioApp().querySelector('#' + div.id)) { + locator().appendChild(div); + } + } + bboxes[idx] = [div, bbox, shower]; + } + + // Show the bounding box + displayBox(canvas, is_t2i, bboxes[idx]); + return true; + } else { + if (!bboxes[idx]) { return false; } + const [div, bbox, shower] = bboxes[idx]; + div.style.display = 'none'; + } + return false; +} + +function onBoxChange(is_t2i, idx, what, v) { + // This function handles all the changes of the bounding box + // Including the rendering and python slider update + let bboxes = null; + let canvas = null; + if (is_t2i) { + bboxes = t2i_bboxes; + canvas = gradioApp().querySelector('#MD-bbox-ref-t2i img'); + } else { + bboxes = i2i_bboxes; + canvas = gradioApp().querySelector('#MD-bbox-ref-i2i img'); + } + if (!bboxes[idx] || !canvas) { + switch (what) { + case 'x': return DEFAULT_X; + case 'y': return DEFAULT_Y; + case 'w': return DEFAULT_W; + case 'h': return DEFAULT_H; + } + } + const [div, bbox, shower] = bboxes[idx]; + if (div.style.display === 'none') { return v; } + + // parse trigger + switch (what) { + case 'x': bbox[0] = v; break; + case 'y': bbox[1] = v; break; + case 'w': bbox[2] = v; break; + case 'h': bbox[3] = v; break; + } + displayBox(canvas, is_t2i, bboxes[idx]); + return v; +} + +// ↓↓↓ called from js ↓↓↓ + +function getSeedInfo(is_t2i, id, current_seed) { + const info_id = is_t2i ? '#html_info_txt2img' : '#html_info_img2img'; + const info_div = gradioApp().querySelector(info_id); + try{ + current_seed = parseInt(current_seed); + } catch(e) { + current_seed = -1; + } + if (!info_div) return current_seed; + let info = info_div.innerHTML; + if (!info) return current_seed; + // remove all html tags + info = info.replace(/<[^>]*>/g, ''); + // Find a json string 'region control:' in the info + // get its index + idx = info.indexOf('Region control'); + if (idx == -1) return current_seed; + // get the json string (detect the bracket) + // find the first '{' + let start_idx = info.indexOf('{', idx); + let bracket = 1; + let end_idx = start_idx + 1; + while (bracket > 0 && end_idx < info.length) { + if (info[end_idx] == '{') bracket++; + if (info[end_idx] == '}') bracket--; + end_idx++; + } + if (bracket > 0) { + return current_seed; + } + // get the json string + let json_str = info.substring(start_idx, end_idx); + // replace the single quote to double quote + json_str = json_str.replace(/'/g, '"'); + // replace python True to javascript true, False to false + json_str = json_str.replace(/True/g, 'true'); + // parse the json string + let json = JSON.parse(json_str); + // get the seed if the region id is in the json + const region_id = 'Region ' + id.toString(); + if (!(region_id in json)) return current_seed; + const region = json[region_id]; + if (!('seed' in region)) return current_seed; + let seed = region['seed']; + try{ + seed = parseInt(seed); + } catch(e) { + return current_seed; + } + return seed; +} + +function displayBox(canvas, is_t2i, bbox_info) { + // check null input + const [div, bbox, shower] = bbox_info; + const [x, y, w, h] = bbox; + if (!canvas || !div || x == null || y == null || w == null || h == null) { return; } + + // client: canvas widget display size + // natural: content image real size + let vpScale = Math.min(canvas.clientWidth / canvas.naturalWidth, canvas.clientHeight / canvas.naturalHeight); + let canvasCenterX = canvas.clientWidth / 2; + let canvasCenterY = canvas.clientHeight / 2; + let scaledX = canvas.naturalWidth * vpScale; + let scaledY = canvas.naturalHeight * vpScale; + let viewRectLeft = canvasCenterX - scaledX / 2; + let viewRectRight = canvasCenterX + scaledX / 2; + let viewRectTop = canvasCenterY - scaledY / 2; + let viewRectDown = canvasCenterY + scaledY / 2; + + let xDiv = viewRectLeft + scaledX * x; + let yDiv = viewRectTop + scaledY * y; + let wDiv = Math.min(scaledX * w, viewRectRight - xDiv); + let hDiv = Math.min(scaledY * h, viewRectDown - yDiv); + + // Calculate warning bbox size + let upscalerFactor = 1.0; + if (!is_t2i) { + const upscalerInput = parseFloat(gradioApp().querySelector('#MD-i2i-upscaler-factor input').value); + if (!isNaN(upscalerInput)) upscalerFactor = upscalerInput; + } + let maxSize = BBOX_WARNING_SIZE / upscalerFactor * vpScale; + let maxW = maxSize / scaledX; + let maxH = maxSize / scaledY; + if (w > maxW || h > maxH) { + div.querySelector('span').style.display = 'block'; + } else { + div.querySelector('span').style.display = 'none'; + } + + // update
when not equal + div.style.left = xDiv + 'px'; + div.style.top = yDiv + 'px'; + div.style.width = wDiv + 'px'; + div.style.height = hDiv + 'px'; + div.style.display = 'block'; + + // insert it to DOM if not appear + shower(); +} + +function onBoxMouseDown(e, is_t2i, idx) { + let bboxes = null; + let canvas = null; + if (is_t2i) { + bboxes = t2i_bboxes; + canvas = gradioApp().querySelector('#MD-bbox-ref-t2i img'); + } else { + bboxes = i2i_bboxes; + canvas = gradioApp().querySelector('#MD-bbox-ref-i2i img'); + } + // Get the bounding box + if (!canvas || !bboxes[idx]) { return; } + const [div, bbox, shower] = bboxes[idx]; + + // Check if the click is inside the bounding box + const boxRect = div.getBoundingClientRect(); + let mouseX = e.clientX; + let mouseY = e.clientY; + + const resizeLeft = mouseX >= boxRect.left && mouseX <= boxRect.left + RESIZE_BORDER; + const resizeRight = mouseX >= boxRect.right - RESIZE_BORDER && mouseX <= boxRect.right; + const resizeTop = mouseY >= boxRect.top && mouseY <= boxRect.top + RESIZE_BORDER; + const resizeBottom = mouseY >= boxRect.bottom - RESIZE_BORDER && mouseY <= boxRect.bottom; + + const moveHorizontal = mouseX >= boxRect.left + MOVE_BORDER && mouseX <= boxRect.right - MOVE_BORDER; + const moveVertical = mouseY >= boxRect.top + MOVE_BORDER && mouseY <= boxRect.bottom - MOVE_BORDER; + + if (!resizeLeft && !resizeRight && !resizeTop && !resizeBottom && !moveHorizontal && !moveVertical) { return; } + + const horizontalPivot = resizeLeft ? bbox[0] + bbox[2] : bbox[0]; + const verticalPivot = resizeTop ? bbox[1] + bbox[3] : bbox[1]; + + // Canvas can be regarded as invariant during the drag operation + // Calculate in advance to reduce overhead + + // Calculate viewport scale based on the current canvas size and the natural image size + let vpScale = Math.min(canvas.clientWidth / canvas.naturalWidth, canvas.clientHeight / canvas.naturalHeight); + let vpOffset = canvas.getBoundingClientRect(); + + // Calculate scaled dimensions of the canvas + let scaledX = canvas.naturalWidth * vpScale; + let scaledY = canvas.naturalHeight * vpScale; + + // Calculate the canvas center and view rectangle coordinates + let canvasCenterX = (vpOffset.left + window.scrollX) + canvas.clientWidth / 2; + let canvasCenterY = (vpOffset.top + window.scrollY) + canvas.clientHeight / 2; + let viewRectLeft = canvasCenterX - scaledX / 2 - window.scrollX; + let viewRectRight = canvasCenterX + scaledX / 2 - window.scrollX; + let viewRectTop = canvasCenterY - scaledY / 2 - window.scrollY; + let viewRectDown = canvasCenterY + scaledY / 2 - window.scrollY; + + mouseX = Math.min(Math.max(mouseX, viewRectLeft), viewRectRight); + mouseY = Math.min(Math.max(mouseY, viewRectTop), viewRectDown); + + const accordion = gradioApp().querySelector(`#MD-accordion-${is_t2i ? 't2i' : 'i2i'}-${idx}`); + + // Move or resize the bounding box on mousemove + function onMouseMove(e) { + // Prevent selecting anything irrelevant + e.preventDefault(); + + // Get the new mouse position + let newMouseX = e.clientX; + let newMouseY = e.clientY; + + // clamp the mouse position to the view rectangle + newMouseX = Math.min(Math.max(newMouseX, viewRectLeft), viewRectRight); + newMouseY = Math.min(Math.max(newMouseY, viewRectTop), viewRectDown); + + // Calculate the mouse movement delta + const dx = (newMouseX - mouseX) / scaledX; + const dy = (newMouseY - mouseY) / scaledY; + + // Update the mouse position + mouseX = newMouseX; + mouseY = newMouseY; + + // if no move just return + if (dx === 0 && dy === 0) { return; } + + // Update the mouse position + let [x, y, w, h] = bbox; + if (moveHorizontal && moveVertical) { + // If moving the bounding box + x = Math.min(Math.max(x + dx, 0), 1 - w); + y = Math.min(Math.max(y + dy, 0), 1 - h); + } else { + // If resizing the bounding box + if (resizeLeft || resizeRight) { + if (x < horizontalPivot) { + if (dx <= w) { + // If still within the left side of the pivot + x = x + dx; + w = w - dx; + } else { + // If crossing the pivot + w = dx - w; + x = horizontalPivot; + } + } else { + if (w + dx < 0) { + // If still within the right side of the pivot + x = horizontalPivot + w + dx; + w = - dx - w; + } else { + // If crossing the pivot + x = horizontalPivot; + w = w + dx; + } + } + + // Clamp the bounding box to the image + if (x < 0) { + w = w + x; + x = 0; + } else if (x + w > 1) { + w = 1 - x; + } + } + // Same as above, but for the vertical axis + if (resizeTop || resizeBottom) { + if (y < verticalPivot) { + if (dy <= h) { + y = y + dy; + h = h - dy; + } else { + h = dy - h; + y = verticalPivot; + } + } else { + if (h + dy < 0) { + y = verticalPivot + h + dy; + h = - dy - h; + } else { + y = verticalPivot; + h = h + dy; + } + } + if (y < 0) { + h = h + y; + y = 0; + } else if (y + h > 1) { + h = 1 - y; + } + } + } + const [div, old_bbox, _] = bboxes[idx]; + + // If all the values are the same, just return + if (old_bbox[0] === x && old_bbox[1] === y && old_bbox[2] === w && old_bbox[3] === h) { return; } + // else update the bbox + const event = new Event('input'); + const coords = [x, y, w, h]; + // The querySelector is not very efficient, so we query it once and reuse it + // caching will result gradio bugs that stucks bbox and cannot move & drag + const sliderIds = ['x', 'y', 'w', 'h']; + // We try to select the input sliders + const sliderSelectors = sliderIds.map(id => `#MD-${is_t2i ? 't2i' : 'i2i'}-${idx}-${id} input`).join(', '); + let sliderInputs = accordion.querySelectorAll(sliderSelectors); + if (sliderInputs.length == 0) { + // If we failed, the accordion is probably closed and sliders are removed in the dom, so we open it + accordion.querySelector('.label-wrap').click(); + // and try again + sliderInputs = accordion.querySelectorAll(sliderSelectors); + // If we still failed, we just return + if (sliderInputs.length == 0) { return; } + } + for (let i = 0; i < 4; i++) { + if (old_bbox[i] !== coords[i]) { + sliderInputs[2*i].value = coords[i]; + sliderInputs[2*i].dispatchEvent(event); + } + } + } + + // Remove the mousemove and mouseup event listeners + function onMouseUp() { + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + } + + // Add the event listeners + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); +} + +function updateCursorStyle(e, is_t2i, idx) { + // This function changes the cursor style when hovering over the bounding box + const bboxes = is_t2i ? t2i_bboxes : i2i_bboxes; + if (!bboxes[idx]) return; + + const div = bboxes[idx][0]; + const boxRect = div.getBoundingClientRect(); + const mouseX = e.clientX; + const mouseY = e.clientY; + + const resizeLeft = mouseX >= boxRect.left && mouseX <= boxRect.left + RESIZE_BORDER; + const resizeRight = mouseX >= boxRect.right - RESIZE_BORDER && mouseX <= boxRect.right; + const resizeTop = mouseY >= boxRect.top && mouseY <= boxRect.top + RESIZE_BORDER; + const resizeBottom = mouseY >= boxRect.bottom - RESIZE_BORDER && mouseY <= boxRect.bottom; + + if ((resizeLeft && resizeTop) || (resizeRight && resizeBottom)) { + div.style.cursor = 'nwse-resize'; + } else if ((resizeLeft && resizeBottom) || (resizeRight && resizeTop)) { + div.style.cursor = 'nesw-resize'; + } else if (resizeLeft || resizeRight) { + div.style.cursor = 'ew-resize'; + } else if (resizeTop || resizeBottom) { + div.style.cursor = 'ns-resize'; + } else { + div.style.cursor = 'move'; + } +} + +// ↓↓↓ auto called event listeners ↓↓↓ + +function updateBoxes(is_t2i) { + // This function redraw all bounding boxes + let bboxes = null; + let canvas = null; + if (is_t2i) { + bboxes = t2i_bboxes; + canvas = gradioApp().querySelector('#MD-bbox-ref-t2i img'); + } else { + bboxes = i2i_bboxes; + canvas = gradioApp().querySelector('#MD-bbox-ref-i2i img'); + } + if (!canvas) return; + + for (let idx = 0; idx < bboxes.length; idx++) { + if (!bboxes[idx]) continue; + const [div, bbox, shower] = bboxes[idx]; + if (div.style.display === 'none') { return; } + + displayBox(canvas, is_t2i, bboxes[idx]); + } +} + +window.addEventListener('resize', _ => { + updateBoxes(true); + updateBoxes(false); +}); + +// ======== Gradio Bug Fix ======== +// For Gradio versions > 3.16.0 and < 3.29.0, the accordion DOM will be deleted when it is closed. +// We need to judge the versions and listen to the accordion open event, rerender the bbox at that time. +// This silly bug fix is only for compatibility, we recommend to update the gradio version to 3.29.0 or higher. +try { + const GRADIO_VERSIONS = window.gradio_config["version"].split("."); + const gradio_major_version = parseInt(GRADIO_VERSIONS[0]); + const gradio_minor_version = parseInt(GRADIO_VERSIONS[1]); + if (gradio_major_version == 3 && gradio_minor_version > 16 && gradio_minor_version < 29) { + let listener = e => { + if (!e) { return; } + if (!e.target) { return; } + if (!e.target.classList) { return; } + if (!e.target.classList.contains('label-wrap')) { return; } + for (let tab of ['t2i', 'i2i']) { + const div = gradioApp().querySelector('#MD-bbox-control-' + tab +' div.label-wrap'); + if (!div) { continue; } + updateBoxes(tab === 't2i'); + } + }; + window.addEventListener('DOMNodeInserted', listener); + } +} catch (ignored) { + // If the above code failed, the gradio version shouldn't be in the range of 3.16.0 to 3.29.0, so we just return. +} +// ======== Gradio Bug Fix ======== diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/__pycache__/tilediffusion.cpython-310.pyc b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/__pycache__/tilediffusion.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8dd6bda272c1fd39c8a87434215b2a50d663bac0 Binary files /dev/null and b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/__pycache__/tilediffusion.cpython-310.pyc differ diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/__pycache__/tileglobal.cpython-310.pyc b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/__pycache__/tileglobal.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf8d2d66c5979e19dbe6d582ce67456879eec164 Binary files /dev/null and b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/__pycache__/tileglobal.cpython-310.pyc differ diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/__pycache__/tilevae.cpython-310.pyc b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/__pycache__/tilevae.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a320ea1d9a54439167b1afe29423a0ef6046f061 Binary files /dev/null and b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/__pycache__/tilevae.cpython-310.pyc differ diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/tilediffusion.py b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/tilediffusion.py new file mode 100644 index 0000000000000000000000000000000000000000..1589946235f822ed1121fc7a5dcb4e947a78b35c --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/tilediffusion.py @@ -0,0 +1,609 @@ +''' +# ------------------------------------------------------------------------ +# +# Tiled Diffusion for Automatic1111 WebUI +# +# Introducing revolutionary large image drawing methods: +# MultiDiffusion and Mixture of Diffusers! +# +# Techniques is not originally proposed by me, please refer to +# +# MultiDiffusion: https://multidiffusion.github.io +# Mixture of Diffusers: https://github.com/albarji/mixture-of-diffusers +# +# The script contains a few optimizations including: +# - symmetric tiling bboxes +# - cached tiling weights +# - batched denoising +# - advanced prompt control for each tile +# +# ------------------------------------------------------------------------ +# +# This script hooks into the original sampler and decomposes the latent +# image, sampled separately and run weighted average to merge them back. +# +# Advantages: +# - Allows for super large resolutions (2k~8k) for both txt2img and img2img. +# - The merged output is completely seamless without any post-processing. +# - Training free. No need to train a new model, and you can control the +# text prompt for specific regions. +# +# Drawbacks: +# - Depending on your parameter settings, the process can be very slow, +# especially when overlap is relatively large. +# - The gradient calculation is not compatible with this hack. It +# will break any backward() or torch.autograd.grad() that passes UNet. +# +# How it works: +# 1. The latent image is split into tiles. +# 2. In MultiDiffusion: +# 1. The UNet predicts the noise of each tile. +# 2. The tiles are denoised by the original sampler for one time step. +# 3. The tiles are added together but divided by how many times each pixel is added. +# 3. In Mixture of Diffusers: +# 1. The UNet predicts the noise of each tile +# 2. All noises are fused with a gaussian weight mask. +# 3. The denoiser denoises the whole image for one time step using fused noises. +# 4. Repeat 2-3 until all timesteps are completed. +# +# Enjoy! +# +# @author: LI YI @ Nanyang Technological University - Singapore +# @date: 2023-03-03 +# @license: CC BY-NC-SA 4.0 +# +# Please give me a star if you like this project! +# +# ------------------------------------------------------------------------ +''' + +import os +import json +import torch +import numpy as np +import gradio as gr + +from modules import sd_samplers, images, shared, devices, processing, scripts +from modules.shared import opts +from modules.processing import opt_f, get_fixed_seed +from modules.ui import gr_show + +from tile_methods.abstractdiffusion import AbstractDiffusion +from tile_methods.multidiffusion import MultiDiffusion +from tile_methods.mixtureofdiffusers import MixtureOfDiffusers +from tile_utils.utils import * +if hasattr(opts, 'hypertile_enable_unet'): # webui >= 1.7 + from modules.ui_components import InputAccordion +else: + InputAccordion = None + +CFG_PATH = os.path.join(scripts.basedir(), 'region_configs') +BBOX_MAX_NUM = min(getattr(shared.cmd_opts, 'md_max_regions', 8), 16) + + +class Script(scripts.Script): + + def __init__(self): + self.controlnet_script: ModuleType = None + self.stablesr_script: ModuleType = None + self.delegate: AbstractDiffusion = None + self.noise_inverse_cache: NoiseInverseCache = None + + def title(self): + return 'Tiled Diffusion' + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + tab = 't2i' if not is_img2img else 'i2i' + is_t2i = 'true' if not is_img2img else 'false' + uid = lambda name: f'MD-{tab}-{name}' + + with ( + InputAccordion(False, label='Tiled Diffusion', elem_id=uid('enabled')) if InputAccordion + else gr.Accordion('Tiled Diffusion', open=False, elem_id=f'MD-{tab}') + as enabled + ): + with gr.Row(variant='compact') as tab_enable: + if not InputAccordion: + enabled = gr.Checkbox(label='Enable Tiled Diffusion', value=False, elem_id=uid('enabled')) + overwrite_size = gr.Checkbox(label='Overwrite image size', value=False, visible=not is_img2img, elem_id=uid('overwrite-image-size')) + keep_input_size = gr.Checkbox(label='Keep input image size', value=True, visible=is_img2img, elem_id=uid('keep-input-size')) + + with gr.Row(variant='compact', visible=False) as tab_size: + image_width = gr.Slider(minimum=256, maximum=16384, step=16, label='Image width', value=1024, elem_id=f'MD-overwrite-width-{tab}') + image_height = gr.Slider(minimum=256, maximum=16384, step=16, label='Image height', value=1024, elem_id=f'MD-overwrite-height-{tab}') + overwrite_size.change(fn=gr_show, inputs=overwrite_size, outputs=tab_size, show_progress=False) + + with gr.Row(variant='compact') as tab_param: + method = gr.Dropdown(label='Method', choices=[e.value for e in Method], value=Method.MULTI_DIFF.value if is_t2i else Method.MIX_DIFF.value, elem_id=uid('method')) + control_tensor_cpu = gr.Checkbox(label='Move ControlNet tensor to CPU (if applicable)', value=False, elem_id=uid('control-tensor-cpu')) + reset_status = gr.Button(value='Free GPU', variant='tool') + reset_status.click(fn=self.reset_and_gc, show_progress=False) + + with gr.Group() as tab_tile: + with gr.Row(variant='compact'): + tile_width = gr.Slider(minimum=16, maximum=256, step=16, label='Latent tile width', value=96, elem_id=uid('latent-tile-width')) + tile_height = gr.Slider(minimum=16, maximum=256, step=16, label='Latent tile height', value=96, elem_id=uid('latent-tile-height')) + + with gr.Row(variant='compact'): + overlap = gr.Slider(minimum=0, maximum=256, step=4, label='Latent tile overlap', value=48 if is_t2i else 8, elem_id=uid('latent-tile-overlap')) + batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Latent tile batch size', value=4, elem_id=uid('latent-tile-batch-size')) + + with gr.Row(variant='compact', visible=is_img2img) as tab_upscale: + upscaler_name = gr.Dropdown(label='Upscaler', choices=[x.name for x in shared.sd_upscalers], value='None', elem_id=uid('upscaler-index')) + scale_factor = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label='Scale Factor', value=2.0, elem_id=uid('upscaler-factor')) + + with gr.Accordion('Noise Inversion', open=True, visible=is_img2img) as tab_noise_inv: + with gr.Row(variant='compact'): + noise_inverse = gr.Checkbox(label='Enable Noise Inversion', value=False, elem_id=uid('noise-inverse')) + noise_inverse_steps = gr.Slider(minimum=1, maximum=200, step=1, label='Inversion steps', value=10, elem_id=uid('noise-inverse-steps')) + gr.HTML('

Please test on small images before actual upscale. Default params require denoise <= 0.6

') + with gr.Row(variant='compact'): + noise_inverse_retouch = gr.Slider(minimum=1, maximum=100, step=0.1, label='Retouch', value=1, elem_id=uid('noise-inverse-retouch')) + noise_inverse_renoise_strength = gr.Slider(minimum=0, maximum=2, step=0.01, label='Renoise strength', value=1, elem_id=uid('noise-inverse-renoise-strength')) + noise_inverse_renoise_kernel = gr.Slider(minimum=2, maximum=512, step=1, label='Renoise kernel size', value=64, elem_id=uid('noise-inverse-renoise-kernel')) + + # The control includes txt2img and img2img, we use t2i and i2i to distinguish them + with gr.Group(elem_id=f'MD-bbox-control-{tab}') as tab_bbox: + with gr.Accordion('Region Prompt Control', open=False): + with gr.Row(variant='compact'): + enable_bbox_control = gr.Checkbox(label='Enable Control', value=False, elem_id=uid('enable-bbox-control')) + draw_background = gr.Checkbox(label='Draw full canvas background', value=False, elem_id=uid('draw-background')) + causal_layers = gr.Checkbox(label='Causalize layers', value=False, visible=False, elem_id='MD-causal-layers') # NOTE: currently not used + + with gr.Row(variant='compact'): + create_button = gr.Button(value="Create txt2img canvas" if not is_img2img else "From img2img", elem_id='MD-create-canvas') + + bbox_controls: List[Component] = [] # control set for each bbox + with gr.Row(variant='compact'): + ref_image = gr.Image(label='Ref image (for conviently locate regions)', image_mode=None, elem_id=f'MD-bbox-ref-{tab}', interactive=True) + if not is_img2img: + # gradio has a serious bug: it cannot accept multiple inputs when you use both js and fn. + # to workaround this, we concat the inputs into a single string and parse it in js + def create_t2i_ref(string): + w, h = [int(x) for x in string.split('x')] + w = max(w, opt_f) + h = max(h, opt_f) + return np.zeros(shape=(h, w, 3), dtype=np.uint8) + 255 + create_button.click( + fn=create_t2i_ref, + inputs=overwrite_size, + outputs=ref_image, + _js='onCreateT2IRefClick', + show_progress=False) + else: + create_button.click(fn=None, outputs=ref_image, _js='onCreateI2IRefClick', show_progress=False) + + with gr.Row(variant='compact'): + cfg_name = gr.Textbox(label='Custom Config File', value='config.json', elem_id=uid('cfg-name')) + cfg_dump = gr.Button(value='💾 Save', variant='tool') + cfg_load = gr.Button(value='⚙️ Load', variant='tool') + + with gr.Row(variant='compact'): + cfg_tip = gr.HTML(value='', visible=False) + + for i in range(BBOX_MAX_NUM): + # Only when displaying & png generate info we use index i+1, in other cases we use i + with gr.Accordion(f'Region {i+1}', open=False, elem_id=f'MD-accordion-{tab}-{i}'): + with gr.Row(variant='compact'): + e = gr.Checkbox(label=f'Enable Region {i+1}', value=False, elem_id=f'MD-bbox-{tab}-{i}-enable') + e.change(fn=None, inputs=e, outputs=e, _js=f'e => onBoxEnableClick({is_t2i}, {i}, e)', show_progress=False) + + blend_mode = gr.Dropdown(label='Type', choices=[e.value for e in BlendMode], value=BlendMode.BACKGROUND.value, elem_id=f'MD-{tab}-{i}-blend-mode') + feather_ratio = gr.Slider(label='Feather', value=0.2, minimum=0, maximum=1, step=0.05, visible=False, elem_id=f'MD-{tab}-{i}-feather') + + blend_mode.change(fn=lambda x: gr_show(x==BlendMode.FOREGROUND.value), inputs=blend_mode, outputs=feather_ratio, show_progress=False) + + with gr.Row(variant='compact'): + x = gr.Slider(label='x', value=0.4, minimum=0.0, maximum=1.0, step=0.0001, elem_id=f'MD-{tab}-{i}-x') + y = gr.Slider(label='y', value=0.4, minimum=0.0, maximum=1.0, step=0.0001, elem_id=f'MD-{tab}-{i}-y') + + with gr.Row(variant='compact'): + w = gr.Slider(label='w', value=0.2, minimum=0.0, maximum=1.0, step=0.0001, elem_id=f'MD-{tab}-{i}-w') + h = gr.Slider(label='h', value=0.2, minimum=0.0, maximum=1.0, step=0.0001, elem_id=f'MD-{tab}-{i}-h') + + x.change(fn=None, inputs=x, outputs=x, _js=f'v => onBoxChange({is_t2i}, {i}, "x", v)', show_progress=False) + y.change(fn=None, inputs=y, outputs=y, _js=f'v => onBoxChange({is_t2i}, {i}, "y", v)', show_progress=False) + w.change(fn=None, inputs=w, outputs=w, _js=f'v => onBoxChange({is_t2i}, {i}, "w", v)', show_progress=False) + h.change(fn=None, inputs=h, outputs=h, _js=f'v => onBoxChange({is_t2i}, {i}, "h", v)', show_progress=False) + + prompt = gr.Text(show_label=False, placeholder=f'Prompt, will append to your {tab} prompt', max_lines=2, elem_id=f'MD-{tab}-{i}-prompt') + neg_prompt = gr.Text(show_label=False, placeholder='Negative Prompt, will also be appended', max_lines=1, elem_id=f'MD-{tab}-{i}-neg-prompt') + with gr.Row(variant='compact'): + seed = gr.Number(label='Seed', value=-1, visible=True, elem_id=f'MD-{tab}-{i}-seed') + random_seed = gr.Button(value='🎲', variant='tool', elem_id=f'MD-{tab}-{i}-random_seed') + reuse_seed = gr.Button(value='♻️', variant='tool', elem_id=f'MD-{tab}-{i}-reuse_seed') + random_seed.click(fn=lambda: -1, outputs=seed, show_progress=False) + reuse_seed.click(fn=None, inputs=seed, outputs=seed, _js=f'e => getSeedInfo({is_t2i}, {i+1}, e)', show_progress=False) + + control = [e, x, y, w, h, prompt, neg_prompt, blend_mode, feather_ratio, seed] + assert len(control) == NUM_BBOX_PARAMS + bbox_controls.extend(control) + + # NOTE: dynamically hard coded!! + load_regions_js = ''' + function onBoxChangeAll(ref_image, cfg_name, ...args) { + const is_t2i = %s; + const n_bbox = %d; + const n_ctrl = %d; + for (let i=0; i 0 + if is_img2img: # img2img, TODO: replace with `images.resize_image()` + idx = [x.name for x in shared.sd_upscalers].index(upscaler_name) + upscaler = shared.sd_upscalers[idx] + init_img = p.init_images[0] + init_img = images.flatten(init_img, opts.img2img_background_color) + if upscaler.name != "None": + print(f"[Tiled Diffusion] upscaling image with {upscaler.name}...") + image = upscaler.scaler.upscale(init_img, scale_factor, upscaler.data_path) + p.extra_generation_params["Tiled Diffusion upscaler"] = upscaler.name + p.extra_generation_params["Tiled Diffusion scale factor"] = scale_factor + # For webui folder based batch processing, the length of init_images is not 1 + # We need to replace all images with the upsampled one + for i in range(len(p.init_images)): + p.init_images[i] = image + else: + image = init_img + + # decide final canvas size + if keep_input_size: + p.width = image.width + p.height = image.height + elif upscaler.name != "None": + p.width = int(scale_factor * p.width_original_md) + p.height = int(scale_factor * p.height_original_md) + elif overwrite_size: # txt2img + p.width = image_width + p.height = image_height + + ''' sanitiy check ''' + chks = [ + splitable(p.width, p.height, tile_width, tile_height, overlap), + enable_bbox_control, + is_img2img and noise_inverse, + ] + if not any(chks): + print("[Tiled Diffusion] ignore tiling when there's only 1 tile or nothing to do :)") + return + + bbox_settings = build_bbox_settings(bbox_control_states) if enable_bbox_control else {} + + if 'png info': + info = {} + p.extra_generation_params["Tiled Diffusion"] = info + + info['Method'] = method + info['Tile tile width'] = tile_width + info['Tile tile height'] = tile_height + info['Tile Overlap'] = overlap + info['Tile batch size'] = tile_batch_size + + if is_img2img: + if upscaler.name != "None": + info['Upscaler'] = upscaler.name + info['Upscale factor'] = scale_factor + if keep_input_size: + info['Keep input size'] = keep_input_size + if noise_inverse: + info['NoiseInv'] = noise_inverse + info['NoiseInv Steps'] = noise_inverse_steps + info['NoiseInv Retouch'] = noise_inverse_retouch + info['NoiseInv Renoise strength'] = noise_inverse_renoise_strength + info['NoiseInv Kernel size'] = noise_inverse_renoise_kernel + + ''' ControlNet hackin ''' + try: + from scripts.cldm import ControlNet + + for script in p.scripts.scripts + p.scripts.alwayson_scripts: + if hasattr(script, "latest_network") and script.title().lower() == "controlnet": + self.controlnet_script = script + print("[Tiled Diffusion] ControlNet found, support is enabled.") + break + except ImportError: + pass + + ''' StableSR hackin ''' + for script in p.scripts.scripts: + if hasattr(script, "stablesr_model") and script.title().lower() == "stablesr": + if script.stablesr_model is not None: + self.stablesr_script = script + print("[Tiled Diffusion] StableSR found, support is enabled.") + break + + ''' hijack inner APIs, see unhijack in reset() ''' + Script.create_sampler_original_md = sd_samplers.create_sampler + sd_samplers.create_sampler = lambda name, model: self.create_sampler_hijack( + name, model, p, Method(method), + tile_width, tile_height, overlap, tile_batch_size, + noise_inverse, noise_inverse_steps, noise_inverse_retouch, + noise_inverse_renoise_strength, noise_inverse_renoise_kernel, + control_tensor_cpu, + enable_bbox_control, draw_background, causal_layers, + bbox_settings, + ) + + if enable_bbox_control: + region_info = { f'Region {i+1}': v._asdict() for i, v in bbox_settings.items() } + info["Region control"] = region_info + Script.create_random_tensors_original_md = processing.create_random_tensors + processing.create_random_tensors = lambda *args, **kwargs: self.create_random_tensors_hijack( + bbox_settings, region_info, + *args, **kwargs, + ) + + def postprocess_batch(self, p: Processing, enabled, *args, **kwargs): + if not enabled: return + + if self.delegate is not None: self.delegate.reset_controlnet_tensors() + + def postprocess(self, p: Processing, processed, enabled, *args): + if not enabled: return + + # unhijack & unhook + self.reset() + + # restore canvas size settings + if hasattr(p, 'init_images') and hasattr(p, 'init_images_original_md'): + p.init_images.clear() # NOTE: do NOT change the list object, compatible with shallow copy of XYZ-plot + p.init_images.extend(p.init_images_original_md) + del p.init_images_original_md + p.width = p.width_original_md ; del p.width_original_md + p.height = p.height_original_md ; del p.height_original_md + + # clean up noise inverse latent for folder-based processing + if hasattr(p, 'noise_inverse_latent'): + del p.noise_inverse_latent + + ''' ↓↓↓ inner API hijack ↓↓↓ ''' + + def create_sampler_hijack( + self, name: str, model: LatentDiffusion, p: Processing, method: Method, + tile_width: int, tile_height: int, overlap: int, tile_batch_size: int, + noise_inverse: bool, noise_inverse_steps: int, noise_inverse_retouch:float, + noise_inverse_renoise_strength: float, noise_inverse_renoise_kernel: int, + control_tensor_cpu: bool, + enable_bbox_control: bool, draw_background: bool, causal_layers: bool, + bbox_settings: Dict[int, BBoxSettings] + ): + + if self.delegate is not None: + # samplers are stateless, we reuse it if possible + if self.delegate.sampler_name == name: + # before we reuse the sampler, we refresh the control tensor + # so that we are compatible with ControlNet batch processing + if self.controlnet_script: + self.delegate.prepare_controlnet_tensors(refresh=True) + return self.delegate.sampler_raw + else: + self.reset() + + flag_noise_inverse = hasattr(p, "init_images") and len(p.init_images) > 0 and noise_inverse + if flag_noise_inverse: + print('warn: noise inversion only supports the "Euler" sampler, switch to it sliently...') + name = 'Euler' + p.sampler_name = 'Euler' + if name is None: print('>> name is empty') + if model is None: print('>> model is empty') + sampler = Script.create_sampler_original_md(name, model) + if method == Method.MULTI_DIFF: delegate_cls = MultiDiffusion + elif method == Method.MIX_DIFF: delegate_cls = MixtureOfDiffusers + else: raise NotImplementedError(f"Method {method} not implemented.") + + # delegate hacks into the `sampler` with context of `p` + delegate = delegate_cls(p, sampler) + + # setup **optional** supports through `init_*`, make everything relatively pluggable!! + if flag_noise_inverse: + get_cache_callback = self.noise_inverse_get_cache + set_cache_callback = lambda x0, xt, prompts: self.noise_inverse_set_cache(p, x0, xt, prompts, noise_inverse_steps, noise_inverse_retouch) + delegate.init_noise_inverse(noise_inverse_steps, noise_inverse_retouch, get_cache_callback, set_cache_callback, noise_inverse_renoise_strength, noise_inverse_renoise_kernel) + if not enable_bbox_control or draw_background: + delegate.init_grid_bbox(tile_width, tile_height, overlap, tile_batch_size) + if enable_bbox_control: + delegate.init_custom_bbox(bbox_settings, draw_background, causal_layers) + if self.controlnet_script: + delegate.init_controlnet(self.controlnet_script, control_tensor_cpu) + if self.stablesr_script: + delegate.init_stablesr(self.stablesr_script) + + # init everything done, perform sanity check & pre-computations + delegate.init_done() + # hijack the behaviours + delegate.hook() + + self.delegate = delegate + + info = ', '.join([ + f"{method.value} hooked into {name!r} sampler", + f"Tile size: {delegate.tile_h}x{delegate.tile_w}", + f"Tile count: {delegate.num_tiles}", + f"Batch size: {delegate.tile_bs}", + f"Tile batches: {len(delegate.batched_bboxes)}", + ]) + exts = [ + "NoiseInv" if flag_noise_inverse else None, + "RegionCtrl" if enable_bbox_control else None, + "ContrlNet" if self.controlnet_script else None, + "StableSR" if self.stablesr_script else None, + ] + ext_info = ', '.join([e for e in exts if e]) + if ext_info: ext_info = f' (ext: {ext_info})' + print(info + ext_info) + + return delegate.sampler_raw + + def create_random_tensors_hijack( + self, bbox_settings: Dict, region_info: Dict, + shape, seeds, subseeds=None, subseed_strength=0.0, seed_resize_from_h=0, seed_resize_from_w=0, p=None, + ): + org_random_tensors = Script.create_random_tensors_original_md(shape, seeds, subseeds, subseed_strength, seed_resize_from_h, seed_resize_from_w, p) + height, width = shape[1], shape[2] + background_noise = torch.zeros_like(org_random_tensors) + background_noise_count = torch.zeros((1, 1, height, width), device=org_random_tensors.device) + foreground_noise = torch.zeros_like(org_random_tensors) + foreground_noise_count = torch.zeros((1, 1, height, width), device=org_random_tensors.device) + + for i, v in bbox_settings.items(): + seed = get_fixed_seed(v.seed) + x, y, w, h = v.x, v.y, v.w, v.h + # convert to pixel + x = int(x * width) + y = int(y * height) + w = math.ceil(w * width) + h = math.ceil(h * height) + # clamp + x = max(0, x) + y = max(0, y) + w = min(width - x, w) + h = min(height - y, h) + # create random tensor + torch.manual_seed(seed) + rand_tensor = torch.randn((1, org_random_tensors.shape[1], h, w), device=devices.cpu) + if BlendMode(v.blend_mode) == BlendMode.BACKGROUND: + background_noise [:, :, y:y+h, x:x+w] += rand_tensor.to(background_noise.device) + background_noise_count[:, :, y:y+h, x:x+w] += 1 + elif BlendMode(v.blend_mode) == BlendMode.FOREGROUND: + foreground_noise [:, :, y:y+h, x:x+w] += rand_tensor.to(foreground_noise.device) + foreground_noise_count[:, :, y:y+h, x:x+w] += 1 + else: + raise NotImplementedError + region_info['Region ' + str(i+1)]['seed'] = seed + + # average + background_noise = torch.where(background_noise_count > 1, background_noise / background_noise_count, background_noise) + foreground_noise = torch.where(foreground_noise_count > 1, foreground_noise / foreground_noise_count, foreground_noise) + # paste two layers to original random tensor + org_random_tensors = torch.where(background_noise_count > 0, background_noise, org_random_tensors) + org_random_tensors = torch.where(foreground_noise_count > 0, foreground_noise, org_random_tensors) + return org_random_tensors + + ''' ↓↓↓ helper methods ↓↓↓ ''' + + def dump_regions(self, cfg_name, *bbox_controls): + if not cfg_name: return gr_value(f'Config file name cannot be empty.', visible=True) + + bbox_settings = build_bbox_settings(bbox_controls) + data = {'bbox_controls': [v._asdict() for v in bbox_settings.values()]} + + if not os.path.exists(CFG_PATH): os.makedirs(CFG_PATH) + fp = os.path.join(CFG_PATH, cfg_name) + with open(fp, 'w', encoding='utf-8') as fh: + json.dump(data, fh, indent=2, ensure_ascii=False) + + return gr_value(f'Config saved to {fp}.', visible=True) + + def load_regions(self, ref_image, cfg_name, *bbox_controls): + if ref_image is None: + return [gr_value(v) for v in bbox_controls] + [gr_value(f'Please create or upload a ref image first.', visible=True)] + fp = os.path.join(CFG_PATH, cfg_name) + if not os.path.exists(fp): + return [gr_value(v) for v in bbox_controls] + [gr_value(f'Config {fp} not found.', visible=True)] + + try: + with open(fp, 'r', encoding='utf-8') as fh: + data = json.load(fh) + except Exception as e: + return [gr_value(v) for v in bbox_controls] + [gr_value(f'Failed to load config {fp}: {e}', visible=True)] + + num_boxes = len(data['bbox_controls']) + data_list = [] + for i in range(BBOX_MAX_NUM): + if i < num_boxes: + for k in BBoxSettings._fields: + if k in data['bbox_controls'][i]: + data_list.append(data['bbox_controls'][i][k]) + else: + data_list.append(None) + else: + data_list.extend(DEFAULT_BBOX_SETTINGS) + + return [gr_value(v) for v in data_list] + [gr_value(f'Config loaded from {fp}.', visible=True)] + + def noise_inverse_set_cache(self, p: ProcessingImg2Img, x0: Tensor, xt: Tensor, prompts: List[str], steps: int, retouch:float): + self.noise_inverse_cache = NoiseInverseCache(p.sd_model.sd_model_hash, x0, xt, steps, retouch, prompts) + + def noise_inverse_get_cache(self): + return self.noise_inverse_cache + + def reset(self): + ''' unhijack inner APIs, see hijack in process() ''' + if hasattr(Script, "create_sampler_original_md"): + sd_samplers.create_sampler = Script.create_sampler_original_md + del Script.create_sampler_original_md + if hasattr(Script, "create_random_tensors_original_md"): + processing.create_random_tensors = Script.create_random_tensors_original_md + del Script.create_random_tensors_original_md + MultiDiffusion .unhook() + MixtureOfDiffusers.unhook() + self.delegate = None + + def reset_and_gc(self): + self.reset() + self.noise_inverse_cache = None + + import gc; gc.collect() + devices.torch_gc() + + try: + import os + import psutil + mem = psutil.Process(os.getpid()).memory_info() + print(f'[Mem] rss: {mem.rss/2**30:.3f} GB, vms: {mem.vms/2**30:.3f} GB') + from modules.shared import mem_mon as vram_mon + from modules.memmon import MemUsageMonitor + vram_mon: MemUsageMonitor + free, total = vram_mon.cuda_mem_get_info() + print(f'[VRAM] free: {free/2**30:.3f} GB, total: {total/2**30:.3f} GB') + except: + pass diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/tileglobal.py b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/tileglobal.py new file mode 100644 index 0000000000000000000000000000000000000000..f1e0fe8e379ff547af4f372ad31245d88c45af06 --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/tileglobal.py @@ -0,0 +1,566 @@ +import os +import json +import torch +import torch.nn.functional as F +import numpy as np +import gradio as gr + +from modules import sd_samplers, images, shared, devices, processing, scripts, sd_samplers_common, rng +from modules.shared import opts +from modules.processing import opt_f, get_fixed_seed +from modules.ui import gr_show + +from tile_methods.abstractdiffusion import AbstractDiffusion +from tile_methods.demofusion import DemoFusion +from tile_utils.utils import * +from modules.sd_samplers_common import InterruptedException +# import k_diffusion.sampling +if hasattr(opts, 'hypertile_enable_unet'): # webui >= 1.7 + from modules.ui_components import InputAccordion +else: + InputAccordion = None + + +CFG_PATH = os.path.join(scripts.basedir(), 'region_configs') +BBOX_MAX_NUM = min(getattr(shared.cmd_opts, 'md_max_regions', 8), 16) + + +def create_infotext_hijack(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, use_main_prompt=False, index=-1, all_negative_prompts=None): + idx = index + if index == -1: + idx = None + text = processing.create_infotext_ori(p, all_prompts, all_seeds, all_subseeds, comments, iteration, position_in_batch, use_main_prompt, idx, all_negative_prompts) + start_index = text.find("Size") + if start_index != -1: + r_text = f"Size:{p.width_list[index]}x{p.height_list[index]}" + end_index = text.find(",", start_index) + if end_index != -1: + replaced_string = text[:start_index] + r_text + text[end_index:] + return replaced_string + return text + +class Script(scripts.Script): + def __init__(self): + self.controlnet_script: ModuleType = None + self.stablesr_script: ModuleType = None + self.delegate: AbstractDiffusion = None + self.noise_inverse_cache: NoiseInverseCache = None + + def title(self): + return 'demofusion' + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + ext_id = 'demofusion' + tab = f'{ext_id}-t2i' if not is_img2img else f'{ext_id}-i2i' + is_t2i = 'true' if not is_img2img else 'false' + uid = lambda name: f'MD-{tab}-{name}' + + with ( + InputAccordion(False, label='DemoFusion', elem_id=uid('enabled')) if InputAccordion + else gr.Accordion('DemoFusion', open=False, elem_id=f'MD-{tab}') + as enabled + ): + with gr.Row(variant='compact') as tab_enable: + if not InputAccordion: + enabled = gr.Checkbox(label='Enable DemoFusion(Dont open with tilediffusion)', value=False, elem_id=uid('enabled')) + else: + gr.Markdown('(Dont open with tilediffusion)') + random_jitter = gr.Checkbox(label='Random Jitter', value = True, elem_id=uid('random-jitter')) + keep_input_size = gr.Checkbox(label='Keep input-image size', value=False,visible=is_img2img, elem_id=uid('keep-input-size')) + mixture_mode = gr.Checkbox(label='Mixture mode', value=False,elem_id=uid('mixture-mode')) + + gaussian_filter = gr.Checkbox(label='Gaussian Filter', value=True, visible=False, elem_id=uid('gaussian')) + + + with gr.Row(variant='compact') as tab_param: + method = gr.Dropdown(label='Method', choices=[Method_2.DEMO_FU.value], value=Method_2.DEMO_FU.value, visible= False, elem_id=uid('method')) + control_tensor_cpu = gr.Checkbox(label='Move ControlNet tensor to CPU (if applicable)', value=False, elem_id=uid('control-tensor-cpu')) + reset_status = gr.Button(value='Free GPU', variant='tool') + reset_status.click(fn=self.reset_and_gc, show_progress=False) + + with gr.Group() as tab_tile: + with gr.Row(variant='compact'): + window_size = gr.Slider(minimum=16, maximum=256, step=16, label='Latent window size', value=128, elem_id=uid('latent-window-size')) + + with gr.Row(variant='compact'): + overlap = gr.Slider(minimum=0, maximum=256, step=4, label='Latent window overlap', value=64, elem_id=uid('latent-tile-overlap')) + batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Latent window batch size', value=4, elem_id=uid('latent-tile-batch-size')) + batch_size_g = gr.Slider(minimum=1, maximum=8, step=1, label='Global window batch size', value=4, elem_id=uid('Global-tile-batch-size')) + with gr.Row(variant='compact', visible=True) as tab_c: + c1 = gr.Slider(minimum=0, maximum=5, step=0.01, label='Cosine Scale 1', value=3, elem_id=f'C1-{tab}') + c2 = gr.Slider(minimum=0, maximum=5, step=0.01, label='Cosine Scale 2', value=1, elem_id=f'C2-{tab}') + c3 = gr.Slider(minimum=0, maximum=5, step=0.01, label='Cosine Scale 3', value=1, elem_id=f'C3-{tab}') + sigma = gr.Slider(minimum=0, maximum=2, step=0.01, label='Sigma', value=0.6, elem_id=f'Sigma-{tab}') + with gr.Group() as tab_denoise: + strength = gr.Slider(minimum=0, maximum=1, step=0.01, value = 0.85,label='Denoising Strength for Substage',visible=not is_img2img, elem_id=f'strength-{tab}') + with gr.Row(variant='compact') as tab_upscale: + scale_factor = gr.Slider(minimum=1.0, maximum=8.0, step=1, label='Scale Factor', value=2.0, elem_id=uid('upscaler-factor')) + + + with gr.Accordion('Noise Inversion', open=True, visible=is_img2img) as tab_noise_inv: + with gr.Row(variant='compact'): + noise_inverse = gr.Checkbox(label='Enable Noise Inversion', value=False, elem_id=uid('noise-inverse')) + noise_inverse_steps = gr.Slider(minimum=1, maximum=200, step=1, label='Inversion steps', value=10, elem_id=uid('noise-inverse-steps')) + gr.HTML('

Please test on small images before actual upscale. Default params require denoise <= 0.6

') + with gr.Row(variant='compact'): + noise_inverse_retouch = gr.Slider(minimum=1, maximum=100, step=0.1, label='Retouch', value=1, elem_id=uid('noise-inverse-retouch')) + noise_inverse_renoise_strength = gr.Slider(minimum=0, maximum=2, step=0.01, label='Renoise strength', value=1, elem_id=uid('noise-inverse-renoise-strength')) + noise_inverse_renoise_kernel = gr.Slider(minimum=2, maximum=512, step=1, label='Renoise kernel size', value=64, elem_id=uid('noise-inverse-renoise-kernel')) + + # The control includes txt2img and img2img, we use t2i and i2i to distinguish them + + return [ + enabled, method, + keep_input_size, + window_size, overlap, batch_size, + scale_factor, + noise_inverse, noise_inverse_steps, noise_inverse_retouch, noise_inverse_renoise_strength, noise_inverse_renoise_kernel, + control_tensor_cpu, + random_jitter, + c1,c2,c3,gaussian_filter,strength,sigma,batch_size_g,mixture_mode + ] + + + def process(self, p: Processing, + enabled: bool, method: str, + keep_input_size: bool, + window_size:int, overlap: int, tile_batch_size: int, + scale_factor: float, + noise_inverse: bool, noise_inverse_steps: int, noise_inverse_retouch: float, noise_inverse_renoise_strength: float, noise_inverse_renoise_kernel: int, + control_tensor_cpu: bool, + random_jitter:bool, + c1,c2,c3,gaussian_filter,strength,sigma,batch_size_g,mixture_mode + ): + + # unhijack & unhook, in case it broke at last time + self.reset() + p.mixture = mixture_mode + if not mixture_mode: + sigma = sigma/2 + if not enabled: return + + ''' upscale ''' + # store canvas size settings + if hasattr(p, "init_images"): + p.init_images_original_md = [img.copy() for img in p.init_images] + p.width_original_md = p.width + p.height_original_md = p.height + p.current_scale_num = 1 + p.gaussian_filter = gaussian_filter + p.scale_factor = int(scale_factor) + + is_img2img = hasattr(p, "init_images") and len(p.init_images) > 0 + if is_img2img: + init_img = p.init_images[0] + init_img = images.flatten(init_img, opts.img2img_background_color) + image = init_img + if keep_input_size: + p.width = image.width + p.height = image.height + p.width_original_md = p.width + p.height_original_md = p.height + else: #XXX:To adapt to noise inversion, we do not multiply the scale factor here + p.width = p.width_original_md + p.height = p.height_original_md + else: # txt2img + p.width = p.width_original_md + p.height = p.height_original_md + + if 'png info': + info = {} + p.extra_generation_params["Tiled Diffusion"] = info + + info['Method'] = method + info['Window Size'] = window_size + info['Tile Overlap'] = overlap + info['Tile batch size'] = tile_batch_size + info["Global batch size"] = batch_size_g + + if is_img2img: + info['Upscale factor'] = scale_factor + if keep_input_size: + info['Keep input size'] = keep_input_size + if noise_inverse: + info['NoiseInv'] = noise_inverse + info['NoiseInv Steps'] = noise_inverse_steps + info['NoiseInv Retouch'] = noise_inverse_retouch + info['NoiseInv Renoise strength'] = noise_inverse_renoise_strength + info['NoiseInv Kernel size'] = noise_inverse_renoise_kernel + + ''' ControlNet hackin ''' + try: + from scripts.cldm import ControlNet + + for script in p.scripts.scripts + p.scripts.alwayson_scripts: + if hasattr(script, "latest_network") and script.title().lower() == "controlnet": + self.controlnet_script = script + print("[Demo Fusion] ControlNet found, support is enabled.") + break + except ImportError: + pass + + ''' StableSR hackin ''' + for script in p.scripts.scripts: + if hasattr(script, "stablesr_model") and script.title().lower() == "stablesr": + if script.stablesr_model is not None: + self.stablesr_script = script + print("[Demo Fusion] StableSR found, support is enabled.") + break + + ''' hijack inner APIs, see unhijack in reset() ''' + Script.create_sampler_original_md = sd_samplers.create_sampler + + sd_samplers.create_sampler = lambda name, model: self.create_sampler_hijack( + name, model, p, Method_2(method), control_tensor_cpu,window_size, noise_inverse, noise_inverse_steps, noise_inverse_retouch, + noise_inverse_renoise_strength, noise_inverse_renoise_kernel, overlap, tile_batch_size,random_jitter,batch_size_g + ) + + + p.sample = lambda conditioning, unconditional_conditioning,seeds, subseeds, subseed_strength, prompts: self.sample_hijack( + conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts,p, is_img2img, + window_size, overlap, tile_batch_size,random_jitter,c1,c2,c3,strength,sigma,batch_size_g) + + processing.create_infotext_ori = processing.create_infotext + + p.width_list = [p.height] + p.height_list = [p.height] + + processing.create_infotext = create_infotext_hijack + ## end + + + def postprocess_batch(self, p: Processing, enabled, *args, **kwargs): + if not enabled: return + + if self.delegate is not None: self.delegate.reset_controlnet_tensors() + + def postprocess_batch_list(self, p, pp, enabled, *args, **kwargs): + if not enabled: return + for idx,image in enumerate(pp.images): + idx_b = idx//p.batch_size + pp.images[idx] = image[:,:image.shape[1]//(p.scale_factor)*(idx_b+1),:image.shape[2]//(p.scale_factor)*(idx_b+1)] + p.seeds = [item for _ in range(p.scale_factor) for item in p.seeds] + p.prompts = [item for _ in range(p.scale_factor) for item in p.prompts] + p.all_negative_prompts = [item for _ in range(p.scale_factor) for item in p.all_negative_prompts] + p.negative_prompts = [item for _ in range(p.scale_factor) for item in p.negative_prompts] + if p.color_corrections != None: + p.color_corrections = [item for _ in range(p.scale_factor) for item in p.color_corrections] + p.width_list = [item*(idx+1) for idx in range(p.scale_factor) for item in [p.width for _ in range(p.batch_size)]] + p.height_list = [item*(idx+1) for idx in range(p.scale_factor) for item in [p.height for _ in range(p.batch_size)]] + return + + def postprocess(self, p: Processing, processed, enabled, *args): + if not enabled: return + # unhijack & unhook + self.reset() + + # restore canvas size settings + if hasattr(p, 'init_images') and hasattr(p, 'init_images_original_md'): + p.init_images.clear() # NOTE: do NOT change the list object, compatible with shallow copy of XYZ-plot + p.init_images.extend(p.init_images_original_md) + del p.init_images_original_md + p.width = p.width_original_md ; del p.width_original_md + p.height = p.height_original_md ; del p.height_original_md + + # clean up noise inverse latent for folder-based processing + if hasattr(p, 'noise_inverse_latent'): + del p.noise_inverse_latent + + ''' ↓↓↓ inner API hijack ↓↓↓ ''' + @torch.no_grad() + def sample_hijack(self, conditioning, unconditional_conditioning,seeds, subseeds, subseed_strength, prompts,p,image_ori,window_size, overlap, tile_batch_size,random_jitter,c1,c2,c3,strength,sigma,batch_size_g): + ################################################## Phase Initialization ###################################################### + + if not image_ori: + p.current_step = 0 + p.denoising_strength = strength + # p.sampler = sd_samplers.create_sampler(p.sampler_name, p.sd_model) #NOTE:Wrong but very useful. If corrected, please replace with the content with the following lines + # latents = p.rng.next() + + p.sampler = Script.create_sampler_original_md(p.sampler_name, p.sd_model) #scale + x = p.rng.next() + print("### Phase 1 Denoising ###") + latents = p.sampler.sample(p, x, conditioning, unconditional_conditioning, image_conditioning=p.txt2img_image_conditioning(x)) + latents_ = F.pad(latents, (0, latents.shape[3]*(p.scale_factor-1), 0, latents.shape[2]*(p.scale_factor-1))) + res = latents_ + del x + p.sampler = sd_samplers.create_sampler(p.sampler_name, p.sd_model) + starting_scale = 2 + else: # img2img + print("### Encoding Real Image ###") + latents = p.init_latent + starting_scale = 1 + + + anchor_mean = latents.mean() + anchor_std = latents.std() + + devices.torch_gc() + + ####################################################### Phase Upscaling ##################################################### + p.cosine_scale_1 = c1 + p.cosine_scale_2 = c2 + p.cosine_scale_3 = c3 + self.delegate.sig = sigma + p.latents = latents + for current_scale_num in range(starting_scale, p.scale_factor+1): + p.current_scale_num = current_scale_num + print("### Phase {} Denoising ###".format(current_scale_num)) + p.current_height = p.height_original_md * current_scale_num + p.current_width = p.width_original_md * current_scale_num + + + p.latents = F.interpolate(p.latents, size=(int(p.current_height / opt_f), int(p.current_width / opt_f)), mode='bicubic') + p.rng = rng.ImageRNG(p.latents.shape[1:], p.seeds, subseeds=p.subseeds, subseed_strength=p.subseed_strength, seed_resize_from_h=p.seed_resize_from_h, seed_resize_from_w=p.seed_resize_from_w) + + + self.delegate.w = int(p.current_width / opt_f) + self.delegate.h = int(p.current_height / opt_f) + self.delegate.get_views(overlap, tile_batch_size,batch_size_g) + + info = ', '.join([ + # f"{method.value} hooked into {name!r} sampler", + f"Tile size: {self.delegate.window_size}", + f"Tile count: {self.delegate.num_tiles}", + f"Batch size: {self.delegate.tile_bs}", + f"Tile batches: {len(self.delegate.batched_bboxes)}", + f"Global batch size: {self.delegate.global_tile_bs}", + f"Global batches: {len(self.delegate.global_batched_bboxes)}", + ]) + + print(info) + + noise = p.rng.next() + if hasattr(p,'initial_noise_multiplier'): + if p.initial_noise_multiplier != 1.0: + p.extra_generation_params["Noise multiplier"] = p.initial_noise_multiplier + noise *= p.initial_noise_multiplier + else: + p.image_conditioning = p.txt2img_image_conditioning(noise) + + p.noise = noise + p.x = p.latents.clone() + p.current_step=0 + + p.latents = p.sampler.sample_img2img(p,p.latents, noise , conditioning, unconditional_conditioning, image_conditioning=p.image_conditioning) + if self.flag_noise_inverse: + self.delegate.sampler_raw.sample_img2img = self.delegate.sample_img2img_original + self.flag_noise_inverse = False + + p.latents = (p.latents - p.latents.mean()) / p.latents.std() * anchor_std + anchor_mean + latents_ = F.pad(p.latents, (0, p.latents.shape[3]//current_scale_num*(p.scale_factor-current_scale_num), 0, p.latents.shape[2]//current_scale_num*(p.scale_factor-current_scale_num))) + if current_scale_num==1: + res = latents_ + else: + res = torch.concatenate((res,latents_),axis=0) + + ######################################################################################################################################### + + return res + + @staticmethod + def callback_hijack(self_sampler,d,p): + p.current_step = d['i'] + + if self_sampler.stop_at is not None and p.current_step > self_sampler.stop_at: + raise InterruptedException + + state.sampling_step = p.current_step + shared.total_tqdm.update() + p.current_step += 1 + + + def create_sampler_hijack( + self, name: str, model: LatentDiffusion, p: Processing, method: Method_2, control_tensor_cpu:bool,window_size, noise_inverse: bool, noise_inverse_steps: int, noise_inverse_retouch:float, + noise_inverse_renoise_strength: float, noise_inverse_renoise_kernel: int, overlap:int, tile_batch_size:int, random_jitter:bool,batch_size_g:int + ): + if self.delegate is not None: + # samplers are stateless, we reuse it if possible + if self.delegate.sampler_name == name: + # before we reuse the sampler, we refresh the control tensor + # so that we are compatible with ControlNet batch processing + if self.controlnet_script: + self.delegate.prepare_controlnet_tensors(refresh=True) + return self.delegate.sampler_raw + else: + self.reset() + sd_samplers_common.Sampler.callback_ori = sd_samplers_common.Sampler.callback_state + sd_samplers_common.Sampler.callback_state = lambda self_sampler,d:Script.callback_hijack(self_sampler,d,p) + + self.flag_noise_inverse = hasattr(p, "init_images") and len(p.init_images) > 0 and noise_inverse + flag_noise_inverse = self.flag_noise_inverse + if flag_noise_inverse: + print('warn: noise inversion only supports the "Euler" sampler, switch to it sliently...') + name = 'Euler' + p.sampler_name = 'Euler' + if name is None: print('>> name is empty') + if model is None: print('>> model is empty') + sampler = Script.create_sampler_original_md(name, model) + if method ==Method_2.DEMO_FU: delegate_cls = DemoFusion + else: raise NotImplementedError(f"Method {method} not implemented.") + + delegate = delegate_cls(p, sampler) + delegate.window_size = min(min(window_size,p.width//8),p.height//8) + p.random_jitter = random_jitter + + if flag_noise_inverse: + get_cache_callback = self.noise_inverse_get_cache + set_cache_callback = lambda x0, xt, prompts: self.noise_inverse_set_cache(p, x0, xt, prompts, noise_inverse_steps, noise_inverse_retouch) + delegate.init_noise_inverse(noise_inverse_steps, noise_inverse_retouch, get_cache_callback, set_cache_callback, noise_inverse_renoise_strength, noise_inverse_renoise_kernel) + + # delegate.get_views(overlap,tile_batch_size,batch_size_g) + if self.controlnet_script: + delegate.init_controlnet(self.controlnet_script, control_tensor_cpu) + if self.stablesr_script: + delegate.init_stablesr(self.stablesr_script) + + # init everything done, perform sanity check & pre-computations + # hijack the behaviours + delegate.hook() + + self.delegate = delegate + + exts = [ + "ContrlNet" if self.controlnet_script else None, + "StableSR" if self.stablesr_script else None, + ] + ext_info = ', '.join([e for e in exts if e]) + if ext_info: ext_info = f' (ext: {ext_info})' + print(ext_info) + + return delegate.sampler_raw + + def create_random_tensors_hijack( + self, bbox_settings: Dict, region_info: Dict, + shape, seeds, subseeds=None, subseed_strength=0.0, seed_resize_from_h=0, seed_resize_from_w=0, p=None, + ): + org_random_tensors = Script.create_random_tensors_original_md(shape, seeds, subseeds, subseed_strength, seed_resize_from_h, seed_resize_from_w, p) + height, width = shape[1], shape[2] + background_noise = torch.zeros_like(org_random_tensors) + background_noise_count = torch.zeros((1, 1, height, width), device=org_random_tensors.device) + foreground_noise = torch.zeros_like(org_random_tensors) + foreground_noise_count = torch.zeros((1, 1, height, width), device=org_random_tensors.device) + + for i, v in bbox_settings.items(): + seed = get_fixed_seed(v.seed) + x, y, w, h = v.x, v.y, v.w, v.h + # convert to pixel + x = int(x * width) + y = int(y * height) + w = math.ceil(w * width) + h = math.ceil(h * height) + # clamp + x = max(0, x) + y = max(0, y) + w = min(width - x, w) + h = min(height - y, h) + # create random tensor + torch.manual_seed(seed) + rand_tensor = torch.randn((1, org_random_tensors.shape[1], h, w), device=devices.cpu) + if BlendMode(v.blend_mode) == BlendMode.BACKGROUND: + background_noise [:, :, y:y+h, x:x+w] += rand_tensor.to(background_noise.device) + background_noise_count[:, :, y:y+h, x:x+w] += 1 + elif BlendMode(v.blend_mode) == BlendMode.FOREGROUND: + foreground_noise [:, :, y:y+h, x:x+w] += rand_tensor.to(foreground_noise.device) + foreground_noise_count[:, :, y:y+h, x:x+w] += 1 + else: + raise NotImplementedError + region_info['Region ' + str(i+1)]['seed'] = seed + + # average + background_noise = torch.where(background_noise_count > 1, background_noise / background_noise_count, background_noise) + foreground_noise = torch.where(foreground_noise_count > 1, foreground_noise / foreground_noise_count, foreground_noise) + # paste two layers to original random tensor + org_random_tensors = torch.where(background_noise_count > 0, background_noise, org_random_tensors) + org_random_tensors = torch.where(foreground_noise_count > 0, foreground_noise, org_random_tensors) + return org_random_tensors + + ''' ↓↓↓ helper methods ↓↓↓ ''' + + def dump_regions(self, cfg_name, *bbox_controls): + if not cfg_name: return gr_value(f'Config file name cannot be empty.', visible=True) + + bbox_settings = build_bbox_settings(bbox_controls) + data = {'bbox_controls': [v._asdict() for v in bbox_settings.values()]} + + if not os.path.exists(CFG_PATH): os.makedirs(CFG_PATH) + fp = os.path.join(CFG_PATH, cfg_name) + with open(fp, 'w', encoding='utf-8') as fh: + json.dump(data, fh, indent=2, ensure_ascii=False) + + return gr_value(f'Config saved to {fp}.', visible=True) + + def load_regions(self, ref_image, cfg_name, *bbox_controls): + if ref_image is None: + return [gr_value(v) for v in bbox_controls] + [gr_value(f'Please create or upload a ref image first.', visible=True)] + fp = os.path.join(CFG_PATH, cfg_name) + if not os.path.exists(fp): + return [gr_value(v) for v in bbox_controls] + [gr_value(f'Config {fp} not found.', visible=True)] + + try: + with open(fp, 'r', encoding='utf-8') as fh: + data = json.load(fh) + except Exception as e: + return [gr_value(v) for v in bbox_controls] + [gr_value(f'Failed to load config {fp}: {e}', visible=True)] + + num_boxes = len(data['bbox_controls']) + data_list = [] + for i in range(BBOX_MAX_NUM): + if i < num_boxes: + for k in BBoxSettings._fields: + if k in data['bbox_controls'][i]: + data_list.append(data['bbox_controls'][i][k]) + else: + data_list.append(None) + else: + data_list.extend(DEFAULT_BBOX_SETTINGS) + + return [gr_value(v) for v in data_list] + [gr_value(f'Config loaded from {fp}.', visible=True)] + + + def noise_inverse_set_cache(self, p: ProcessingImg2Img, x0: Tensor, xt: Tensor, prompts: List[str], steps: int, retouch:float): + self.noise_inverse_cache = NoiseInverseCache(p.sd_model.sd_model_hash, x0, xt, steps, retouch, prompts) + + def noise_inverse_get_cache(self): + return self.noise_inverse_cache + + + def reset(self): + ''' unhijack inner APIs, see hijack in process() ''' + if hasattr(Script, "create_sampler_original_md"): + sd_samplers.create_sampler = Script.create_sampler_original_md + del Script.create_sampler_original_md + if hasattr(Script, "create_random_tensors_original_md"): + processing.create_random_tensors = Script.create_random_tensors_original_md + del Script.create_random_tensors_original_md + if hasattr(sd_samplers_common.Sampler, "callback_ori"): + sd_samplers_common.Sampler.callback_state = sd_samplers_common.Sampler.callback_ori + del sd_samplers_common.Sampler.callback_ori + if hasattr(processing, "create_infotext_ori"): + processing.create_infotext = processing.create_infotext_ori + del processing.create_infotext_ori + DemoFusion.unhook() + self.delegate = None + + def reset_and_gc(self): + self.reset() + self.noise_inverse_cache = None + + import gc; gc.collect() + devices.torch_gc() + + try: + import os + import psutil + mem = psutil.Process(os.getpid()).memory_info() + print(f'[Mem] rss: {mem.rss/2**30:.3f} GB, vms: {mem.vms/2**30:.3f} GB') + from modules.shared import mem_mon as vram_mon + from modules.memmon import MemUsageMonitor + vram_mon: MemUsageMonitor + free, total = vram_mon.cuda_mem_get_info() + print(f'[VRAM] free: {free/2**30:.3f} GB, total: {total/2**30:.3f} GB') + except: + pass diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/tilevae.py b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/tilevae.py new file mode 100644 index 0000000000000000000000000000000000000000..5f4ed4a2e3b0e0213911cb8d289fb2d36d974d9c --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/scripts/tilevae.py @@ -0,0 +1,758 @@ +''' +# ------------------------------------------------------------------------ +# +# Tiled VAE +# +# Introducing a revolutionary new optimization designed to make +# the VAE work with giant images on limited VRAM! +# Say goodbye to the frustration of OOM and hello to seamless output! +# +# ------------------------------------------------------------------------ +# +# This script is a wild hack that splits the image into tiles, +# encodes each tile separately, and merges the result back together. +# +# Advantages: +# - The VAE can now work with giant images on limited VRAM +# (~10 GB for 8K images!) +# - The merged output is completely seamless without any post-processing. +# +# Drawbacks: +# - NaNs always appear in for 8k images when you use fp16 (half) VAE +# You must use --no-half-vae to disable half VAE for that giant image. +# - The gradient calculation is not compatible with this hack. It +# will break any backward() or torch.autograd.grad() that passes VAE. +# (But you can still use the VAE to generate training data.) +# +# How it works: +# 1. The image is split into tiles, which are then padded with 11/32 pixels' in the decoder/encoder. +# 2. When Fast Mode is disabled: +# 1. The original VAE forward is decomposed into a task queue and a task worker, which starts to process each tile. +# 2. When GroupNorm is needed, it suspends, stores current GroupNorm mean and var, send everything to RAM, and turns to the next tile. +# 3. After all GroupNorm means and vars are summarized, it applies group norm to tiles and continues. +# 4. A zigzag execution order is used to reduce unnecessary data transfer. +# 3. When Fast Mode is enabled: +# 1. The original input is downsampled and passed to a separate task queue. +# 2. Its group norm parameters are recorded and used by all tiles' task queues. +# 3. Each tile is separately processed without any RAM-VRAM data transfer. +# 4. After all tiles are processed, tiles are written to a result buffer and returned. +# Encoder color fix = only estimate GroupNorm before downsampling, i.e., run in a semi-fast mode. +# +# Enjoy! +# +# @Author: LI YI @ Nanyang Technological University - Singapore +# @Date: 2023-03-02 +# @License: CC BY-NC-SA 4.0 +# +# Please give me a star if you like this project! +# +# ------------------------------------------------------------------------- +''' + +import gc +import math +from time import time +from tqdm import tqdm + +import torch +import torch.version +import torch.nn.functional as F +import gradio as gr + +import modules.scripts as scripts +import modules.devices as devices +from modules.shared import state, opts +from modules.ui import gr_show +from modules.processing import opt_f +from modules.sd_vae_approx import cheap_approximation +from ldm.modules.diffusionmodules.model import AttnBlock, MemoryEfficientAttnBlock + +from tile_utils.attn import get_attn_func +from tile_utils.typing import Processing + +if hasattr(opts, 'hypertile_enable_unet'): # webui >= 1.7 + from modules.ui_components import InputAccordion +else: + InputAccordion = None + + +def get_rcmd_enc_tsize(): + if torch.cuda.is_available() and devices.device not in ['cpu', devices.cpu]: + total_memory = torch.cuda.get_device_properties(devices.device).total_memory // 2**20 + if total_memory > 16*1000: ENCODER_TILE_SIZE = 3072 + elif total_memory > 12*1000: ENCODER_TILE_SIZE = 2048 + elif total_memory > 8*1000: ENCODER_TILE_SIZE = 1536 + else: ENCODER_TILE_SIZE = 960 + else: ENCODER_TILE_SIZE = 512 + return ENCODER_TILE_SIZE + + +def get_rcmd_dec_tsize(): + if torch.cuda.is_available() and devices.device not in ['cpu', devices.cpu]: + total_memory = torch.cuda.get_device_properties(devices.device).total_memory // 2**20 + if total_memory > 30*1000: DECODER_TILE_SIZE = 256 + elif total_memory > 16*1000: DECODER_TILE_SIZE = 192 + elif total_memory > 12*1000: DECODER_TILE_SIZE = 128 + elif total_memory > 8*1000: DECODER_TILE_SIZE = 96 + else: DECODER_TILE_SIZE = 64 + else: DECODER_TILE_SIZE = 64 + return DECODER_TILE_SIZE + + +def inplace_nonlinearity(x): + # Test: fix for Nans + return F.silu(x, inplace=True) + + +def attn2task(task_queue, net): + attn_forward = get_attn_func() + task_queue.append(('store_res', lambda x: x)) + task_queue.append(('pre_norm', net.norm)) + task_queue.append(('attn', lambda x, net=net: attn_forward(net, x))) + task_queue.append(['add_res', None]) + + +def resblock2task(queue, block): + """ + Turn a ResNetBlock into a sequence of tasks and append to the task queue + + @param queue: the target task queue + @param block: ResNetBlock + + """ + if block.in_channels != block.out_channels: + if block.use_conv_shortcut: + queue.append(('store_res', block.conv_shortcut)) + else: + queue.append(('store_res', block.nin_shortcut)) + else: + queue.append(('store_res', lambda x: x)) + queue.append(('pre_norm', block.norm1)) + queue.append(('silu', inplace_nonlinearity)) + queue.append(('conv1', block.conv1)) + queue.append(('pre_norm', block.norm2)) + queue.append(('silu', inplace_nonlinearity)) + queue.append(('conv2', block.conv2)) + queue.append(['add_res', None]) + + +def build_sampling(task_queue, net, is_decoder): + """ + Build the sampling part of a task queue + @param task_queue: the target task queue + @param net: the network + @param is_decoder: currently building decoder or encoder + """ + if is_decoder: + resblock2task(task_queue, net.mid.block_1) + attn2task(task_queue, net.mid.attn_1) + resblock2task(task_queue, net.mid.block_2) + resolution_iter = reversed(range(net.num_resolutions)) + block_ids = net.num_res_blocks + 1 + condition = 0 + module = net.up + func_name = 'upsample' + else: + resolution_iter = range(net.num_resolutions) + block_ids = net.num_res_blocks + condition = net.num_resolutions - 1 + module = net.down + func_name = 'downsample' + + for i_level in resolution_iter: + for i_block in range(block_ids): + resblock2task(task_queue, module[i_level].block[i_block]) + if i_level != condition: + task_queue.append((func_name, getattr(module[i_level], func_name))) + + if not is_decoder: + resblock2task(task_queue, net.mid.block_1) + attn2task(task_queue, net.mid.attn_1) + resblock2task(task_queue, net.mid.block_2) + + +def build_task_queue(net, is_decoder): + """ + Build a single task queue for the encoder or decoder + @param net: the VAE decoder or encoder network + @param is_decoder: currently building decoder or encoder + @return: the task queue + """ + task_queue = [] + task_queue.append(('conv_in', net.conv_in)) + + # construct the sampling part of the task queue + # because encoder and decoder share the same architecture, we extract the sampling part + build_sampling(task_queue, net, is_decoder) + + if not is_decoder or not net.give_pre_end: + task_queue.append(('pre_norm', net.norm_out)) + task_queue.append(('silu', inplace_nonlinearity)) + task_queue.append(('conv_out', net.conv_out)) + if is_decoder and net.tanh_out: + task_queue.append(('tanh', torch.tanh)) + + return task_queue + + +def clone_task_queue(task_queue): + """ + Clone a task queue + @param task_queue: the task queue to be cloned + @return: the cloned task queue + """ + return [[item for item in task] for task in task_queue] + + +def get_var_mean(input, num_groups, eps=1e-6): + """ + Get mean and var for group norm + """ + b, c = input.size(0), input.size(1) + channel_in_group = int(c/num_groups) + input_reshaped = input.contiguous().view(1, int(b * num_groups), channel_in_group, *input.size()[2:]) + var, mean = torch.var_mean(input_reshaped, dim=[0, 2, 3, 4], unbiased=False) + return var, mean + + +def custom_group_norm(input, num_groups, mean, var, weight=None, bias=None, eps=1e-6): + """ + Custom group norm with fixed mean and var + + @param input: input tensor + @param num_groups: number of groups. by default, num_groups = 32 + @param mean: mean, must be pre-calculated by get_var_mean + @param var: var, must be pre-calculated by get_var_mean + @param weight: weight, should be fetched from the original group norm + @param bias: bias, should be fetched from the original group norm + @param eps: epsilon, by default, eps = 1e-6 to match the original group norm + + @return: normalized tensor + """ + b, c = input.size(0), input.size(1) + channel_in_group = int(c/num_groups) + input_reshaped = input.contiguous().view( + 1, int(b * num_groups), channel_in_group, *input.size()[2:]) + + out = F.batch_norm(input_reshaped, mean.to(input), var.to(input), weight=None, bias=None, training=False, momentum=0, eps=eps) + out = out.view(b, c, *input.size()[2:]) + + # post affine transform + if weight is not None: + out *= weight.view(1, -1, 1, 1) + if bias is not None: + out += bias.view(1, -1, 1, 1) + return out + + +def crop_valid_region(x, input_bbox, target_bbox, is_decoder): + """ + Crop the valid region from the tile + @param x: input tile + @param input_bbox: original input bounding box + @param target_bbox: output bounding box + @param scale: scale factor + @return: cropped tile + """ + padded_bbox = [i * 8 if is_decoder else i//8 for i in input_bbox] + margin = [target_bbox[i] - padded_bbox[i] for i in range(4)] + return x[:, :, margin[2]:x.size(2)+margin[3], margin[0]:x.size(3)+margin[1]] + + +# ↓↓↓ https://github.com/Kahsolt/stable-diffusion-webui-vae-tile-infer ↓↓↓ + +def perfcount(fn): + def wrapper(*args, **kwargs): + ts = time() + + if torch.cuda.is_available(): + torch.cuda.reset_peak_memory_stats(devices.device) + devices.torch_gc() + gc.collect() + + ret = fn(*args, **kwargs) + + devices.torch_gc() + gc.collect() + if torch.cuda.is_available(): + vram = torch.cuda.max_memory_allocated(devices.device) / 2**20 + print(f'[Tiled VAE]: Done in {time() - ts:.3f}s, max VRAM alloc {vram:.3f} MB') + else: + print(f'[Tiled VAE]: Done in {time() - ts:.3f}s') + + return ret + return wrapper + +# ↑↑↑ https://github.com/Kahsolt/stable-diffusion-webui-vae-tile-infer ↑↑↑ + + +class GroupNormParam: + + def __init__(self): + self.var_list = [] + self.mean_list = [] + self.pixel_list = [] + self.weight = None + self.bias = None + + def add_tile(self, tile, layer): + var, mean = get_var_mean(tile, 32) + # For giant images, the variance can be larger than max float16 + # In this case we create a copy to float32 + if var.dtype == torch.float16 and var.isinf().any(): + fp32_tile = tile.float() + var, mean = get_var_mean(fp32_tile, 32) + # ============= DEBUG: test for infinite ============= + # if torch.isinf(var).any(): + # print('var: ', var) + # ==================================================== + self.var_list.append(var) + self.mean_list.append(mean) + self.pixel_list.append( + tile.shape[2]*tile.shape[3]) + if hasattr(layer, 'weight'): + self.weight = layer.weight + self.bias = layer.bias + else: + self.weight = None + self.bias = None + + def summary(self): + """ + summarize the mean and var and return a function + that apply group norm on each tile + """ + if len(self.var_list) == 0: return None + + var = torch.vstack(self.var_list) + mean = torch.vstack(self.mean_list) + max_value = max(self.pixel_list) + pixels = torch.tensor(self.pixel_list, dtype=torch.float32, device=devices.device) / max_value + sum_pixels = torch.sum(pixels) + pixels = pixels.unsqueeze(1) / sum_pixels + var = torch.sum(var * pixels, dim=0) + mean = torch.sum(mean * pixels, dim=0) + return lambda x: custom_group_norm(x, 32, mean, var, self.weight, self.bias) + + @staticmethod + def from_tile(tile, norm): + """ + create a function from a single tile without summary + """ + var, mean = get_var_mean(tile, 32) + if var.dtype == torch.float16 and var.isinf().any(): + fp32_tile = tile.float() + var, mean = get_var_mean(fp32_tile, 32) + # if it is a macbook, we need to convert back to float16 + if var.device.type == 'mps': + # clamp to avoid overflow + var = torch.clamp(var, 0, 60000) + var = var.half() + mean = mean.half() + if hasattr(norm, 'weight'): + weight = norm.weight + bias = norm.bias + else: + weight = None + bias = None + + def group_norm_func(x, mean=mean, var=var, weight=weight, bias=bias): + return custom_group_norm(x, 32, mean, var, weight, bias, 1e-6) + return group_norm_func + + +class VAEHook: + + def __init__(self, net, tile_size, is_decoder:bool, fast_decoder:bool, fast_encoder:bool, color_fix:bool, to_gpu:bool=False): + self.net = net # encoder | decoder + self.tile_size = tile_size + self.is_decoder = is_decoder + self.fast_mode = (fast_encoder and not is_decoder) or (fast_decoder and is_decoder) + self.color_fix = color_fix and not is_decoder + self.to_gpu = to_gpu + self.pad = 11 if is_decoder else 32 # FIXME: magic number + + def __call__(self, x): + original_device = next(self.net.parameters()).device + try: + if self.to_gpu: + self.net = self.net.to(devices.get_optimal_device()) + + B, C, H, W = x.shape + if max(H, W) <= self.pad * 2 + self.tile_size: + print("[Tiled VAE]: the input size is tiny and unnecessary to tile.") + return self.net.original_forward(x) + else: + return self.vae_tile_forward(x) + finally: + self.net = self.net.to(original_device) + + def get_best_tile_size(self, lowerbound, upperbound): + """ + Get the best tile size for GPU memory + """ + divider = 32 + while divider >= 2: + remainer = lowerbound % divider + if remainer == 0: + return lowerbound + candidate = lowerbound - remainer + divider + if candidate <= upperbound: + return candidate + divider //= 2 + return lowerbound + + def split_tiles(self, h, w): + """ + Tool function to split the image into tiles + @param h: height of the image + @param w: width of the image + @return: tile_input_bboxes, tile_output_bboxes + """ + tile_input_bboxes, tile_output_bboxes = [], [] + tile_size = self.tile_size + pad = self.pad + num_height_tiles = math.ceil((h - 2 * pad) / tile_size) + num_width_tiles = math.ceil((w - 2 * pad) / tile_size) + # If any of the numbers are 0, we let it be 1 + # This is to deal with long and thin images + num_height_tiles = max(num_height_tiles, 1) + num_width_tiles = max(num_width_tiles, 1) + + # Suggestions from https://github.com/Kahsolt: auto shrink the tile size + real_tile_height = math.ceil((h - 2 * pad) / num_height_tiles) + real_tile_width = math.ceil((w - 2 * pad) / num_width_tiles) + real_tile_height = self.get_best_tile_size(real_tile_height, tile_size) + real_tile_width = self.get_best_tile_size(real_tile_width, tile_size) + + print(f'[Tiled VAE]: split to {num_height_tiles}x{num_width_tiles} = {num_height_tiles*num_width_tiles} tiles. ' + + f'Optimal tile size {real_tile_width}x{real_tile_height}, original tile size {tile_size}x{tile_size}') + + for i in range(num_height_tiles): + for j in range(num_width_tiles): + # bbox: [x1, x2, y1, y2] + # the padding is is unnessary for image borders. So we directly start from (32, 32) + input_bbox = [ + pad + j * real_tile_width, + min(pad + (j + 1) * real_tile_width, w), + pad + i * real_tile_height, + min(pad + (i + 1) * real_tile_height, h), + ] + + # if the output bbox is close to the image boundary, we extend it to the image boundary + output_bbox = [ + input_bbox[0] if input_bbox[0] > pad else 0, + input_bbox[1] if input_bbox[1] < w - pad else w, + input_bbox[2] if input_bbox[2] > pad else 0, + input_bbox[3] if input_bbox[3] < h - pad else h, + ] + + # scale to get the final output bbox + output_bbox = [x * 8 if self.is_decoder else x // 8 for x in output_bbox] + tile_output_bboxes.append(output_bbox) + + # indistinguishable expand the input bbox by pad pixels + tile_input_bboxes.append([ + max(0, input_bbox[0] - pad), + min(w, input_bbox[1] + pad), + max(0, input_bbox[2] - pad), + min(h, input_bbox[3] + pad), + ]) + + return tile_input_bboxes, tile_output_bboxes + + @torch.no_grad() + def estimate_group_norm(self, z, task_queue, color_fix): + device = z.device + tile = z + last_id = len(task_queue) - 1 + while last_id >= 0 and task_queue[last_id][0] != 'pre_norm': + last_id -= 1 + if last_id <= 0 or task_queue[last_id][0] != 'pre_norm': + raise ValueError('No group norm found in the task queue') + # estimate until the last group norm + for i in range(last_id + 1): + task = task_queue[i] + if task[0] == 'pre_norm': + group_norm_func = GroupNormParam.from_tile(tile, task[1]) + task_queue[i] = ('apply_norm', group_norm_func) + if i == last_id: + return True + tile = group_norm_func(tile) + elif task[0] == 'store_res': + task_id = i + 1 + while task_id < last_id and task_queue[task_id][0] != 'add_res': + task_id += 1 + if task_id >= last_id: + continue + task_queue[task_id][1] = task[1](tile) + elif task[0] == 'add_res': + tile += task[1].to(device) + task[1] = None + elif color_fix and task[0] == 'downsample': + for j in range(i, last_id + 1): + if task_queue[j][0] == 'store_res': + task_queue[j] = ('store_res_cpu', task_queue[j][1]) + return True + else: + tile = task[1](tile) + try: + devices.test_for_nans(tile, "vae") + except: + print(f'Nan detected in fast mode estimation. Fast mode disabled.') + return False + + raise IndexError('Should not reach here') + + @perfcount + @torch.no_grad() + def vae_tile_forward(self, z): + """ + Decode a latent vector z into an image in a tiled manner. + @param z: latent vector + @return: image + """ + device = next(self.net.parameters()).device + dtype = next(self.net.parameters()).dtype + net = self.net + tile_size = self.tile_size + is_decoder = self.is_decoder + + z = z.detach() # detach the input to avoid backprop + + N, height, width = z.shape[0], z.shape[2], z.shape[3] + net.last_z_shape = z.shape + + # Split the input into tiles and build a task queue for each tile + print(f'[Tiled VAE]: input_size: {z.shape}, tile_size: {tile_size}, padding: {self.pad}') + + in_bboxes, out_bboxes = self.split_tiles(height, width) + + # Prepare tiles by split the input latents + tiles = [] + for input_bbox in in_bboxes: + tile = z[:, :, input_bbox[2]:input_bbox[3], input_bbox[0]:input_bbox[1]].cpu() + tiles.append(tile) + + num_tiles = len(tiles) + num_completed = 0 + + # Build task queues + single_task_queue = build_task_queue(net, is_decoder) + if self.fast_mode: + # Fast mode: downsample the input image to the tile size, + # then estimate the group norm parameters on the downsampled image + scale_factor = tile_size / max(height, width) + z = z.to(device) + downsampled_z = F.interpolate(z, scale_factor=scale_factor, mode='nearest-exact') + # use nearest-exact to keep statictics as close as possible + print(f'[Tiled VAE]: Fast mode enabled, estimating group norm parameters on {downsampled_z.shape[3]} x {downsampled_z.shape[2]} image') + + # ======= Special thanks to @Kahsolt for distribution shift issue ======= # + # The downsampling will heavily distort its mean and std, so we need to recover it. + std_old, mean_old = torch.std_mean(z, dim=[0, 2, 3], keepdim=True) + std_new, mean_new = torch.std_mean(downsampled_z, dim=[0, 2, 3], keepdim=True) + downsampled_z = (downsampled_z - mean_new) / std_new * std_old + mean_old + del std_old, mean_old, std_new, mean_new + # occasionally the std_new is too small or too large, which exceeds the range of float16 + # so we need to clamp it to max z's range. + downsampled_z = torch.clamp_(downsampled_z, min=z.min(), max=z.max()) + estimate_task_queue = clone_task_queue(single_task_queue) + if self.estimate_group_norm(downsampled_z, estimate_task_queue, color_fix=self.color_fix): + single_task_queue = estimate_task_queue + del downsampled_z + + task_queues = [clone_task_queue(single_task_queue) for _ in range(num_tiles)] + + # Dummy result + result = None + result_approx = None + try: + with devices.autocast(): + result_approx = torch.cat([F.interpolate(cheap_approximation(x).unsqueeze(0), scale_factor=opt_f, mode='nearest-exact') for x in z], dim=0).cpu() + except: pass + # Free memory of input latent tensor + del z + + # Task queue execution + pbar = tqdm(total=num_tiles * len(task_queues[0]), desc=f"[Tiled VAE]: Executing {'Decoder' if is_decoder else 'Encoder'} Task Queue: ") + + # execute the task back and forth when switch tiles so that we always + # keep one tile on the GPU to reduce unnecessary data transfer + forward = True + interrupted = False + #state.interrupted = interrupted + while True: + if state.interrupted: interrupted = True ; break + + group_norm_param = GroupNormParam() + for i in range(num_tiles) if forward else reversed(range(num_tiles)): + if state.interrupted: interrupted = True ; break + + tile = tiles[i].to(device) + input_bbox = in_bboxes[i] + task_queue = task_queues[i] + + interrupted = False + while len(task_queue) > 0: + if state.interrupted: interrupted = True ; break + + # DEBUG: current task + # print('Running task: ', task_queue[0][0], ' on tile ', i, '/', num_tiles, ' with shape ', tile.shape) + task = task_queue.pop(0) + if task[0] == 'pre_norm': + group_norm_param.add_tile(tile, task[1]) + break + elif task[0] == 'store_res' or task[0] == 'store_res_cpu': + task_id = 0 + res = task[1](tile) + if not self.fast_mode or task[0] == 'store_res_cpu': + res = res.cpu() + while task_queue[task_id][0] != 'add_res': + task_id += 1 + task_queue[task_id][1] = res + elif task[0] == 'add_res': + tile += task[1].to(device) + task[1] = None + else: + tile = task[1](tile) + pbar.update(1) + + if interrupted: break + + # check for NaNs in the tile. + # If there are NaNs, we abort the process to save user's time + devices.test_for_nans(tile, "vae") + + if len(task_queue) == 0: + tiles[i] = None + num_completed += 1 + if result is None: # NOTE: dim C varies from different cases, can only be inited dynamically + result = torch.zeros((N, tile.shape[1], height * 8 if is_decoder else height // 8, width * 8 if is_decoder else width // 8), device=device, requires_grad=False) + result[:, :, out_bboxes[i][2]:out_bboxes[i][3], out_bboxes[i][0]:out_bboxes[i][1]] = crop_valid_region(tile, in_bboxes[i], out_bboxes[i], is_decoder) + del tile + elif i == num_tiles - 1 and forward: + forward = False + tiles[i] = tile + elif i == 0 and not forward: + forward = True + tiles[i] = tile + else: + tiles[i] = tile.cpu() + del tile + + if interrupted: break + if num_completed == num_tiles: break + + # insert the group norm task to the head of each task queue + group_norm_func = group_norm_param.summary() + if group_norm_func is not None: + for i in range(num_tiles): + task_queue = task_queues[i] + task_queue.insert(0, ('apply_norm', group_norm_func)) + + # Done! + pbar.close() + return result.to(dtype) if result is not None else result_approx.to(device, dtype=dtype) + + +class Script(scripts.Script): + + def __init__(self): + self.hooked = False + + def title(self): + return "Tiled VAE" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + tab = 't2i' if not is_img2img else 'i2i' + uid = lambda name: f'MD-{tab}-{name}' + + with ( + InputAccordion(False, label='Tiled VAE', elem_id=f'MDV-{tab}-enabled') if InputAccordion + else gr.Accordion('Tiled VAE', open=False, elem_id=f'MDV-{tab}') + as enabled + ): + with gr.Row() as tab_enable: + if not InputAccordion: + enabled = gr.Checkbox(label='Enable Tiled VAE', value=False, elem_id=uid('enable')) + vae_to_gpu = gr.Checkbox(label='Move VAE to GPU (if possible)', value=True, elem_id=uid('vae2gpu')) + + gr.HTML('

Recommended to set tile sizes as large as possible before got CUDA error: out of memory.

') + with gr.Row() as tab_size: + encoder_tile_size = gr.Slider(label='Encoder Tile Size', minimum=256, maximum=4096, step=16, value=get_rcmd_enc_tsize(), elem_id=uid('enc-size')) + decoder_tile_size = gr.Slider(label='Decoder Tile Size', minimum=48, maximum=512, step=16, value=get_rcmd_dec_tsize(), elem_id=uid('dec-size')) + reset = gr.Button(value='↻ Reset', variant='tool') + reset.click(fn=lambda: [get_rcmd_enc_tsize(), get_rcmd_dec_tsize()], outputs=[encoder_tile_size, decoder_tile_size], show_progress=False) + + with gr.Row() as tab_param: + fast_encoder = gr.Checkbox(label='Fast Encoder', value=True, elem_id=uid('fastenc')) + color_fix = gr.Checkbox(label='Fast Encoder Color Fix', value=False, visible=True, elem_id=uid('fastenc-colorfix')) + fast_decoder = gr.Checkbox(label='Fast Decoder', value=True, elem_id=uid('fastdec')) + + fast_encoder.change(fn=gr_show, inputs=fast_encoder, outputs=color_fix, show_progress=False) + + return [ + enabled, + encoder_tile_size, decoder_tile_size, + vae_to_gpu, fast_decoder, fast_encoder, color_fix, + ] + + def process(self, p:Processing, + enabled:bool, + encoder_tile_size:int, decoder_tile_size:int, + vae_to_gpu:bool, fast_decoder:bool, fast_encoder:bool, color_fix:bool + ): + + # for shorthand + vae = p.sd_model.first_stage_model + encoder = vae.encoder + decoder = vae.decoder + + # undo hijack if disabled (in cases last time crashed) + if not enabled: + if self.hooked: + if isinstance(encoder.forward, VAEHook): + encoder.forward.net = None + encoder.forward = encoder.original_forward + if isinstance(decoder.forward, VAEHook): + decoder.forward.net = None + decoder.forward = decoder.original_forward + self.hooked = False + return + + if devices.get_optimal_device_name().startswith('cuda') and vae.device == devices.cpu and not vae_to_gpu: + print("[Tiled VAE] warn: VAE is not on GPU, check 'Move VAE to GPU' if possible.") + + # do hijack + kwargs = { + 'fast_decoder': fast_decoder, + 'fast_encoder': fast_encoder, + 'color_fix': color_fix, + 'to_gpu': vae_to_gpu, + } + + # save original forward (only once) + if not hasattr(encoder, 'original_forward'): setattr(encoder, 'original_forward', encoder.forward) + if not hasattr(decoder, 'original_forward'): setattr(decoder, 'original_forward', decoder.forward) + + self.hooked = True + + encoder.forward = VAEHook(encoder, encoder_tile_size, is_decoder=False, **kwargs) + decoder.forward = VAEHook(decoder, decoder_tile_size, is_decoder=True, **kwargs) + + def postprocess(self, p:Processing, processed, enabled:bool, *args): + if not enabled: return + + vae = p.sd_model.first_stage_model + encoder = vae.encoder + decoder = vae.decoder + if isinstance(encoder.forward, VAEHook): + encoder.forward.net = None + encoder.forward = encoder.original_forward + if isinstance(decoder.forward, VAEHook): + decoder.forward.net = None + decoder.forward = decoder.original_forward diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/abstractdiffusion.cpython-310.pyc b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/abstractdiffusion.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8c2c8df35d3835feae2abbc545eb35b96d85b04 Binary files /dev/null and b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/abstractdiffusion.cpython-310.pyc differ diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/demofusion.cpython-310.pyc b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/demofusion.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..888412ca6acb9ab5fc12845cf96b4bc9ad682f33 Binary files /dev/null and b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/demofusion.cpython-310.pyc differ diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/mixtureofdiffusers.cpython-310.pyc b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/mixtureofdiffusers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d87d14e5afdcfec86f7aeb26e7e0d64a44602e31 Binary files /dev/null and b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/mixtureofdiffusers.cpython-310.pyc differ diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/multidiffusion.cpython-310.pyc b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/multidiffusion.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df9e59715f9a03a73815275ee019edf53cfcac92 Binary files /dev/null and b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/__pycache__/multidiffusion.cpython-310.pyc differ diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/abstractdiffusion.py b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/abstractdiffusion.py new file mode 100644 index 0000000000000000000000000000000000000000..88916f3881479355d6ac0ab0425836a09255e126 --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/abstractdiffusion.py @@ -0,0 +1,747 @@ +from tile_utils.utils import * + + +class AbstractDiffusion: + + def __init__(self, p: Processing, sampler: Sampler): + self.method = self.__class__.__name__ + self.p: Processing = p + self.pbar = None + + # sampler + self.sampler_name = p.sampler_name + self.sampler_raw = sampler + self.sampler = sampler + + # fix. Kdiff 'AND' support and image editing model support + if self.is_kdiff and not hasattr(self, 'is_edit_model'): + self.is_edit_model = (shared.sd_model.cond_stage_key == "edit" # "txt" + and self.sampler.model_wrap_cfg.image_cfg_scale is not None + and self.sampler.model_wrap_cfg.image_cfg_scale != 1.0) + + # cache. final result of current sampling step, [B, C=4, H//8, W//8] + # avoiding overhead of creating new tensors and weight summing + self.x_buffer: Tensor = None + self.w: int = int(self.p.width // opt_f) # latent size + self.h: int = int(self.p.height // opt_f) + # weights for background & grid bboxes + self.weights: Tensor = torch.zeros((1, 1, self.h, self.w), device=devices.device, dtype=torch.float32) + + # FIXME: I'm trying to count the step correctly but it's not working + self.step_count = 0 + self.inner_loop_count = 0 + self.kdiff_step = -1 + + # ext. Grid tiling painting (grid bbox) + self.enable_grid_bbox: bool = False + self.tile_w: int = None + self.tile_h: int = None + self.tile_bs: int = None + self.num_tiles: int = None + self.num_batches: int = None + self.batched_bboxes: List[List[BBox]] = [] + + # ext. Region Prompt Control (custom bbox) + self.enable_custom_bbox: bool = False + self.custom_bboxes: List[CustomBBox] = [] + self.cond_basis: Cond = None + self.uncond_basis: Uncond = None + self.draw_background: bool = True # by default we draw major prompts in grid tiles + self.causal_layers: bool = None + + # ext. Noise Inversion (noise inversion) + self.noise_inverse_enabled: bool = False + self.noise_inverse_steps: int = 0 + self.noise_inverse_retouch: float = None + self.noise_inverse_renoise_strength: float = None + self.noise_inverse_renoise_kernel: int = None + self.noise_inverse_get_cache = None + self.noise_inverse_set_cache = None + self.sample_img2img_original = None + + # ext. ControlNet + self.enable_controlnet: bool = False + self.controlnet_script: ModuleType = None + self.control_tensor_batch: List[List[Tensor]] = [] + self.control_params: Dict[str, Tensor] = {} + self.control_tensor_cpu: bool = None + self.control_tensor_custom: List[List[Tensor]] = [] + + # ext. StableSR + self.enable_stablesr: bool = False + self.stablesr_script: ModuleType = None + self.stablesr_tensor: Tensor = None + self.stablesr_tensor_batch: List[Tensor] = [] + self.stablesr_tensor_custom: List[Tensor] = [] + + @property + def is_kdiff(self): + return isinstance(self.sampler_raw, KDiffusionSampler) + + @property + def is_ddim(self): + return isinstance(self.sampler_raw, CompVisSampler) + + def update_pbar(self): + if self.pbar.n >= self.pbar.total: + self.pbar.close() + else: + if self.step_count == state.sampling_step: + self.inner_loop_count += 1 + if self.inner_loop_count < self.total_bboxes: + self.pbar.update() + else: + self.step_count = state.sampling_step + self.inner_loop_count = 0 + + def reset_buffer(self, x_in:Tensor): + # Judge if the shape of x_in is the same as the shape of x_buffer + if self.x_buffer is None or self.x_buffer.shape != x_in.shape: + self.x_buffer = torch.zeros_like(x_in, device=x_in.device, dtype=x_in.dtype) + else: + self.x_buffer.zero_() + + def init_done(self): + ''' + Call this after all `init_*`, settings are done, now perform: + - settings sanity check + - pre-computations, cache init + - anything thing needed before denoising starts + ''' + + self.total_bboxes = 0 + if self.enable_grid_bbox: self.total_bboxes += self.num_batches + if self.enable_custom_bbox: self.total_bboxes += len(self.custom_bboxes) + assert self.total_bboxes > 0, "Nothing to paint! No background to draw and no custom bboxes were provided." + + self.pbar = tqdm(total=(self.total_bboxes) * state.sampling_steps, desc=f"{self.method} Sampling: ") + + ''' ↓↓↓ cond_dict utils ↓↓↓ ''' + + def _tcond_key(self, cond_dict:CondDict) -> str: + return 'crossattn' if 'crossattn' in cond_dict else 'c_crossattn' + + def get_tcond(self, cond_dict:CondDict) -> Tensor: + tcond = cond_dict[self._tcond_key(cond_dict)] + if isinstance(tcond, list): tcond = tcond[0] + return tcond + + def set_tcond(self, cond_dict:CondDict, tcond:Tensor): + key = self._tcond_key(cond_dict) + if isinstance(cond_dict[key], list): tcond = [tcond] + cond_dict[key] = tcond + + def _icond_key(self, cond_dict:CondDict) -> str: + return 'c_adm' if shared.sd_model.model.conditioning_key in ['crossattn-adm', 'adm'] else 'c_concat' + + def get_icond(self, cond_dict:CondDict) -> Tensor: + ''' icond differs for different models (inpaint/unclip model) ''' + key = self._icond_key(cond_dict) + icond = cond_dict[key] + if isinstance(icond, list): icond = icond[0] + return icond + + def set_icond(self, cond_dict:CondDict, icond:Tensor): + key = self._icond_key(cond_dict) + if isinstance(cond_dict[key], list): icond = [icond] + cond_dict[key] = icond + + def _vcond_key(self, cond_dict:CondDict) -> Optional[str]: + return 'vector' if 'vector' in cond_dict else None + + def get_vcond(self, cond_dict:CondDict) -> Optional[Tensor]: + ''' vector for SDXL ''' + key = self._vcond_key(cond_dict) + return cond_dict.get(key) + + def set_vcond(self, cond_dict:CondDict, vcond:Optional[Tensor]): + key = self._vcond_key(cond_dict) + if key is not None: + cond_dict[key] = vcond + + def make_cond_dict(self, cond_in:CondDict, tcond:Tensor, icond:Tensor, vcond:Tensor=None) -> CondDict: + ''' copy & replace the content, returns a new object ''' + cond_out = cond_in.copy() + self.set_tcond(cond_out, tcond) + self.set_icond(cond_out, icond) + self.set_vcond(cond_out, vcond) + return cond_out + + ''' ↓↓↓ extensive functionality ↓↓↓ ''' + + @grid_bbox + def init_grid_bbox(self, tile_w:int, tile_h:int, overlap:int, tile_bs:int): + self.enable_grid_bbox = True + + self.tile_w = min(tile_w, self.w) + self.tile_h = min(tile_h, self.h) + overlap = max(0, min(overlap, min(tile_w, tile_h) - 4)) + # split the latent into overlapped tiles, then batching + # weights basically indicate how many times a pixel is painted + bboxes, weights = split_bboxes(self.w, self.h, self.tile_w, self.tile_h, overlap, self.get_tile_weights()) + self.weights += weights + self.num_tiles = len(bboxes) + self.num_batches = math.ceil(self.num_tiles / tile_bs) + self.tile_bs = math.ceil(len(bboxes) / self.num_batches) # optimal_batch_size + self.batched_bboxes = [bboxes[i*self.tile_bs:(i+1)*self.tile_bs] for i in range(self.num_batches)] + + @grid_bbox + def get_tile_weights(self) -> Union[Tensor, float]: + return 1.0 + + + @custom_bbox + def init_custom_bbox(self, bbox_settings:Dict[int,BBoxSettings], draw_background:bool, causal_layers:bool): + self.enable_custom_bbox = True + + self.causal_layers = causal_layers + self.draw_background = draw_background + if not draw_background: + self.enable_grid_bbox = False + self.weights.zero_() + + self.custom_bboxes: List[CustomBBox] = [] + for bbox_setting in bbox_settings.values(): + e, x, y, w, h, p, n, blend_mode, feather_ratio, seed = bbox_setting + if not e or x > 1.0 or y > 1.0 or w <= 0.0 or h <= 0.0: continue + x = int(x * self.w) + y = int(y * self.h) + w = math.ceil(w * self.w) + h = math.ceil(h * self.h) + x = max(0, x) + y = max(0, y) + w = min(self.w - x, w) + h = min(self.h - y, h) + self.custom_bboxes.append(CustomBBox(x, y, w, h, p, n, blend_mode, feather_ratio, seed)) + + if len(self.custom_bboxes) == 0: + self.enable_custom_bbox = False + return + + # prepare cond + p = self.p + prompts = p.all_prompts[:p.batch_size] + neg_prompts = p.all_negative_prompts[:p.batch_size] + for bbox in self.custom_bboxes: + bbox.cond, bbox.extra_network_data = Condition.get_custom_cond(prompts, bbox.prompt, p.steps, p.styles) + bbox.uncond = Condition.get_uncond(Prompt.append_prompt(neg_prompts, bbox.neg_prompt), p.steps, p.styles) + self.cond_basis = Condition.get_cond(prompts, p.steps) + self.uncond_basis = Condition.get_uncond(neg_prompts, p.steps) + + @custom_bbox + def reconstruct_custom_cond(self, org_cond:CondDict, custom_cond:Cond, custom_uncond:Uncond, bbox:CustomBBox) -> Tuple[List, Tensor, Uncond, Tensor]: + image_conditioning = None + if isinstance(org_cond, dict): + icond = self.get_icond(org_cond) + if icond.shape[2:] == (self.h, self.w): # img2img + icond = icond[bbox.slicer] + image_conditioning = icond + + sampler_step = self.sampler.model_wrap_cfg.step + tensor = Condition.reconstruct_cond(custom_cond, sampler_step) + custom_uncond = Condition.reconstruct_uncond(custom_uncond, sampler_step) + return tensor, custom_uncond, image_conditioning + + @custom_bbox + def kdiff_custom_forward(self, x_tile:Tensor, sigma_in:Tensor, original_cond:CondDict, bbox_id:int, bbox:CustomBBox, forward_func:Callable) -> Tensor: + ''' + The inner kdiff noise prediction is usually batched. + We need to unwrap the inside loop to simulate the batched behavior. + This can be extremely tricky. + ''' + + sampler_step = self.sampler.model_wrap_cfg.step + if self.kdiff_step != sampler_step: + self.kdiff_step = sampler_step + self.kdiff_step_bbox = [-1 for _ in range(len(self.custom_bboxes))] + self.tensor = {} # {int: Tensor[cond]} + self.uncond = {} # {int: Tensor[cond]} + self.image_cond_in = {} + # Initialize global prompts just for estimate the behavior of kdiff + self.real_tensor = Condition.reconstruct_cond(self.cond_basis, sampler_step) + self.real_uncond = Condition.reconstruct_uncond(self.uncond_basis, sampler_step) + # reset the progress for all bboxes + self.a = [0 for _ in range(len(self.custom_bboxes))] + + if self.kdiff_step_bbox[bbox_id] != sampler_step: + # When a new step starts for a bbox, we need to judge whether the tensor is batched. + self.kdiff_step_bbox[bbox_id] = sampler_step + + tensor, uncond, image_cond_in = self.reconstruct_custom_cond(original_cond, bbox.cond, bbox.uncond, bbox) + + if self.real_tensor.shape[1] == self.real_uncond.shape[1]: + if shared.batch_cond_uncond: + # when the real tensor is with equal length, all information is contained in x_tile. + # we simulate the batched behavior and compute all the tensors in one go. + if tensor.shape[1] == uncond.shape[1]: + # When our prompt tensor is with equal length, we can directly their code. + if not self.is_edit_model: + cond = torch.cat([tensor, uncond]) + else: + cond = torch.cat([tensor, uncond, uncond]) + self.set_custom_controlnet_tensors(bbox_id, x_tile.shape[0]) + self.set_custom_stablesr_tensors(bbox_id) + return forward_func( + x_tile, + sigma_in, + cond=self.make_cond_dict(original_cond, cond, image_cond_in), + ) + else: + # When not, we need to pass the tensor to UNet separately. + x_out = torch.zeros_like(x_tile) + cond_size = tensor.shape[0] + self.set_custom_controlnet_tensors(bbox_id, cond_size) + self.set_custom_stablesr_tensors(bbox_id) + cond_out = forward_func( + x_tile [:cond_size], + sigma_in[:cond_size], + cond=self.make_cond_dict(original_cond, tensor, image_cond_in[:cond_size]), + ) + uncond_size = uncond.shape[0] + self.set_custom_controlnet_tensors(bbox_id, uncond_size) + self.set_custom_stablesr_tensors(bbox_id) + uncond_out = forward_func( + x_tile [cond_size:cond_size+uncond_size], + sigma_in[cond_size:cond_size+uncond_size], + cond=self.make_cond_dict(original_cond, uncond, image_cond_in[cond_size:cond_size+uncond_size]), + ) + x_out[:cond_size] = cond_out + x_out[cond_size:cond_size+uncond_size] = uncond_out + if self.is_edit_model: + x_out[cond_size+uncond_size:] = uncond_out + return x_out + + # otherwise, the x_tile is only a partial batch. + # We have to denoise in different runs. + # We store the prompt and neg_prompt tensors for current bbox + self.tensor[bbox_id] = tensor + self.uncond[bbox_id] = uncond + self.image_cond_in[bbox_id] = image_cond_in + + # Now we get current batch of prompt and neg_prompt tensors + tensor: Tensor = self.tensor[bbox_id] + uncond: Tensor = self.uncond[bbox_id] + batch_size = x_tile.shape[0] + # get the start and end index of the current batch + a = self.a[bbox_id] + b = a + batch_size + self.a[bbox_id] += batch_size + + if self.real_tensor.shape[1] == self.real_uncond.shape[1]: + # When use --lowvram or --medvram, kdiff will slice the cond and uncond with [a:b] + # So we need to slice our tensor and uncond with the same index as original kdiff. + + # --- original code in kdiff --- + # if not self.is_edit_model: + # cond = torch.cat([tensor, uncond]) + # else: + # cond = torch.cat([tensor, uncond, uncond]) + # cond = cond[a:b] + # ------------------------------ + + # The original kdiff code is to concat and then slice, but this cannot apply to + # our custom prompt tensor when tensor.shape[1] != uncond.shape[1]. So we adapt it. + cond_in, uncond_in = None, None + # Slice the [prompt, neg prompt, (possibly) neg prompt] with [a:b] + if not self.is_edit_model: + if b <= tensor.shape[0]: cond_in = tensor[a:b] + elif a >= tensor.shape[0]: cond_in = uncond[a-tensor.shape[0]:b-tensor.shape[0]] + else: + cond_in = tensor[a:] + uncond_in = uncond[:b-tensor.shape[0]] + else: + if b <= tensor.shape[0]: + cond_in = tensor[a:b] + elif b > tensor.shape[0] and b <= tensor.shape[0] + uncond.shape[0]: + if a>= tensor.shape[0]: + cond_in = uncond[a-tensor.shape[0]:b-tensor.shape[0]] + else: + cond_in = tensor[a:] + uncond_in = uncond[:b-tensor.shape[0]] + else: + if a >= tensor.shape[0] + uncond.shape[0]: + cond_in = uncond[a-tensor.shape[0]-uncond.shape[0]:b-tensor.shape[0]-uncond.shape[0]] + elif a >= tensor.shape[0]: + cond_in = torch.cat([uncond[a-tensor.shape[0]:], uncond[:b-tensor.shape[0]-uncond.shape[0]]]) + + if uncond_in is None or tensor.shape[1] == uncond.shape[1]: + # If the tensor can be passed to UNet in one go, do it. + if uncond_in is not None: + cond_in = torch.cat([cond_in, uncond_in]) + self.set_custom_controlnet_tensors(bbox_id, x_tile.shape[0]) + self.set_custom_stablesr_tensors(bbox_id) + return forward_func( + x_tile, + sigma_in, + cond=self.make_cond_dict(original_cond, cond_in, self.image_cond_in[bbox_id]), + ) + else: + # If not, we need to pass the tensor to UNet separately. + x_out = torch.zeros_like(x_tile) + cond_size = cond_in.shape[0] + self.set_custom_controlnet_tensors(bbox_id, cond_size) + self.set_custom_stablesr_tensors(bbox_id) + cond_out = forward_func( + x_tile [:cond_size], + sigma_in[:cond_size], + cond=self.make_cond_dict(original_cond, cond_in, self.image_cond_in[bbox_id]) + ) + self.set_custom_controlnet_tensors(bbox_id, uncond_in.shape[0]) + self.set_custom_stablesr_tensors(bbox_id) + uncond_out = forward_func( + x_tile [cond_size:], + sigma_in[cond_size:], + cond=self.make_cond_dict(original_cond, uncond_in, self.image_cond_in[bbox_id]) + ) + x_out[:cond_size] = cond_out + x_out[cond_size:] = uncond_out + return x_out + + # If the original prompt is with different length, + # kdiff will deal with the cond and uncond separately. + # Hence we also deal with the tensor and uncond separately. + # get the start and end index of the current batch + + if a < tensor.shape[0]: + # Deal with custom prompt tensor + if not self.is_edit_model: + c_crossattn = tensor[a:b] + else: + c_crossattn = torch.cat([tensor[a:b]], uncond) + self.set_custom_controlnet_tensors(bbox_id, x_tile.shape[0]) + self.set_custom_stablesr_tensors(bbox_id) + # complete this batch. + return forward_func( + x_tile, + sigma_in, + cond=self.make_cond_dict(original_cond, c_crossattn, self.image_cond_in[bbox_id]) + ) + else: + # if the cond is finished, we need to process the uncond. + self.set_custom_controlnet_tensors(bbox_id, uncond.shape[0]) + self.set_custom_stablesr_tensors(bbox_id) + return forward_func( + x_tile, + sigma_in, + cond=self.make_cond_dict(original_cond, uncond, self.image_cond_in[bbox_id]) + ) + + @custom_bbox + def ddim_custom_forward(self, x:Tensor, cond_in:CondDict, bbox:CustomBBox, ts:Tensor, forward_func:Callable, *args, **kwargs) -> Tensor: + ''' draw custom bbox ''' + + tensor, uncond, image_conditioning = self.reconstruct_custom_cond(cond_in, bbox.cond, bbox.uncond, bbox) + + cond = tensor + # for DDIM, shapes definitely match. So we dont need to do the same thing as in the KDIFF sampler. + if uncond.shape[1] < cond.shape[1]: + last_vector = uncond[:, -1:] + last_vector_repeated = last_vector.repeat([1, cond.shape[1] - uncond.shape[1], 1]) + uncond = torch.hstack([uncond, last_vector_repeated]) + elif uncond.shape[1] > cond.shape[1]: + uncond = uncond[:, :cond.shape[1]] + + # Wrap the image conditioning back up since the DDIM code can accept the dict directly. + # Note that they need to be lists because it just concatenates them later. + if image_conditioning is not None: + cond = self.make_cond_dict(cond_in, cond, image_conditioning) + uncond = self.make_cond_dict(cond_in, uncond, image_conditioning) + + # We cannot determine the batch size here for different methods, so delay it to the forward_func. + return forward_func(x, cond, ts, unconditional_conditioning=uncond, *args, **kwargs) + + + @controlnet + def init_controlnet(self, controlnet_script:ModuleType, control_tensor_cpu:bool): + self.enable_controlnet = True + + self.controlnet_script = controlnet_script + self.control_tensor_cpu = control_tensor_cpu + self.control_tensor_batch = None + self.control_params = None + self.control_tensor_custom = [] + + self.prepare_controlnet_tensors() + + @controlnet + def reset_controlnet_tensors(self): + if not self.enable_controlnet: return + if self.control_tensor_batch is None: return + + for param_id in range(len(self.control_params)): + self.control_params[param_id].hint_cond = self.org_control_tensor_batch[param_id] + + @controlnet + def prepare_controlnet_tensors(self, refresh:bool=False): + ''' Crop the control tensor into tiles and cache them ''' + + if not refresh: + if self.control_tensor_batch is not None or self.control_params is not None: return + + if not self.enable_controlnet or self.controlnet_script is None: return + + latest_network = self.controlnet_script.latest_network + if latest_network is None or not hasattr(latest_network, 'control_params'): return + + self.control_params = latest_network.control_params + tensors = [param.hint_cond for param in latest_network.control_params] + self.org_control_tensor_batch = tensors + + if len(tensors) == 0: return + + self.control_tensor_batch = [] + for i in range(len(tensors)): + control_tile_list = [] + control_tensor = tensors[i] + for bboxes in self.batched_bboxes: + single_batch_tensors = [] + for bbox in bboxes: + if len(control_tensor.shape) == 3: + control_tensor.unsqueeze_(0) + control_tile = control_tensor[:, :, bbox[1]*opt_f:bbox[3]*opt_f, bbox[0]*opt_f:bbox[2]*opt_f] + single_batch_tensors.append(control_tile) + control_tile = torch.cat(single_batch_tensors, dim=0) + if self.control_tensor_cpu: + control_tile = control_tile.cpu() + control_tile_list.append(control_tile) + self.control_tensor_batch.append(control_tile_list) + + if len(self.custom_bboxes) > 0: + custom_control_tile_list = [] + for bbox in self.custom_bboxes: + if len(control_tensor.shape) == 3: + control_tensor.unsqueeze_(0) + control_tile = control_tensor[:, :, bbox[1]*opt_f:bbox[3]*opt_f, bbox[0]*opt_f:bbox[2]*opt_f] + if self.control_tensor_cpu: + control_tile = control_tile.cpu() + custom_control_tile_list.append(control_tile) + self.control_tensor_custom.append(custom_control_tile_list) + + @controlnet + def switch_controlnet_tensors(self, batch_id:int, x_batch_size:int, tile_batch_size:int, is_denoise=False): + if not self.enable_controlnet: return + if self.control_tensor_batch is None: return + + for param_id in range(len(self.control_params)): + control_tile = self.control_tensor_batch[param_id][batch_id] + if self.is_kdiff: + all_control_tile = [] + for i in range(tile_batch_size): + this_control_tile = [control_tile[i].unsqueeze(0)] * x_batch_size + all_control_tile.append(torch.cat(this_control_tile, dim=0)) + control_tile = torch.cat(all_control_tile, dim=0) + else: + control_tile = control_tile.repeat([x_batch_size if is_denoise else x_batch_size * 2, 1, 1, 1]) + self.control_params[param_id].hint_cond = control_tile.to(devices.device) + + @controlnet + def set_custom_controlnet_tensors(self, bbox_id:int, repeat_size:int): + if not self.enable_controlnet: return + if not len(self.control_tensor_custom): return + + for param_id in range(len(self.control_params)): + control_tensor = self.control_tensor_custom[param_id][bbox_id].to(devices.device) + self.control_params[param_id].hint_cond = control_tensor.repeat((repeat_size, 1, 1, 1)) + + + @stablesr + def init_stablesr(self, stablesr_script:ModuleType): + if stablesr_script.stablesr_model is None: return + self.stablesr_script = stablesr_script + def set_image_hook(latent_image): + self.enable_stablesr = True + self.stablesr_tensor = latent_image + self.stablesr_tensor_batch = [] + for bboxes in self.batched_bboxes: + single_batch_tensors = [] + for bbox in bboxes: + stablesr_tile = self.stablesr_tensor[:, :, bbox[1]:bbox[3], bbox[0]:bbox[2]] + single_batch_tensors.append(stablesr_tile) + stablesr_tile = torch.cat(single_batch_tensors, dim=0) + self.stablesr_tensor_batch.append(stablesr_tile) + if len(self.custom_bboxes) > 0: + self.stablesr_tensor_custom = [] + for bbox in self.custom_bboxes: + stablesr_tile = self.stablesr_tensor[:, :, bbox[1]:bbox[3], bbox[0]:bbox[2]] + self.stablesr_tensor_custom.append(stablesr_tile) + + stablesr_script.stablesr_model.set_image_hooks['TiledDiffusion'] = set_image_hook + + @stablesr + def reset_stablesr_tensors(self): + if not self.enable_stablesr: return + if self.stablesr_script.stablesr_model is None: return + self.stablesr_script.stablesr_model.latent_image = self.stablesr_tensor + + @stablesr + def switch_stablesr_tensors(self, batch_id:int): + if not self.enable_stablesr: return + if self.stablesr_script.stablesr_model is None: return + if self.stablesr_tensor_batch is None: return + self.stablesr_script.stablesr_model.latent_image = self.stablesr_tensor_batch[batch_id] + + @stablesr + def set_custom_stablesr_tensors(self, bbox_id:int): + if not self.enable_stablesr: return + if self.stablesr_script.stablesr_model is None: return + if not len(self.stablesr_tensor_custom): return + self.stablesr_script.stablesr_model.latent_image = self.stablesr_tensor_custom[bbox_id] + + + @noise_inverse + def init_noise_inverse(self, steps:int, retouch:float, get_cache_callback, set_cache_callback, renoise_strength:float, renoise_kernel:int): + self.noise_inverse_enabled = True + self.noise_inverse_steps = steps + self.noise_inverse_retouch = float(retouch) + self.noise_inverse_renoise_strength = float(renoise_strength) + self.noise_inverse_renoise_kernel = int(renoise_kernel) + if self.sample_img2img_original is None: + self.sample_img2img_original = self.sampler_raw.sample_img2img + self.sampler_raw.sample_img2img = MethodType(self.sample_img2img, self.sampler_raw) + self.noise_inverse_set_cache = set_cache_callback + self.noise_inverse_get_cache = get_cache_callback + + @noise_inverse + @keep_signature + def sample_img2img(self, sampler: KDiffusionSampler, p:ProcessingImg2Img, + x:Tensor, noise:Tensor, conditioning, unconditional_conditioning, + steps=None, image_conditioning=None): + # noise inverse sampling - renoise mask + import torch.nn.functional as F + renoise_mask = None + if self.noise_inverse_renoise_strength > 0: + image = p.init_images[0] + # convert to grayscale with PIL + image = image.convert('L') + np_mask = get_retouch_mask(np.asarray(image), self.noise_inverse_renoise_kernel) + renoise_mask = torch.from_numpy(np_mask).to(noise.device) + # resize retouch mask to match noise size + renoise_mask = 1 - F.interpolate(renoise_mask.unsqueeze(0).unsqueeze(0), size=noise.shape[-2:], mode='bilinear').squeeze(0).squeeze(0) + renoise_mask *= self.noise_inverse_renoise_strength + renoise_mask = torch.clamp(renoise_mask, 0, 1) + + prompts = p.all_prompts[:p.batch_size] + + latent = None + # try to use cached latent to save huge amount of time. + cached_latent: NoiseInverseCache = self.noise_inverse_get_cache() + if cached_latent is not None and \ + cached_latent.model_hash == p.sd_model.sd_model_hash and \ + cached_latent.noise_inversion_steps == self.noise_inverse_steps and \ + len(cached_latent.prompts) == len(prompts) and \ + all([cached_latent.prompts[i] == prompts[i] for i in range(len(prompts))]) and \ + abs(cached_latent.retouch - self.noise_inverse_retouch) < 0.01 and \ + cached_latent.x0.shape == p.init_latent.shape and \ + torch.abs(cached_latent.x0.to(p.init_latent.device) - p.init_latent).sum() < 100: # the 100 is an arbitrary threshold copy-pasted from the img2img alt code + # use cached noise + print('[Tiled Diffusion] Your checkpoint, image, prompts, inverse steps, and retouch params are all unchanged.') + print('[Tiled Diffusion] Noise Inversion will use the cached noise from the previous run. To clear the cache, click the Free GPU button.') + latent = cached_latent.xt.to(noise.device) + if latent is None: + # run noise inversion + shared.state.job_count += 1 + latent = self.find_noise_for_image_sigma_adjustment(sampler.model_wrap, self.noise_inverse_steps, prompts) + shared.state.nextjob() + self.noise_inverse_set_cache(p.init_latent.clone().cpu(), latent.clone().cpu(), prompts) + # The cache is only 1 latent image and is very small (16 MB for 8192 * 8192 image), so we don't need to worry about memory leakage. + + # calculate sampling steps + adjusted_steps, _ = sd_samplers_common.setup_img2img_steps(p, steps) + sigmas = sampler.get_sigmas(p, adjusted_steps) + inverse_noise = latent - (p.init_latent / sigmas[0]) + + # inject noise to high-frequency area so that the details won't lose too much + if renoise_mask is not None: + # If the background is not drawn, we need to filter out the un-drawn pixels and reweight foreground with feather mask + # This is to enable the renoise mask in regional inpainting + if not self.enable_grid_bbox: + background_count = torch.zeros((1, 1, noise.shape[2], noise.shape[3]), device=noise.device) + foreground_noise = torch.zeros_like(noise) + foreground_weight = torch.zeros((1, 1, noise.shape[2], noise.shape[3]), device=noise.device) + foreground_count = torch.zeros((1, 1, noise.shape[2], noise.shape[3]), device=noise.device) + for bbox in self.custom_bboxes: + if bbox.blend_mode == BlendMode.BACKGROUND: + background_count[bbox.slicer] += 1 + elif bbox.blend_mode == BlendMode.FOREGROUND: + foreground_noise [bbox.slicer] += noise[bbox.slicer] + foreground_weight[bbox.slicer] += bbox.feather_mask + foreground_count [bbox.slicer] += 1 + background_noise = torch.where(background_count > 0, noise, 0) + foreground_noise = torch.where(foreground_count > 0, foreground_noise / foreground_count, 0) + foreground_weight = torch.where(foreground_count > 0, foreground_weight / foreground_count, 0) + noise = background_noise * (1 - foreground_weight) + foreground_noise * foreground_weight + del background_noise, foreground_noise, foreground_weight, background_count, foreground_count + combined_noise = ((1 - renoise_mask) * inverse_noise + renoise_mask * noise) / ((renoise_mask**2 + (1 - renoise_mask)**2) ** 0.5) + else: + combined_noise = inverse_noise + + # use the estimated noise for the original img2img sampling + return self.sample_img2img_original(p, x, combined_noise, conditioning, unconditional_conditioning, steps, image_conditioning) + + @noise_inverse + @torch.no_grad() + def find_noise_for_image_sigma_adjustment(self, dnw, steps, prompts:List[str]) -> Tensor: + ''' + Migrate from the built-in script img2imgalt.py + Tiled noise inverse for better image upscaling + ''' + import k_diffusion as K + assert self.p.sampler_name == 'Euler' + + x = self.p.init_latent + s_in = x.new_ones([x.shape[0]]) + skip = 1 if shared.sd_model.parameterization == "v" else 0 + sigmas = dnw.get_sigmas(steps).flip(0) + + cond = self.p.sd_model.get_learned_conditioning(prompts) + if isinstance(cond, Tensor): # SD1/SD2 + cond_dict_dummy = { + 'c_crossattn': [], # List[Tensor] + 'c_concat': [], # List[Tensor] + } + cond_in = self.make_cond_dict(cond_dict_dummy, cond, self.p.image_conditioning) + else: # SDXL + cond_dict_dummy = { + 'crossattn': None, # Tensor + 'vector': None, # Tensor + 'c_concat': [], # List[Tensor] + } + cond_in = self.make_cond_dict(cond_dict_dummy, cond['crossattn'], self.p.image_conditioning, cond['vector']) + + state.sampling_steps = steps + pbar = tqdm(total=steps, desc='Noise Inversion') + for i in range(1, len(sigmas)): + if state.interrupted: return x + + state.sampling_step += 1 + + x_in = x + sigma_in = torch.cat([sigmas[i] * s_in]) + c_out, c_in = [K.utils.append_dims(k, x_in.ndim) for k in dnw.get_scalings(sigma_in)[skip:]] + + t = dnw.sigma_to_t(sigma_in) + t = t / self.noise_inverse_retouch + + eps = self.get_noise(x_in * c_in, t, cond_in, steps - i) + denoised = x_in + eps * c_out + + # Euler method: + d = (x - denoised) / sigmas[i] + dt = sigmas[i] - sigmas[i - 1] + x = x + d * dt + + sd_samplers_common.store_latent(x) + + # This is neccessary to save memory before the next iteration + del x_in, sigma_in, c_out, c_in, t, + del eps, denoised, d, dt + + pbar.update(1) + pbar.close() + + return x / sigmas[-1] + + @noise_inverse + @torch.no_grad() + def get_noise(self, x_in: Tensor, sigma_in:Tensor, cond_in:Dict[str, Tensor], step:int) -> Tensor: + raise NotImplementedError diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/demofusion.py b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/demofusion.py new file mode 100644 index 0000000000000000000000000000000000000000..758ccfe0d13c6e92b878660a475905cf325a29fd --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/demofusion.py @@ -0,0 +1,353 @@ +from tile_methods.abstractdiffusion import AbstractDiffusion +from tile_utils.utils import * +import torch.nn.functional as F +import random +from copy import deepcopy +import inspect +from modules import sd_samplers_common + + +class DemoFusion(AbstractDiffusion): + """ + DemoFusion Implementation + https://arxiv.org/abs/2311.16973 + """ + + def __init__(self, p:Processing, *args, **kwargs): + super().__init__(p, *args, **kwargs) + assert p.sampler_name != 'UniPC', 'Demofusion is not compatible with UniPC!' + + + def hook(self): + steps, self.t_enc = sd_samplers_common.setup_img2img_steps(self.p, None) + + self.sampler.model_wrap_cfg.forward_ori = self.sampler.model_wrap_cfg.forward + self.sampler_forward = self.sampler.model_wrap_cfg.inner_model.forward + self.sampler.model_wrap_cfg.forward = self.forward_one_step + if self.is_kdiff: + self.sampler: KDiffusionSampler + self.sampler.model_wrap_cfg: CFGDenoiserKDiffusion + self.sampler.model_wrap_cfg.inner_model: Union[CompVisDenoiser, CompVisVDenoiser] + else: + self.sampler: CompVisSampler + self.sampler.model_wrap_cfg: CFGDenoiserTimesteps + self.sampler.model_wrap_cfg.inner_model: Union[CompVisTimestepsDenoiser, CompVisTimestepsVDenoiser] + self.timesteps = self.sampler.get_timesteps(self.p, steps) + + @staticmethod + def unhook(): + if hasattr(shared.sd_model, 'apply_model_ori'): + shared.sd_model.apply_model = shared.sd_model.apply_model_ori + del shared.sd_model.apply_model_ori + + def reset_buffer(self, x_in:Tensor): + super().reset_buffer(x_in) + + + + def repeat_tensor(self, x:Tensor, n:int) -> Tensor: + ''' repeat the tensor on it's first dim ''' + if n == 1: return x + B = x.shape[0] + r_dims = len(x.shape) - 1 + if B == 1: # batch_size = 1 (not `tile_batch_size`) + shape = [n] + [-1] * r_dims # [N, -1, ...] + return x.expand(shape) # `expand` is much lighter than `tile` + else: + shape = [n] + [1] * r_dims # [N, 1, ...] + return x.repeat(shape) + + def repeat_cond_dict(self, cond_in:CondDict, bboxes,mode) -> CondDict: + ''' repeat all tensors in cond_dict on it's first dim (for a batch of tiles), returns a new object ''' + # n_repeat + n_rep = len(bboxes) + # txt cond + tcond = self.get_tcond(cond_in) # [B=1, L, D] => [B*N, L, D] + tcond = self.repeat_tensor(tcond, n_rep) + # img cond + icond = self.get_icond(cond_in) + if icond.shape[2:] == (self.h, self.w): # img2img, [B=1, C, H, W] + if mode == 0: + if self.p.random_jitter: + jitter_range = self.jitter_range + icond = F.pad(icond,(jitter_range, jitter_range, jitter_range, jitter_range),'constant',value=0) + icond = torch.cat([icond[bbox.slicer] for bbox in bboxes], dim=0) + else: + icond = torch.cat([icond[:,:,bbox[1]::self.p.current_scale_num,bbox[0]::self.p.current_scale_num] for bbox in bboxes], dim=0) + else: # txt2img, [B=1, C=5, H=1, W=1] + icond = self.repeat_tensor(icond, n_rep) + + # vec cond (SDXL) + vcond = self.get_vcond(cond_in) # [B=1, D] + if vcond is not None: + vcond = self.repeat_tensor(vcond, n_rep) # [B*N, D] + return self.make_cond_dict(cond_in, tcond, icond, vcond) + + + def global_split_bboxes(self): + cols = self.p.current_scale_num + rows = cols + + bbox_list = [] + for row in range(rows): + y = row + for col in range(cols): + x = col + bbox = (x, y) + bbox_list.append(bbox) + + return bbox_list+bbox_list if self.p.mixture else bbox_list + + def split_bboxes_jitter(self,w_l:int, h_l:int, tile_w:int, tile_h:int, overlap:int=16, init_weight:Union[Tensor, float]=1.0) -> Tuple[List[BBox], Tensor]: + cols = math.ceil((w_l - overlap) / (tile_w - overlap)) + rows = math.ceil((h_l - overlap) / (tile_h - overlap)) + if rows==0: + rows=1 + if cols == 0: + cols=1 + dx = (w_l - tile_w) / (cols - 1) if cols > 1 else 0 + dy = (h_l - tile_h) / (rows - 1) if rows > 1 else 0 + bbox_list: List[BBox] = [] + self.jitter_range = 0 + for row in range(rows): + for col in range(cols): + h = min(int(row * dy), h_l - tile_h) + w = min(int(col * dx), w_l - tile_w) + if self.p.random_jitter: + self.jitter_range = min(max((min(self.w, self.h)-self.stride)//4,0),min(int(self.window_size/2),int(self.overlap/2))) + jitter_range = self.jitter_range + w_jitter = 0 + h_jitter = 0 + if (w != 0) and (w+tile_w != w_l): + w_jitter = random.randint(-jitter_range, jitter_range) + elif (w == 0) and (w + tile_w != w_l): + w_jitter = random.randint(-jitter_range, 0) + elif (w != 0) and (w + tile_w == w_l): + w_jitter = random.randint(0, jitter_range) + if (h != 0) and (h + tile_h != h_l): + h_jitter = random.randint(-jitter_range, jitter_range) + elif (h == 0) and (h + tile_h != h_l): + h_jitter = random.randint(-jitter_range, 0) + elif (h != 0) and (h + tile_h == h_l): + h_jitter = random.randint(0, jitter_range) + h +=(h_jitter + jitter_range) + w += (w_jitter + jitter_range) + + bbox = BBox(w, h, tile_w, tile_h) + bbox_list.append(bbox) + return bbox_list, None + + @grid_bbox + def get_views(self, overlap:int, tile_bs:int,tile_bs_g:int): + self.enable_grid_bbox = True + self.tile_w = self.window_size + self.tile_h = self.window_size + + self.overlap = max(0, min(overlap, self.window_size - 4)) + + self.stride = max(4,self.window_size - self.overlap) + + # split the latent into overlapped tiles, then batching + # weights basically indicate how many times a pixel is painted + bboxes, _ = self.split_bboxes_jitter(self.w, self.h, self.tile_w, self.tile_h, self.overlap, self.get_tile_weights()) + self.num_tiles = len(bboxes) + self.num_batches = math.ceil(self.num_tiles / tile_bs) + self.tile_bs = math.ceil(len(bboxes) / self.num_batches) # optimal_batch_size + self.batched_bboxes = [bboxes[i*self.tile_bs:(i+1)*self.tile_bs] for i in range(self.num_batches)] + + global_bboxes = self.global_split_bboxes() + self.global_num_tiles = len(global_bboxes) + self.global_num_batches = math.ceil(self.global_num_tiles / tile_bs_g) + self.global_tile_bs = math.ceil(len(global_bboxes) / self.global_num_batches) + self.global_batched_bboxes = [global_bboxes[i*self.global_tile_bs:(i+1)*self.global_tile_bs] for i in range(self.global_num_batches)] + + def gaussian_kernel(self,kernel_size=3, sigma=1.0, channels=3): + x_coord = torch.arange(kernel_size, device=devices.device) + gaussian_1d = torch.exp(-(x_coord - (kernel_size - 1) / 2) ** 2 / (2 * sigma ** 2)) + gaussian_1d = gaussian_1d / gaussian_1d.sum() + gaussian_2d = gaussian_1d[:, None] * gaussian_1d[None, :] + kernel = gaussian_2d[None, None, :, :].repeat(channels, 1, 1, 1) + + return kernel + + def gaussian_filter(self,latents, kernel_size=3, sigma=1.0): + channels = latents.shape[1] + kernel = self.gaussian_kernel(kernel_size, sigma, channels).to(latents.device, latents.dtype) + blurred_latents = F.conv2d(latents, kernel, padding=kernel_size//2, groups=channels) + + return blurred_latents + + + + ''' ↓↓↓ kernel hijacks ↓↓↓ ''' + @torch.no_grad() + @keep_signature + def forward_one_step(self, x_in, sigma, **kwarg): + if self.is_kdiff: + x_noisy = self.p.x + self.p.noise * sigma[0] + else: + alphas_cumprod = self.p.sd_model.alphas_cumprod + sqrt_alpha_cumprod = torch.sqrt(alphas_cumprod[self.timesteps[self.t_enc-self.p.current_step]]) + sqrt_one_minus_alpha_cumprod = torch.sqrt(1 - alphas_cumprod[self.timesteps[self.t_enc-self.p.current_step]]) + x_noisy = self.p.x*sqrt_alpha_cumprod + self.p.noise * sqrt_one_minus_alpha_cumprod + + self.cosine_factor = 0.5 * (1 + torch.cos(torch.pi *torch.tensor(((self.p.current_step + 1) / (self.t_enc+1))))) + + c1 = self.cosine_factor ** self.p.cosine_scale_1 + + x_in = x_in*(1 - c1) + x_noisy * c1 + + if self.p.random_jitter: + jitter_range = self.jitter_range + else: + jitter_range = 0 + x_in_ = F.pad(x_in,(jitter_range, jitter_range, jitter_range, jitter_range),'constant',value=0) + _,_,H,W = x_in.shape + + self.sampler.model_wrap_cfg.inner_model.forward = self.sample_one_step + self.repeat_3 = False + + x_out = self.sampler.model_wrap_cfg.forward_ori(x_in_,sigma, **kwarg) + self.sampler.model_wrap_cfg.inner_model.forward = self.sampler_forward + x_out = x_out[:,:,jitter_range:jitter_range+H,jitter_range:jitter_range+W] + + return x_out + + + @torch.no_grad() + @keep_signature + def sample_one_step(self, x_in, sigma, cond): + assert LatentDiffusion.apply_model + def repeat_func_1(x_tile:Tensor, bboxes,mode=0) -> Tensor: + sigma_tile = self.repeat_tensor(sigma, len(bboxes)) + cond_tile = self.repeat_cond_dict(cond, bboxes,mode) + return self.sampler_forward(x_tile, sigma_tile, cond=cond_tile) + + def repeat_func_2(x_tile:Tensor, bboxes,mode=0) -> Tuple[Tensor, Tensor]: + n_rep = len(bboxes) + ts_tile = self.repeat_tensor(sigma, n_rep) + if isinstance(cond, dict): # FIXME: when will enter this branch? + cond_tile = self.repeat_cond_dict(cond, bboxes,mode) + else: + cond_tile = self.repeat_tensor(cond, n_rep) + return self.sampler_forward(x_tile, ts_tile, cond=cond_tile) + + def repeat_func_3(x_tile:Tensor, bboxes,mode=0): + sigma_in_tile = sigma.repeat(len(bboxes)) + cond_out = self.repeat_cond_dict(cond, bboxes,mode) + x_tile_out = shared.sd_model.apply_model(x_tile, sigma_in_tile, cond=cond_out) + return x_tile_out + + if self.repeat_3: + repeat_func = repeat_func_3 + self.repeat_3 = False + elif self.is_kdiff: + repeat_func = repeat_func_1 + else: + repeat_func = repeat_func_2 + N,_,_,_ = x_in.shape + + + self.x_buffer = torch.zeros_like(x_in) + self.weights = torch.zeros_like(x_in) + + for batch_id, bboxes in enumerate(self.batched_bboxes): + if state.interrupted: return x_in + x_tile = torch.cat([x_in[bbox.slicer] for bbox in bboxes], dim=0) + x_tile_out = repeat_func(x_tile, bboxes) + # de-batching + for i, bbox in enumerate(bboxes): + self.x_buffer[bbox.slicer] += x_tile_out[i*N:(i+1)*N, :, :, :] + self.weights[bbox.slicer] += 1 + self.weights = torch.where(self.weights == 0, torch.tensor(1), self.weights) #Prevent NaN from appearing in random_jitter mode + + x_local = self.x_buffer/self.weights + + self.x_buffer = torch.zeros_like(self.x_buffer) + self.weights = torch.zeros_like(self.weights) + + std_, mean_ = x_in.std(), x_in.mean() + c3 = 0.99 * self.cosine_factor ** self.p.cosine_scale_3 + 1e-2 + if self.p.gaussian_filter: + x_in_g = self.gaussian_filter(x_in, kernel_size=(2*self.p.current_scale_num-1), sigma=self.sig*c3) + x_in_g = (x_in_g - x_in_g.mean()) / x_in_g.std() * std_ + mean_ + + if not hasattr(self.p.sd_model, 'apply_model_ori'): + self.p.sd_model.apply_model_ori = self.p.sd_model.apply_model + self.p.sd_model.apply_model = self.apply_model_hijack + x_global = torch.zeros_like(x_local) + jitter_range = self.jitter_range + end = x_global.shape[3]-jitter_range + + current_num = 0 + if self.p.mixture: + for batch_id, bboxes in enumerate(self.global_batched_bboxes): + current_num += len(bboxes) + if current_num > (self.global_num_tiles//2) and (current_num-self.global_tile_bs) < (self.global_num_tiles//2): + res = len(bboxes) - (current_num - self.global_num_tiles//2) + x_in_i = torch.cat([x_in[:,:,bbox[1]+jitter_range:end:self.p.current_scale_num,bbox[0]+jitter_range:end:self.p.current_scale_num] if idx (self.global_num_tiles//2): + x_in_i = torch.cat([x_in_g[:,:,bbox[1]+jitter_range:end:self.p.current_scale_num,bbox[0]+jitter_range:end:self.p.current_scale_num] for bbox in bboxes],dim=0) + else: + x_in_i = torch.cat([x_in[:,:,bbox[1]+jitter_range:end:self.p.current_scale_num,bbox[0]+jitter_range:end:self.p.current_scale_num] for bbox in bboxes],dim=0) + + x_global_i = repeat_func(x_in_i,bboxes,mode=1) + + if current_num > (self.global_num_tiles//2) and (current_num-self.global_tile_bs) < (self.global_num_tiles//2): + for idx,bbox in enumerate(bboxes): + x_global[:,:,bbox[1]+jitter_range:end:self.p.current_scale_num,bbox[0]+jitter_range:end:self.p.current_scale_num] += x_global_i[idx*N:(idx+1)*N,:,:,:] + elif current_num > (self.global_num_tiles//2): + for idx,bbox in enumerate(bboxes): + x_global[:,:,bbox[1]+jitter_range:end:self.p.current_scale_num,bbox[0]+jitter_range:end:self.p.current_scale_num] += x_global_i[idx*N:(idx+1)*N,:,:,:] + else: + for idx,bbox in enumerate(bboxes): + x_global[:,:,bbox[1]+jitter_range:end:self.p.current_scale_num,bbox[0]+jitter_range:end:self.p.current_scale_num] += x_global_i[idx*N:(idx+1)*N,:,:,:] + else: + for batch_id, bboxes in enumerate(self.global_batched_bboxes): + x_in_i = torch.cat([x_in_g[:,:,bbox[1]+jitter_range:end:self.p.current_scale_num,bbox[0]+jitter_range:end:self.p.current_scale_num] for bbox in bboxes],dim=0) + x_global_i = repeat_func(x_in_i,bboxes,mode=1) + for idx,bbox in enumerate(bboxes): + x_global[:,:,bbox[1]+jitter_range:end:self.p.current_scale_num,bbox[0]+jitter_range:end:self.p.current_scale_num] += x_global_i[idx*N:(idx+1)*N,:,:,:] + #NOTE According to the original execution process, it would be very strange to use the predicted noise of gaussian latents to predict the denoised data in non Gaussian latents. Why? + if self.p.mixture: + self.x_buffer +=x_global/2 + else: + self.x_buffer += x_global + self.weights += 1 + + self.p.sd_model.apply_model = self.p.sd_model.apply_model_ori + + x_global = self.x_buffer/self.weights + c2 = self.cosine_factor**self.p.cosine_scale_2 + self.x_buffer= x_local*(1-c2)+ x_global*c2 + + return self.x_buffer + + + + @torch.no_grad() + @keep_signature + def apply_model_hijack(self, x_in:Tensor, t_in:Tensor, cond:CondDict): + assert LatentDiffusion.apply_model + + x_tile_out = self.p.sd_model.apply_model_ori(x_in,t_in,cond) + return x_tile_out + # NOTE: Using Gaussian Latent to Predict Noise on the Original Latent + # if self.flag == 1: + # x_tile_out = self.p.sd_model.apply_model_ori(x_in,t_in,cond) + # self.x_out_list.append(x_tile_out) + # return x_tile_out + # else: + # self.x_out_idx += 1 + # return self.x_out_list[self.x_out_idx] + + + def get_noise(self, x_in:Tensor, sigma_in:Tensor, cond_in:Dict[str, Tensor], step:int) -> Tensor: + # NOTE: The following code is analytically wrong but aesthetically beautiful + cond_in_original = cond_in.copy() + self.repeat_3 = True + self.cosine_factor = 0.5 * (1 + torch.cos(torch.pi *torch.tensor(((self.p.current_step + 1) / (self.t_enc+1))))) + jitter_range = self.jitter_range + _,_,H,W = x_in.shape + x_in_ = F.pad(x_in,(jitter_range, jitter_range, jitter_range, jitter_range),'constant',value=0) + return self.sample_one_step(x_in_, sigma_in, cond_in_original)[:,:,jitter_range:jitter_range+H,jitter_range:jitter_range+W] diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/mixtureofdiffusers.py b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/mixtureofdiffusers.py new file mode 100644 index 0000000000000000000000000000000000000000..a7203897fbca1a99d8fd7058431e89f277a099a0 --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/mixtureofdiffusers.py @@ -0,0 +1,200 @@ +from tile_methods.abstractdiffusion import AbstractDiffusion +from tile_utils.utils import * + + +class MixtureOfDiffusers(AbstractDiffusion): + """ + Mixture-of-Diffusers Implementation + https://github.com/albarji/mixture-of-diffusers + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # weights for custom bboxes + self.custom_weights: List[Tensor] = [] + self.get_weight = gaussian_weights + + def hook(self): + if not hasattr(shared.sd_model, 'apply_model_original_md'): + shared.sd_model.apply_model_original_md = shared.sd_model.apply_model + shared.sd_model.apply_model = self.apply_model_hijack + + @staticmethod + def unhook(): + if hasattr(shared.sd_model, 'apply_model_original_md'): + shared.sd_model.apply_model = shared.sd_model.apply_model_original_md + del shared.sd_model.apply_model_original_md + + def init_done(self): + super().init_done() + # The original gaussian weights can be extremely small, so we rescale them for numerical stability + self.rescale_factor = 1 / self.weights + # Meanwhile, we rescale the custom weights in advance to save time of slicing + for bbox_id, bbox in enumerate(self.custom_bboxes): + if bbox.blend_mode == BlendMode.BACKGROUND: + self.custom_weights[bbox_id] *= self.rescale_factor[bbox.slicer] + + @grid_bbox + def get_tile_weights(self) -> Tensor: + # weights for grid bboxes + if not hasattr(self, 'tile_weights'): + self.tile_weights = self.get_weight(self.tile_w, self.tile_h) + return self.tile_weights + + @custom_bbox + def init_custom_bbox(self, *args): + super().init_custom_bbox(*args) + + for bbox in self.custom_bboxes: + if bbox.blend_mode == BlendMode.BACKGROUND: + custom_weights = self.get_weight(bbox.w, bbox.h) + self.weights[bbox.slicer] += custom_weights + self.custom_weights.append(custom_weights.unsqueeze(0).unsqueeze(0)) + else: + self.custom_weights.append(None) + + ''' ↓↓↓ kernel hijacks ↓↓↓ ''' + + @torch.no_grad() + @keep_signature + def apply_model_hijack(self, x_in:Tensor, t_in:Tensor, cond:CondDict, noise_inverse_step:int=-1): + assert LatentDiffusion.apply_model + + # KDiffusion Compatibility for naming + c_in: CondDict = cond + + N, C, H, W = x_in.shape + if (H, W) != (self.h, self.w): + # We don't tile highres, let's just use the original apply_model + self.reset_controlnet_tensors() + return shared.sd_model.apply_model_original_md(x_in, t_in, c_in) + + # clear buffer canvas + self.reset_buffer(x_in) + + # Global sampling + if self.draw_background: + for batch_id, bboxes in enumerate(self.batched_bboxes): # batch_id is the `Latent tile batch size` + if state.interrupted: return x_in + + # batching + x_tile_list = [] + t_tile_list = [] + tcond_tile_list = [] + icond_tile_list = [] + vcond_tile_list = [] + for bbox in bboxes: + x_tile_list.append(x_in[bbox.slicer]) + t_tile_list.append(t_in) + if isinstance(c_in, dict): + # tcond + tcond_tile = self.get_tcond(c_in) # cond, [1, 77, 768] + tcond_tile_list.append(tcond_tile) + # icond: might be dummy for txt2img, latent mask for img2img + icond = self.get_icond(c_in) + if icond.shape[2:] == (self.h, self.w): + icond = icond[bbox.slicer] + icond_tile_list.append(icond) + # vcond: + vcond = self.get_vcond(c_in) + vcond_tile_list.append(vcond) + else: + print('>> [WARN] not supported, make an issue on github!!') + x_tile = torch.cat(x_tile_list, dim=0) # differs each + t_tile = torch.cat(t_tile_list, dim=0) # just repeat + tcond_tile = torch.cat(tcond_tile_list, dim=0) # just repeat + icond_tile = torch.cat(icond_tile_list, dim=0) # differs each + vcond_tile = torch.cat(vcond_tile_list, dim=0) if None not in vcond_tile_list else None # just repeat + + c_tile = self.make_cond_dict(c_in, tcond_tile, icond_tile, vcond_tile) + + # controlnet + self.switch_controlnet_tensors(batch_id, N, len(bboxes), is_denoise=True) + + # stablesr + self.switch_stablesr_tensors(batch_id) + + # denoising: here the x is the noise + x_tile_out = shared.sd_model.apply_model_original_md(x_tile, t_tile, c_tile) + + # de-batching + for i, bbox in enumerate(bboxes): + # This weights can be calcluated in advance, but will cost a lot of vram + # when you have many tiles. So we calculate it here. + w = self.tile_weights * self.rescale_factor[bbox.slicer] + self.x_buffer[bbox.slicer] += x_tile_out[i*N:(i+1)*N, :, :, :] * w + + self.update_pbar() + + # Custom region sampling + x_feather_buffer = None + x_feather_mask = None + x_feather_count = None + if len(self.custom_bboxes) > 0: + for bbox_id, bbox in enumerate(self.custom_bboxes): + if not self.p.disable_extra_networks: + with devices.autocast(): + extra_networks.activate(self.p, bbox.extra_network_data) + + x_tile = x_in[bbox.slicer] + if noise_inverse_step < 0: + x_tile_out = self.custom_apply_model(x_tile, t_in, c_in, bbox_id, bbox) + else: + tcond = Condition.reconstruct_cond(bbox.cond, noise_inverse_step) + icond = self.get_icond(c_in) + if icond.shape[2:] == (self.h, self.w): + icond = icond[bbox.slicer] + vcond = self.get_vcond(c_in) + c_out = self.make_cond_dict(c_in, tcond, icond, vcond) + x_tile_out = shared.sd_model.apply_model(x_tile, t_in, cond=c_out) + + if bbox.blend_mode == BlendMode.BACKGROUND: + self.x_buffer[bbox.slicer] += x_tile_out * self.custom_weights[bbox_id] + elif bbox.blend_mode == BlendMode.FOREGROUND: + if x_feather_buffer is None: + x_feather_buffer = torch.zeros_like(self.x_buffer) + x_feather_mask = torch.zeros((1, 1, H, W), device=self.x_buffer.device) + x_feather_count = torch.zeros((1, 1, H, W), device=self.x_buffer.device) + x_feather_buffer[bbox.slicer] += x_tile_out + x_feather_mask [bbox.slicer] += bbox.feather_mask + x_feather_count [bbox.slicer] += 1 + + self.update_pbar() + + if not self.p.disable_extra_networks: + with devices.autocast(): + extra_networks.deactivate(self.p, bbox.extra_network_data) + + x_out = self.x_buffer + if x_feather_buffer is not None: + # Average overlapping feathered regions + x_feather_buffer = torch.where(x_feather_count > 1, x_feather_buffer / x_feather_count, x_feather_buffer) + x_feather_mask = torch.where(x_feather_count > 1, x_feather_mask / x_feather_count, x_feather_mask) + # Weighted average with original x_buffer + x_out = torch.where(x_feather_count > 0, x_out * (1 - x_feather_mask) + x_feather_buffer * x_feather_mask, x_out) + + # For mixture of diffusers, we cannot fill the not denoised area. + # So we just leave it as it is. + return x_out + + def custom_apply_model(self, x_in, t_in, c_in, bbox_id, bbox) -> Tensor: + if self.is_kdiff: + return self.kdiff_custom_forward(x_in, t_in, c_in, bbox_id, bbox, forward_func=shared.sd_model.apply_model_original_md) + else: + def forward_func(x, c, ts, unconditional_conditioning, *args, **kwargs) -> Tensor: + # copy from p_sample_ddim in ddim.py + c_in: CondDict = dict() + for k in c: + if isinstance(c[k], list): + c_in[k] = [torch.cat([unconditional_conditioning[k][i], c[k][i]]) for i in range(len(c[k]))] + else: + c_in[k] = torch.cat([unconditional_conditioning[k], c[k]]) + self.set_custom_controlnet_tensors(bbox_id, x.shape[0]) + self.set_custom_stablesr_tensors(bbox_id) + return shared.sd_model.apply_model_original_md(x, ts, c_in) + return self.ddim_custom_forward(x_in, c_in, bbox, ts=t_in, forward_func=forward_func) + + @torch.no_grad() + def get_noise(self, x_in:Tensor, sigma_in:Tensor, cond_in:Dict[str, Tensor], step:int) -> Tensor: + return self.apply_model_hijack(x_in, sigma_in, cond=cond_in, noise_inverse_step=step) diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/multidiffusion.py b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/multidiffusion.py new file mode 100644 index 0000000000000000000000000000000000000000..026fc18f1b213afde439d013d852f45ce8ec62a3 --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_methods/multidiffusion.py @@ -0,0 +1,243 @@ +from tile_methods.abstractdiffusion import AbstractDiffusion +from tile_utils.utils import * + + +class MultiDiffusion(AbstractDiffusion): + """ + Multi-Diffusion Implementation + https://arxiv.org/abs/2302.08113 + """ + + def __init__(self, p:Processing, *args, **kwargs): + super().__init__(p, *args, **kwargs) + assert p.sampler_name != 'UniPC', 'MultiDiffusion is not compatible with UniPC!' + + def hook(self): + if self.is_kdiff: + # For K-Diffusion sampler with uniform prompt, we hijack into the inner model for simplicity + # Otherwise, the masked-redraw will break due to the init_latent + self.sampler: KDiffusionSampler + self.sampler.model_wrap_cfg: CFGDenoiserKDiffusion + self.sampler.model_wrap_cfg.inner_model: Union[CompVisDenoiser, CompVisVDenoiser] + self.sampler_forward = self.sampler.model_wrap_cfg.inner_model.forward + self.sampler.model_wrap_cfg.inner_model.forward = self.kdiff_forward + else: + self.sampler: CompVisSampler + self.sampler.model_wrap_cfg: CFGDenoiserTimesteps + self.sampler.model_wrap_cfg.inner_model: Union[CompVisTimestepsDenoiser, CompVisTimestepsVDenoiser] + self.sampler_forward = self.sampler.model_wrap_cfg.inner_model.forward + self.sampler.model_wrap_cfg.inner_model.forward = self.ddim_forward + + @staticmethod + def unhook(): + # no need to unhook MultiDiffusion as it only hook the sampler, + # which will be destroyed after the painting is done + pass + + def reset_buffer(self, x_in:Tensor): + super().reset_buffer(x_in) + + @custom_bbox + def init_custom_bbox(self, *args): + super().init_custom_bbox(*args) + + for bbox in self.custom_bboxes: + if bbox.blend_mode == BlendMode.BACKGROUND: + self.weights[bbox.slicer] += 1.0 + + ''' ↓↓↓ kernel hijacks ↓↓↓ ''' + + @torch.no_grad() + @keep_signature + def kdiff_forward(self, x_in:Tensor, sigma_in:Tensor, cond:CondDict) -> Tensor: + assert CompVisDenoiser.forward + assert CompVisVDenoiser.forward + + def org_func(x:Tensor) -> Tensor: + return self.sampler_forward(x, sigma_in, cond=cond) + + def repeat_func(x_tile:Tensor, bboxes:List[CustomBBox]) -> Tensor: + # For kdiff sampler, the dim 0 of input x_in is: + # = batch_size * (num_AND + 1) if not an edit model + # = batch_size * (num_AND + 2) otherwise + sigma_tile = self.repeat_tensor(sigma_in, len(bboxes)) + cond_tile = self.repeat_cond_dict(cond, bboxes) + return self.sampler_forward(x_tile, sigma_tile, cond=cond_tile) + + def custom_func(x:Tensor, bbox_id:int, bbox:CustomBBox) -> Tensor: + return self.kdiff_custom_forward(x, sigma_in, cond, bbox_id, bbox, self.sampler_forward) + + return self.sample_one_step(x_in, org_func, repeat_func, custom_func) + + @torch.no_grad() + @keep_signature + def ddim_forward(self, x_in:Tensor, ts_in:Tensor, cond:Union[CondDict, Tensor]) -> Tensor: + assert CompVisTimestepsDenoiser.forward + assert CompVisTimestepsVDenoiser.forward + + def org_func(x:Tensor) -> Tensor: + return self.sampler_forward(x, ts_in, cond=cond) + + def repeat_func(x_tile:Tensor, bboxes:List[CustomBBox]) -> Tuple[Tensor, Tensor]: + n_rep = len(bboxes) + ts_tile = self.repeat_tensor(ts_in, n_rep) + if isinstance(cond, dict): # FIXME: when will enter this branch? + cond_tile = self.repeat_cond_dict(cond, bboxes) + else: + cond_tile = self.repeat_tensor(cond, n_rep) + return self.sampler_forward(x_tile, ts_tile, cond=cond_tile) + + def custom_func(x:Tensor, bbox_id:int, bbox:CustomBBox) -> Tensor: + # before the final forward, we can set the control tensor + def forward_func(x, *args, **kwargs): + self.set_custom_controlnet_tensors(bbox_id, 2*x.shape[0]) + self.set_custom_stablesr_tensors(bbox_id) + return self.sampler_forward(x, *args, **kwargs) + return self.ddim_custom_forward(x, cond, bbox, ts_in, forward_func) + + return self.sample_one_step(x_in, org_func, repeat_func, custom_func) + + def repeat_tensor(self, x:Tensor, n:int) -> Tensor: + ''' repeat the tensor on it's first dim ''' + if n == 1: return x + B = x.shape[0] + r_dims = len(x.shape) - 1 + if B == 1: # batch_size = 1 (not `tile_batch_size`) + shape = [n] + [-1] * r_dims # [N, -1, ...] + return x.expand(shape) # `expand` is much lighter than `tile` + else: + shape = [n] + [1] * r_dims # [N, 1, ...] + return x.repeat(shape) + + def repeat_cond_dict(self, cond_in:CondDict, bboxes:List[CustomBBox]) -> CondDict: + ''' repeat all tensors in cond_dict on it's first dim (for a batch of tiles), returns a new object ''' + # n_repeat + n_rep = len(bboxes) + # txt cond + tcond = self.get_tcond(cond_in) # [B=1, L, D] => [B*N, L, D] + tcond = self.repeat_tensor(tcond, n_rep) + # img cond + icond = self.get_icond(cond_in) + if icond.shape[2:] == (self.h, self.w): # img2img, [B=1, C, H, W] + icond = torch.cat([icond[bbox.slicer] for bbox in bboxes], dim=0) + else: # txt2img, [B=1, C=5, H=1, W=1] + icond = self.repeat_tensor(icond, n_rep) + # vec cond (SDXL) + vcond = self.get_vcond(cond_in) # [B=1, D] + if vcond is not None: + vcond = self.repeat_tensor(vcond, n_rep) # [B*N, D] + return self.make_cond_dict(cond_in, tcond, icond, vcond) + + def sample_one_step(self, x_in:Tensor, org_func:Callable, repeat_func:Callable, custom_func:Callable) -> Tensor: + ''' + this method splits the whole latent and process in tiles + - x_in: current whole U-Net latent + - org_func: original forward function, when use highres + - repeat_func: one step denoiser for grid tile + - custom_func: one step denoiser for custom tile + ''' + + N, C, H, W = x_in.shape + if (H, W) != (self.h, self.w): + # We don't tile highres, let's just use the original org_func + self.reset_controlnet_tensors() + return org_func(x_in) + + # clear buffer canvas + self.reset_buffer(x_in) + + # Background sampling (grid bbox) + if self.draw_background: + for batch_id, bboxes in enumerate(self.batched_bboxes): + if state.interrupted: return x_in + + # batching + x_tile = torch.cat([x_in[bbox.slicer] for bbox in bboxes], dim=0) # [TB, C, TH, TW] + + # controlnet tiling + # FIXME: is_denoise is default to False, however it is set to True in case of MixtureOfDiffusers, why? + self.switch_controlnet_tensors(batch_id, N, len(bboxes)) + + # stablesr tiling + self.switch_stablesr_tensors(batch_id) + + # compute tiles + x_tile_out = repeat_func(x_tile, bboxes) + for i, bbox in enumerate(bboxes): + self.x_buffer[bbox.slicer] += x_tile_out[i*N:(i+1)*N, :, :, :] + + # update progress bar + self.update_pbar() + + # Custom region sampling (custom bbox) + x_feather_buffer = None + x_feather_mask = None + x_feather_count = None + if len(self.custom_bboxes) > 0: + for bbox_id, bbox in enumerate(self.custom_bboxes): + if state.interrupted: return x_in + + if not self.p.disable_extra_networks: + with devices.autocast(): + extra_networks.activate(self.p, bbox.extra_network_data) + + x_tile = x_in[bbox.slicer] + + # retrieve original x_in from construncted input + x_tile_out = custom_func(x_tile, bbox_id, bbox) + + if bbox.blend_mode == BlendMode.BACKGROUND: + self.x_buffer[bbox.slicer] += x_tile_out + elif bbox.blend_mode == BlendMode.FOREGROUND: + if x_feather_buffer is None: + x_feather_buffer = torch.zeros_like(self.x_buffer) + x_feather_mask = torch.zeros((1, 1, H, W), device=x_in.device) + x_feather_count = torch.zeros((1, 1, H, W), device=x_in.device) + x_feather_buffer[bbox.slicer] += x_tile_out + x_feather_mask [bbox.slicer] += bbox.feather_mask + x_feather_count [bbox.slicer] += 1 + + if not self.p.disable_extra_networks: + with devices.autocast(): + extra_networks.deactivate(self.p, bbox.extra_network_data) + + # update progress bar + self.update_pbar() + + # Averaging background buffer + x_out = torch.where(self.weights > 1, self.x_buffer / self.weights, self.x_buffer) + + # Foreground Feather blending + if x_feather_buffer is not None: + # Average overlapping feathered regions + x_feather_buffer = torch.where(x_feather_count > 1, x_feather_buffer / x_feather_count, x_feather_buffer) + x_feather_mask = torch.where(x_feather_count > 1, x_feather_mask / x_feather_count, x_feather_mask) + # Weighted average with original x_buffer + x_out = torch.where(x_feather_count > 0, x_out * (1 - x_feather_mask) + x_feather_buffer * x_feather_mask, x_out) + + return x_out + + def get_noise(self, x_in:Tensor, sigma_in:Tensor, cond_in:Dict[str, Tensor], step:int) -> Tensor: + # NOTE: The following code is analytically wrong but aesthetically beautiful + cond_in_original = cond_in.copy() + + def org_func(x:Tensor): + return shared.sd_model.apply_model(x, sigma_in, cond=cond_in_original) + + def repeat_func(x_tile:Tensor, bboxes:List[CustomBBox]): + sigma_in_tile = sigma_in.repeat(len(bboxes)) + cond_out = self.repeat_cond_dict(cond_in_original, bboxes) + x_tile_out = shared.sd_model.apply_model(x_tile, sigma_in_tile, cond=cond_out) + return x_tile_out + + def custom_func(x:Tensor, bbox_id:int, bbox:CustomBBox): + # The negative prompt in custom bbox should not be used for noise inversion + # otherwise the result will be astonishingly bad. + tcond = Condition.reconstruct_cond(bbox.cond, step).unsqueeze_(0) + icond = self.get_icond(cond_in_original) + if icond.shape[2:] == (self.h, self.w): + icond = icond[bbox.slicer] + cond_out = self.make_cond_dict(cond_in, tcond, icond) + return shared.sd_model.apply_model(x, sigma_in, cond=cond_out) + + return self.sample_one_step(x_in, org_func, repeat_func, custom_func) diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/__pycache__/attn.cpython-310.pyc b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/__pycache__/attn.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54e6b932dc0b2834cadcd29ed09df27cd372f828 Binary files /dev/null and b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/__pycache__/attn.cpython-310.pyc differ diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/__pycache__/typing.cpython-310.pyc b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/__pycache__/typing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13c2b59da826974a8865f3d1fd4573ceb2631aa9 Binary files /dev/null and b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/__pycache__/typing.cpython-310.pyc differ diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/__pycache__/utils.cpython-310.pyc b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af24d21b5c4db97dae0a03e619e65a7adf3a474e Binary files /dev/null and b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/__pycache__/utils.cpython-310.pyc differ diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/attn.py b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/attn.py new file mode 100644 index 0000000000000000000000000000000000000000..5a74a36b9efd6b4961cdaddd782bc28725c06552 --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/attn.py @@ -0,0 +1,183 @@ +''' + This file is modified from the sd_hijack_optimizations.py to remove the residual and norm part, + So that the Tiled VAE can support other types of attention. +''' +import math +import torch + +from modules import shared, sd_hijack +from einops import rearrange +from modules.sd_hijack_optimizations import get_available_vram, get_xformers_flash_attention_op, sub_quad_attention + +try: + import xformers + import xformers.ops +except ImportError: + pass + + +def get_attn_func(): + method = sd_hijack.model_hijack.optimization_method + if method is None: + return attn_forward + method = method.lower() + # The method should be one of the following: + # ['none', 'sdp-no-mem', 'sdp', 'xformers', ''sub-quadratic', 'v1', 'invokeai', 'doggettx'] + if method not in ['none', 'sdp-no-mem', 'sdp', 'xformers', 'sub-quadratic', 'v1', 'invokeai', 'doggettx']: + print(f"[Tiled VAE] Warning: Unknown attention optimization method {method}. Please try to update the extension.") + return attn_forward + + if method == 'none': + return attn_forward + elif method == 'xformers': + return xformers_attnblock_forward + elif method == 'sdp-no-mem': + return sdp_no_mem_attnblock_forward + elif method == 'sdp': + return sdp_attnblock_forward + elif method == 'sub-quadratic': + return sub_quad_attnblock_forward + elif method == 'doggettx': + return cross_attention_attnblock_forward + + return attn_forward + + +# The following functions are all copied from modules.sd_hijack_optimizations +# However, the residual & normalization are removed and computed separately. + +def attn_forward(self, h_): + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + + # compute attention + b, c, h, w = q.shape + q = q.reshape(b, c, h*w) + q = q.permute(0, 2, 1) # b,hw,c + k = k.reshape(b, c, h*w) # b,c,hw + w_ = torch.bmm(q, k) # b,hw,hw w[b,i,j]=sum_c q[b,i,c]k[b,c,j] + w_ = w_ * (int(c)**(-0.5)) + w_ = torch.nn.functional.softmax(w_, dim=2) + + # attend to values + v = v.reshape(b, c, h*w) + w_ = w_.permute(0, 2, 1) # b,hw,hw (first hw of k, second of q) + # b, c,hw (hw of q) h_[b,c,j] = sum_i v[b,c,i] w_[b,i,j] + h_ = torch.bmm(v, w_) + h_ = h_.reshape(b, c, h, w) + + h_ = self.proj_out(h_) + + return h_ + +def xformers_attnblock_forward(self, h_): + try: + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + b, c, h, w = q.shape + q, k, v = map(lambda t: rearrange(t, 'b c h w -> b (h w) c'), (q, k, v)) + dtype = q.dtype + if shared.opts.upcast_attn: + q, k, v = q.float(), k.float(), v.float() + q = q.contiguous() + k = k.contiguous() + v = v.contiguous() + out = xformers.ops.memory_efficient_attention(q, k, v, op=get_xformers_flash_attention_op(q, k, v)) + out = out.to(dtype) + out = rearrange(out, 'b (h w) c -> b c h w', h=h) + out = self.proj_out(out) + return out + except NotImplementedError: + return cross_attention_attnblock_forward(self, h_) + +def cross_attention_attnblock_forward(self, h_): + q1 = self.q(h_) + k1 = self.k(h_) + v = self.v(h_) + + # compute attention + b, c, h, w = q1.shape + + q2 = q1.reshape(b, c, h*w) + del q1 + + q = q2.permute(0, 2, 1) # b,hw,c + del q2 + + k = k1.reshape(b, c, h*w) # b,c,hw + del k1 + + h_ = torch.zeros_like(k, device=q.device) + + mem_free_total = get_available_vram() + + tensor_size = q.shape[0] * q.shape[1] * k.shape[2] * q.element_size() + mem_required = tensor_size * 2.5 + steps = 1 + + if mem_required > mem_free_total: + steps = 2**(math.ceil(math.log(mem_required / mem_free_total, 2))) + + slice_size = q.shape[1] // steps if (q.shape[1] % steps) == 0 else q.shape[1] + for i in range(0, q.shape[1], slice_size): + end = i + slice_size + + w1 = torch.bmm(q[:, i:end], k) # b,hw,hw w[b,i,j]=sum_c q[b,i,c]k[b,c,j] + w2 = w1 * (int(c)**(-0.5)) + del w1 + w3 = torch.nn.functional.softmax(w2, dim=2, dtype=q.dtype) + del w2 + + # attend to values + v1 = v.reshape(b, c, h*w) + w4 = w3.permute(0, 2, 1) # b,hw,hw (first hw of k, second of q) + del w3 + + h_[:, :, i:end] = torch.bmm(v1, w4) # b, c,hw (hw of q) h_[b,c,j] = sum_i v[b,c,i] w_[b,i,j] + del v1, w4 + + h2 = h_.reshape(b, c, h, w) + del h_ + + h3 = self.proj_out(h2) + del h2 + + return h3 + +def sdp_no_mem_attnblock_forward(self, x): + with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=True, enable_mem_efficient=False): + return sdp_attnblock_forward(self, x) + +def sdp_attnblock_forward(self, h_): + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + b, c, h, w = q.shape + q, k, v = map(lambda t: rearrange(t, 'b c h w -> b (h w) c'), (q, k, v)) + dtype = q.dtype + if shared.opts.upcast_attn: + q, k, v = q.float(), k.float(), v.float() + q = q.contiguous() + k = k.contiguous() + v = v.contiguous() + out = torch.nn.functional.scaled_dot_product_attention(q, k, v, dropout_p=0.0, is_causal=False) + out = out.to(dtype) + out = rearrange(out, 'b (h w) c -> b c h w', h=h) + out = self.proj_out(out) + return out + +def sub_quad_attnblock_forward(self, h_): + q = self.q(h_) + k = self.k(h_) + v = self.v(h_) + b, c, h, w = q.shape + q, k, v = map(lambda t: rearrange(t, 'b c h w -> b (h w) c'), (q, k, v)) + q = q.contiguous() + k = k.contiguous() + v = v.contiguous() + out = sub_quad_attention(q, k, v, q_chunk_size=shared.cmd_opts.sub_quad_q_chunk_size, kv_chunk_size=shared.cmd_opts.sub_quad_kv_chunk_size, chunk_threshold=shared.cmd_opts.sub_quad_chunk_threshold, use_checkpoint=self.training) + out = rearrange(out, 'b (h w) c -> b c h w', h=h) + out = self.proj_out(out) + return out diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/typing.py b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/typing.py new file mode 100644 index 0000000000000000000000000000000000000000..625363ea3072fd2279836aab8f718388c38e0684 --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/typing.py @@ -0,0 +1,44 @@ +import sys +from typing import * +NoType = Any + +from torch import Tensor +from gradio.components import Component + +from k_diffusion.external import CompVisDenoiser, CompVisVDenoiser +from ldm.models.diffusion.ddpm import LatentDiffusion + +from modules.processing import StableDiffusionProcessing as Processing, StableDiffusionProcessingImg2Img as ProcessingImg2Img, Processed +from modules.prompt_parser import MulticondLearnedConditioning, ScheduledPromptConditioning +from modules.extra_networks import ExtraNetworkParams +from modules.sd_samplers_kdiffusion import KDiffusionSampler, CFGDenoiser +# ↓↓↓ backward compatible for v1.5.2 ↓↓↓ +try: + from modules.shared_state import State +except ImportError: + from modules.shared import State +try: + from modules.sd_samplers_kdiffusion import CFGDenoiserKDiffusion +except ImportError: + CFGDenoiserKDiffusion = NoType +try: + from modules.sd_samplers_timesteps import CompVisSampler, CFGDenoiserTimesteps, CompVisTimestepsDenoiser, CompVisTimestepsVDenoiser +except ImportError: + from modules.sd_samplers_compvis import VanillaStableDiffusionSampler + CompVisSampler = VanillaStableDiffusionSampler + CFGDenoiserTimesteps, CompVisTimestepsDenoiser, CompVisTimestepsVDenoiser = NoType, NoType, NoType +# ↑↑↑ backward compatible for v1.5.2 ↑↑↑ + +ModuleType = type(sys) + +Sampler = Union[KDiffusionSampler, CompVisSampler] +Cond = MulticondLearnedConditioning +Uncond = List[List[ScheduledPromptConditioning]] +ExtraNetworkData = DefaultDict[str, List[ExtraNetworkParams]] + +# 'c_crossattn' List[Tensor[B, L=77, D=768]] prompt cond (tcond) +# 'c_concat' List[Tensor[B, C=5, H, W]] latent mask (icond) +# 'c_adm' Tensor[?] unclip (icond) +# 'crossattn' Tensor[B, L=77, D=2048] sdxl (tcond) +# 'vector' Tensor[B, D] sdxl (tcond) +CondDict = Dict[str, Tensor] diff --git a/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/utils.py b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ef8497eebade80d57e2c9e163b114db39661eb94 --- /dev/null +++ b/extensions/CHECK/multidiffusion-upscaler-for-automatic1111/tile_utils/utils.py @@ -0,0 +1,260 @@ +import math +from enum import Enum +from collections import namedtuple +from types import MethodType + +import cv2 +import torch +import numpy as np +from tqdm import tqdm + +from modules import devices, shared, prompt_parser, extra_networks +from modules import sd_samplers_common +from modules.shared import state +from modules.processing import opt_f + +from tile_utils.typing import * + +state: State + + +class ComparableEnum(Enum): + + def __eq__(self, other: Any) -> bool: + if isinstance(other, str): return self.value == other + elif isinstance(other, ComparableEnum): return self.value == other.value + else: raise TypeError(f'unsupported type: {type(other)}') + +class Method(ComparableEnum): + + MULTI_DIFF = 'MultiDiffusion' + MIX_DIFF = 'Mixture of Diffusers' + +class Method_2(ComparableEnum): + DEMO_FU = "DemoFusion" + +class BlendMode(Enum): # i.e. LayerType + + FOREGROUND = 'Foreground' + BACKGROUND = 'Background' + +BBoxSettings = namedtuple('BBoxSettings', ['enable', 'x', 'y', 'w', 'h', 'prompt', 'neg_prompt', 'blend_mode', 'feather_ratio', 'seed']) +NoiseInverseCache = namedtuple('NoiseInversionCache', ['model_hash', 'x0', 'xt', 'noise_inversion_steps', 'retouch', 'prompts']) +DEFAULT_BBOX_SETTINGS = BBoxSettings(False, 0.4, 0.4, 0.2, 0.2, '', '', BlendMode.BACKGROUND.value, 0.2, -1) +NUM_BBOX_PARAMS = len(BBoxSettings._fields) + + +def build_bbox_settings(bbox_control_states:List[Any]) -> Dict[int, BBoxSettings]: + settings = {} + for index, i in enumerate(range(0, len(bbox_control_states), NUM_BBOX_PARAMS)): + setting = BBoxSettings(*bbox_control_states[i:i+NUM_BBOX_PARAMS]) + # for float x, y, w, h, feather_ratio, keeps 4 digits + setting = setting._replace( + x=round(setting.x, 4), + y=round(setting.y, 4), + w=round(setting.w, 4), + h=round(setting.h, 4), + feather_ratio=round(setting.feather_ratio, 4), + seed=int(setting.seed), + ) + # sanity check + if not setting.enable or setting.x > 1.0 or setting.y > 1.0 or setting.w <= 0.0 or setting.h <= 0.0: continue + settings[index] = setting + return settings + +def gr_value(value=None, visible=None): + return {"value": value, "visible": visible, "__type__": "update"} + + +class BBox: + + ''' grid bbox ''' + + def __init__(self, x:int, y:int, w:int, h:int): + self.x = x + self.y = y + self.w = w + self.h = h + self.box = [x, y, x+w, y+h] + self.slicer = slice(None), slice(None), slice(y, y+h), slice(x, x+w) + + def __getitem__(self, idx:int) -> int: + return self.box[idx] + +class CustomBBox(BBox): + + ''' region control bbox ''' + + def __init__(self, x:int, y:int, w:int, h:int, prompt:str, neg_prompt:str, blend_mode:str, feather_radio:float, seed:int): + super().__init__(x, y, w, h) + self.prompt = prompt + self.neg_prompt = neg_prompt + self.blend_mode = BlendMode(blend_mode) + self.feather_ratio = max(min(feather_radio, 1.0), 0.0) + self.seed = seed + # initialize necessary fields + self.feather_mask = feather_mask(self.w, self.h, self.feather_ratio) if self.blend_mode == BlendMode.FOREGROUND else None + self.cond: MulticondLearnedConditioning = None + self.extra_network_data: DefaultDict[List[ExtraNetworkParams]] = None + self.uncond: List[List[ScheduledPromptConditioning]] = None + + +class Prompt: + + ''' prompts helper ''' + + @staticmethod + def apply_styles(prompts:List[str], styles=None) -> List[str]: + if not styles: return prompts + return [shared.prompt_styles.apply_styles_to_prompt(p, styles) for p in prompts] + + @staticmethod + def append_prompt(prompts:List[str], prompt:str='') -> List[str]: + if not prompt: return prompts + return [f'{p}, {prompt}' for p in prompts] + +class Condition: + + ''' CLIP cond helper ''' + + @staticmethod + def get_custom_cond(prompts:List[str], prompt, steps:int, styles=None) -> Tuple[Cond, ExtraNetworkData]: + prompt = Prompt.apply_styles([prompt], styles)[0] + _, extra_network_data = extra_networks.parse_prompts([prompt]) + prompts = Prompt.append_prompt(prompts, prompt) + prompts = Prompt.apply_styles(prompts, styles) + cond = Condition.get_cond(prompts, steps) + return cond, extra_network_data + + @staticmethod + def get_cond(prompts, steps:int): + prompts, _ = extra_networks.parse_prompts(prompts) + cond = prompt_parser.get_multicond_learned_conditioning(shared.sd_model, prompts, steps) + return cond + + @staticmethod + def get_uncond(neg_prompts:List[str], steps:int, styles=None) -> Uncond: + neg_prompts = Prompt.apply_styles(neg_prompts, styles) + uncond = prompt_parser.get_learned_conditioning(shared.sd_model, neg_prompts, steps) + return uncond + + @staticmethod + def reconstruct_cond(cond:Cond, step:int) -> Tensor: + list_of_what, tensor = prompt_parser.reconstruct_multicond_batch(cond, step) + return tensor + + def reconstruct_uncond(uncond:Uncond, step:int) -> Tensor: + tensor = prompt_parser.reconstruct_cond_batch(uncond, step) + return tensor + + +def splitable(w:int, h:int, tile_w:int, tile_h:int, overlap:int=16) -> bool: + w, h = w // opt_f, h // opt_f + min_tile_size = min(tile_w, tile_h) + if overlap >= min_tile_size: + overlap = min_tile_size - 4 + cols = math.ceil((w - overlap) / (tile_w - overlap)) + rows = math.ceil((h - overlap) / (tile_h - overlap)) + return cols > 1 or rows > 1 + +def split_bboxes(w:int, h:int, tile_w:int, tile_h:int, overlap:int=16, init_weight:Union[Tensor, float]=1.0) -> Tuple[List[BBox], Tensor]: + cols = math.ceil((w - overlap) / (tile_w - overlap)) + rows = math.ceil((h - overlap) / (tile_h - overlap)) + dx = (w - tile_w) / (cols - 1) if cols > 1 else 0 + dy = (h - tile_h) / (rows - 1) if rows > 1 else 0 + + bbox_list: List[BBox] = [] + weight = torch.zeros((1, 1, h, w), device=devices.device, dtype=torch.float32) + for row in range(rows): + y = min(int(row * dy), h - tile_h) + for col in range(cols): + x = min(int(col * dx), w - tile_w) + + bbox = BBox(x, y, tile_w, tile_h) + bbox_list.append(bbox) + weight[bbox.slicer] += init_weight + + return bbox_list, weight + + +def gaussian_weights(tile_w:int, tile_h:int) -> Tensor: + ''' + Copy from the original implementation of Mixture of Diffusers + https://github.com/albarji/mixture-of-diffusers/blob/master/mixdiff/tiling.py + This generates gaussian weights to smooth the noise of each tile. + This is critical for this method to work. + ''' + from numpy import pi, exp, sqrt + + f = lambda x, midpoint, var=0.01: exp(-(x-midpoint)*(x-midpoint) / (tile_w*tile_w) / (2*var)) / sqrt(2*pi*var) + x_probs = [f(x, (tile_w - 1) / 2) for x in range(tile_w)] # -1 because index goes from 0 to latent_width - 1 + y_probs = [f(y, tile_h / 2) for y in range(tile_h)] + + w = np.outer(y_probs, x_probs) + return torch.from_numpy(w).to(devices.device, dtype=torch.float32) + +def feather_mask(w:int, h:int, ratio:float) -> Tensor: + '''Generate a feather mask for the bbox''' + + mask = np.ones((h, w), dtype=np.float32) + feather_radius = int(min(w//2, h//2) * ratio) + # Generate the mask via gaussian weights + # adjust the weight near the edge. the closer to the edge, the lower the weight + # weight = ( dist / feather_radius) ** 2 + for i in range(h//2): + for j in range(w//2): + dist = min(i, j) + if dist >= feather_radius: continue + weight = (dist / feather_radius) ** 2 + mask[i, j] = weight + mask[i, w-j-1] = weight + mask[h-i-1, j] = weight + mask[h-i-1, w-j-1] = weight + + return torch.from_numpy(mask).to(devices.device, dtype=torch.float32) + +def get_retouch_mask(img_input: np.ndarray, kernel_size: int) -> np.ndarray: + ''' + Return the area where the image is retouched. + Copy from Zhihu.com + ''' + step = 1 + kernel = (kernel_size, kernel_size) + + img = img_input.astype(np.float32)/255.0 + sz = img.shape[:2] + sz1 = (int(round(sz[1] * step)), int(round(sz[0] * step))) + sz2 = (int(round(kernel[0] * step)), int(round(kernel[0] * step))) + sI = cv2.resize(img, sz1, interpolation=cv2.INTER_LINEAR) + sp = cv2.resize(img, sz1, interpolation=cv2.INTER_LINEAR) + msI = cv2.blur(sI, sz2) + msp = cv2.blur(sp, sz2) + msII = cv2.blur(sI*sI, sz2) + msIp = cv2.blur(sI*sp, sz2) + vsI = msII - msI*msI + csIp = msIp - msI*msp + recA = csIp/(vsI+0.01) + recB = msp - recA*msI + mA = cv2.resize(recA, (sz[1],sz[0]), interpolation=cv2.INTER_LINEAR) + mB = cv2.resize(recB, (sz[1],sz[0]), interpolation=cv2.INTER_LINEAR) + + gf = mA * img + mB + gf -= img + gf *= 255 + gf = gf.astype(np.uint8) + gf = gf.clip(0, 255) + gf = gf.astype(np.float32)/255.0 + return gf + + +def null_decorator(fn): + def wrapper(*args, **kwargs): + return fn(*args, **kwargs) + return wrapper + +keep_signature = null_decorator +controlnet = null_decorator +stablesr = null_decorator +grid_bbox = null_decorator +custom_bbox = null_decorator +noise_inverse = null_decorator diff --git a/extensions/CHECK/sd-forge-couple/.gitignore b/extensions/CHECK/sd-forge-couple/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..bee8a64b79a99590d5303307144172cfe824fbf7 --- /dev/null +++ b/extensions/CHECK/sd-forge-couple/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/extensions/CHECK/sd-forge-couple/CHANGELOG.md b/extensions/CHECK/sd-forge-couple/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8c63f37414e5fbce5107ff1715e552db6eaffa12 --- /dev/null +++ b/extensions/CHECK/sd-forge-couple/CHANGELOG.md @@ -0,0 +1,16 @@ +### v1.3.0 - 2024 Mar.30 +- Add **Advanced Mapping** *(manual regions)* + +### v1.2.1 - 2024 Mar.29 +- Add **Couple Separator** Option by. pamparamm + +### v1.2.0 - 2024 Mar.28 +- Improved **Prompt** Processing +- Support **Wildcards** + +### v1.1.0 - 2024 Mar.28 +- Improved **Prompt** Processing +- Support **Batch** Count & Size + +### v1.0.0 - 2024 Mar.28 +- Extension **Released**! diff --git a/extensions/CHECK/sd-forge-couple/LICENSE b/extensions/CHECK/sd-forge-couple/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7 --- /dev/null +++ b/extensions/CHECK/sd-forge-couple/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/extensions/CHECK/sd-forge-couple/README.md b/extensions/CHECK/sd-forge-couple/README.md new file mode 100644 index 0000000000000000000000000000000000000000..73188eabb33cd4f5d461f2e3d61796bd3035c935 --- /dev/null +++ b/extensions/CHECK/sd-forge-couple/README.md @@ -0,0 +1,204 @@ +# SD Forge Attention Couple +This is an Extension for the [Forge Webui](https://github.com/lllyasviel/stable-diffusion-webui-forge), which allows you to ~~generate couples~~ target conditioning at different regions. No more color bleeds or mixed features! + +> This does **not** work with [Automatic1111 Webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) + +> One advantage over **Latent Couple** is that, since the conditioning only needs to be calculated once at the beginning, the actual generation speed is not affected! + +> As shown in the examples below, even if a region only contains 1 subject, it's usually still better to prompt for the total amount of subjects first. + +## How to Use + +### Lines Parsing + +This Extension works by dividing the image into multiple tiles, each corresponding to one line in the prompt. So if you want more characters, just prompt more lines! Empty lines are skipped. + +

+ +

+ +``` +[high quality, best quality], 2girls, blonde twintails, cyan eyes, white dress, looking at viewer, smile, blush +2girls, white long hair, red eyes, black dress, looking at viewer, frown +``` + +

+ +

+ +``` +[high quality, best quality], 3girls, blonde twintails, cyan eyes, white dress, looking at viewer, smile, blush +3girls, white long hair, red eyes, black dress, looking at viewer, frown +3girls, black ponytail, closed eyes, t-shirt, jeans, looking at viewer, sleepy +``` + +### Tile Direction + +Choose between dividing the image into columns or rows +- **Horizontal:** Tiles from left to right +- **Vertical:** Tiles from top to bottom + +

+ +

+ +``` +[high quality, best quality], galaxy, stars, milky way +blue sky, clouds +sunrise, lens flare +ocean, waves +beach, sand +pavement, road +``` + +### Global Effect + +Set either the **first line** or the **last line** of the **Positive** prompts to affect the entire image instead of also being divided. Useful for specifying style, quality, or background, etc. (**Negative** prompt is always global regardless of settings.) + +

+ +

+ +``` +[high quality, best quality], (cinematic), 2girls, beach, summer, day, sky, (bloom, hdr) +2girls, white dress, standing, wind, floating hair, looking at viewer, smile, blush +2girls, frills swimsuit, sitting, chair, knees up, smile, blush +``` + +

+ +

+ +``` +a cinematic photo of 2 men arguing, indoors, court room +2 men, jesus christ, white robe, looking at each other, shouting +2 men, santa claus, looking at each other, shouting +``` + +

+ +

+ +``` +a pencil drawing of scenery +mountains +river +tree +forest +``` + +### Couple Separator + +By default, this Extension uses newline (`\n`) as the separator between tiles. You can also specify any keyword as the separator instead. + +

+
+Separator:{SEP} +

+ +``` +a high quality photo of a man and a woman +side-by-side, +blonde hair, hair bow, smile, blush {SEP} +a man and a woman, +white hair, angry, frown +``` + +### LoRA Support + +Using multiple LoRAs also works to a degree, depending on how well each LoRA works together... + +LoRA with multiple subjects works better in my experience. + +

+ +

+ +``` +2girls, nagase kotono, serafuku, looking at viewer, shy, blush, +2girls, kawasaki sakura, casual, looking at viewer, smile, blush, +[high quality, best quality], 2girls, park, outdoors +``` + +

+ +

+ +``` +[high quality, best quality], 2girls, on stage, backlighting, [bloom, hdr], +2girls, miyama suzune, pink idol costume, feather hair ornament, holding hands, looking at viewer, smile, blush +2girls, hanaoi rena, blue idol costume, feather hair ornament, holding hands, looking at viewer, shy, blush +``` + +## Advanced Mapping +Were these automated equally-sized tiles not sufficient for your needs? Now you can manually specify each regions! The mapping logic is the same: one line corresponds to one entry. + +> **UI/UX** to be improved... + +- **Notes:** + - You **must** have values in the entire mask. Simplest way would be adding a global entry. + - Entries with empty **x** column are skipped + - Right now, there's no way to delete a row, so just leave the **x** column empty... + +- **Regions:** + - Each region contains a (**x**, **y**) range and a **weight** + - The **x** and **y** are in the syntax of `from : to` + - **x** is from left to right; **y** is from top to bottom + - The values should be `0.0 ~ 1.0`, representing the **percentage** of the full width/height + - **eg.** `0.0:1.0` would span across the entire axis + +- **Preview:** + - Specify a width and height for the preivew + - Click the **Preview Mapping** button to see each region + - Colors are mapped in the sequence of a rainbow + +

+ + +

+ +``` +a cinematic photo of a couple, from side, outdoors +couple photo, man, black tuxedo +couple photo, woman, white dress +wedding photo, holding flower bouquet together +sunset, golden hour, lens flare +``` + +
+ +## Compatibility Table + + + + + + + + +
FeatureExampleSupport
Control NetOpenPoseYes
Wildcards__colors__Yes
Single LoRAStyleYes
Multi-LoRACharactersLimited
Prompt Scheduling[from:to:steps]No
+ +
+ +## TypeError: 'NoneType' + +For people that get the following error: +```py +RuntimeError: shape '[X, Y, 1]' is invalid for input of size Z +shape '[X, Y, 1]' is invalid for input of size Z +*** Error completing request + ... + Traceback (most recent call last): + ... + res = list(func(*args, **kwargs)) + TypeError: 'NoneType' object is not iterable +``` + +1. Go to **Settings** -> **Optimizations**, and enable `Pad prompt/negative prompt` +2. Set the `Width` and `Height` to multiple of **64** + +
+ +## Special Thanks +- Credits to the original author, **[laksjdjf](https://github.com/laksjdjf)**, whose original [ComfyUI Node](https://github.com/laksjdjf/cgem156-ComfyUI/tree/main/scripts/attention_couple) I used to port into Forge +- Example images were generated with [Animagine XL V3.1](https://civitai.com/models/260267) and [juggernautXL v7](https://civitai.com/models/133005) diff --git a/extensions/CHECK/sd-forge-couple/example/00.jpg b/extensions/CHECK/sd-forge-couple/example/00.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c6e2acf2b20a2151252cdd05546222fb4e3263a6 Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/example/00.jpg differ diff --git a/extensions/CHECK/sd-forge-couple/example/01.jpg b/extensions/CHECK/sd-forge-couple/example/01.jpg new file mode 100644 index 0000000000000000000000000000000000000000..45c7c8e1b5d0d018cd1d98c007e340073df03619 Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/example/01.jpg differ diff --git a/extensions/CHECK/sd-forge-couple/example/03.jpg b/extensions/CHECK/sd-forge-couple/example/03.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f7ecb2d1ae3831ae527a5f7c049bfdfcffd84ad7 Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/example/03.jpg differ diff --git a/extensions/CHECK/sd-forge-couple/example/04.jpg b/extensions/CHECK/sd-forge-couple/example/04.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1c1119524f181590c4d972e0512221aa6aa9a9a5 Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/example/04.jpg differ diff --git a/extensions/CHECK/sd-forge-couple/example/05.jpg b/extensions/CHECK/sd-forge-couple/example/05.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f5a6f8f758ffca027d5d5f9d1d54cfd15210c4e2 Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/example/05.jpg differ diff --git a/extensions/CHECK/sd-forge-couple/example/06.jpg b/extensions/CHECK/sd-forge-couple/example/06.jpg new file mode 100644 index 0000000000000000000000000000000000000000..768b33ad6c0ae809b5f32ef0a96957b5de378f3a Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/example/06.jpg differ diff --git a/extensions/CHECK/sd-forge-couple/example/07.jpg b/extensions/CHECK/sd-forge-couple/example/07.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aa44ed171b094f044d6ba67054cf2cebf1257973 Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/example/07.jpg differ diff --git a/extensions/CHECK/sd-forge-couple/example/08.jpg b/extensions/CHECK/sd-forge-couple/example/08.jpg new file mode 100644 index 0000000000000000000000000000000000000000..338729da783de33cd968b36cf856ea4b12647542 Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/example/08.jpg differ diff --git a/extensions/CHECK/sd-forge-couple/example/09.jpg b/extensions/CHECK/sd-forge-couple/example/09.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bdab247b6d9acadce356ca6fb8251f936d46ffd7 Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/example/09.jpg differ diff --git a/extensions/CHECK/sd-forge-couple/example/10.jpg b/extensions/CHECK/sd-forge-couple/example/10.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b71bf3eb54a37cdc87f542375e527d53249b71dd Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/example/10.jpg differ diff --git a/extensions/CHECK/sd-forge-couple/example/10s.jpg b/extensions/CHECK/sd-forge-couple/example/10s.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1505158e5617817b569f824361f93bc3ae29d952 Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/example/10s.jpg differ diff --git a/extensions/CHECK/sd-forge-couple/scripts/__pycache__/attention_couple.cpython-310.pyc b/extensions/CHECK/sd-forge-couple/scripts/__pycache__/attention_couple.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8481530bed036e320bb3e9c8723a92b5f37f08fd Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/scripts/__pycache__/attention_couple.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-forge-couple/scripts/__pycache__/attention_masks.cpython-310.pyc b/extensions/CHECK/sd-forge-couple/scripts/__pycache__/attention_masks.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5841405d4da8302076f24eb6c61f2474b0172b6a Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/scripts/__pycache__/attention_masks.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-forge-couple/scripts/__pycache__/couple_mapping.cpython-310.pyc b/extensions/CHECK/sd-forge-couple/scripts/__pycache__/couple_mapping.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ebcd580f76c63fbec971b120749501efdd259d9 Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/scripts/__pycache__/couple_mapping.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-forge-couple/scripts/__pycache__/couple_ui.cpython-310.pyc b/extensions/CHECK/sd-forge-couple/scripts/__pycache__/couple_ui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d76212addf2d0d443e4cabcdb7646d97305882be Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/scripts/__pycache__/couple_ui.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-forge-couple/scripts/__pycache__/forge_couple.cpython-310.pyc b/extensions/CHECK/sd-forge-couple/scripts/__pycache__/forge_couple.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20e11ec41f2079b115ad52d3db611a4567566188 Binary files /dev/null and b/extensions/CHECK/sd-forge-couple/scripts/__pycache__/forge_couple.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-forge-couple/scripts/attention_couple.py b/extensions/CHECK/sd-forge-couple/scripts/attention_couple.py new file mode 100644 index 0000000000000000000000000000000000000000..334f7cbc6e5605a4803464059f155d36dd57357a --- /dev/null +++ b/extensions/CHECK/sd-forge-couple/scripts/attention_couple.py @@ -0,0 +1,88 @@ +""" +Credit: laksjdjf +https://github.com/laksjdjf/cgem156-ComfyUI/blob/main/scripts/attention_couple/node.py + +Modified by. Haoming02 to work with Forge +""" + +from scripts.attention_masks import get_mask, lcm_for_list +from modules.devices import get_optimal_device +import torch + + +class AttentionCouple: + + def patch_unet(self, model, base_mask, kwargs: dict): + + new_model = model.clone() + dtype = new_model.model.diffusion_model.dtype + device = get_optimal_device() + + num_conds = len(kwargs) // 2 + 1 + + mask = [base_mask] + [kwargs[f"mask_{i}"] for i in range(1, num_conds)] + mask = torch.stack(mask, dim=0).to(device, dtype=dtype) + assert mask.sum(dim=0).min() > 0, "Masks must not contain zeroes..." + mask = mask / mask.sum(dim=0, keepdim=True) + + conds = [ + kwargs[f"cond_{i}"][0][0].to(device, dtype=dtype) + for i in range(1, num_conds) + ] + num_tokens = [cond.shape[1] for cond in conds] + + def attn2_patch(q, k, v, extra_options): + assert k.mean() == v.mean(), "k and v must be the same." + cond_or_unconds = extra_options["cond_or_uncond"] + num_chunks = len(cond_or_unconds) + self.batch_size = q.shape[0] // num_chunks + q_chunks = q.chunk(num_chunks, dim=0) + k_chunks = k.chunk(num_chunks, dim=0) + lcm_tokens = lcm_for_list(num_tokens + [k.shape[1]]) + conds_tensor = torch.cat( + [ + cond.repeat(self.batch_size, lcm_tokens // num_tokens[i], 1) + for i, cond in enumerate(conds) + ], + dim=0, + ) + + qs, ks = [], [] + for i, cond_or_uncond in enumerate(cond_or_unconds): + k_target = k_chunks[i].repeat(1, lcm_tokens // k.shape[1], 1) + if cond_or_uncond == 1: # uncond + qs.append(q_chunks[i]) + ks.append(k_target) + else: + qs.append(q_chunks[i].repeat(num_conds, 1, 1)) + ks.append(torch.cat([k_target, conds_tensor], dim=0)) + + qs = torch.cat(qs, dim=0) + ks = torch.cat(ks, dim=0) + + return qs, ks, ks + + def attn2_output_patch(out, extra_options): + cond_or_unconds = extra_options["cond_or_uncond"] + mask_downsample = get_mask( + mask, self.batch_size, out.shape[1], extra_options["original_shape"] + ) + outputs = [] + pos = 0 + for cond_or_uncond in cond_or_unconds: + if cond_or_uncond == 1: # uncond + outputs.append(out[pos : pos + self.batch_size]) + pos += self.batch_size + else: + masked_output = ( + out[pos : pos + num_conds * self.batch_size] * mask_downsample + ).view(num_conds, self.batch_size, out.shape[1], out.shape[2]) + masked_output = masked_output.sum(dim=0) + outputs.append(masked_output) + pos += num_conds * self.batch_size + return torch.cat(outputs, dim=0) + + new_model.set_model_attn2_patch(attn2_patch) + new_model.set_model_attn2_output_patch(attn2_output_patch) + + return new_model diff --git a/extensions/CHECK/sd-forge-couple/scripts/attention_masks.py b/extensions/CHECK/sd-forge-couple/scripts/attention_masks.py new file mode 100644 index 0000000000000000000000000000000000000000..305ec576c586e3734cbfe40a8379086ec55b8ee0 --- /dev/null +++ b/extensions/CHECK/sd-forge-couple/scripts/attention_masks.py @@ -0,0 +1,42 @@ +""" +Credit: laksjdjf +https://github.com/laksjdjf/cgem156-ComfyUI/blob/main/scripts/attention_couple/node.py +""" + +import torch.nn.functional as F +import math + + +def get_mask(mask, batch_size, num_tokens, original_shape): + num_conds = mask.shape[0] + + if original_shape[2] * original_shape[3] == num_tokens: + down_sample_rate = 1 + elif (original_shape[2] // 2) * (original_shape[3] // 2) == num_tokens: + down_sample_rate = 2 + elif (original_shape[2] // 4) * (original_shape[3] // 4) == num_tokens: + down_sample_rate = 4 + else: + down_sample_rate = 8 + + size = ( + original_shape[2] // down_sample_rate, + original_shape[3] // down_sample_rate, + ) + mask_downsample = F.interpolate(mask, size=size, mode="nearest") + mask_downsample = mask_downsample.view(num_conds, num_tokens, 1).repeat_interleave( + batch_size, dim=0 + ) + + return mask_downsample + + +def lcm(a, b): + return a * b // math.gcd(a, b) + + +def lcm_for_list(numbers): + current_lcm = numbers[0] + for number in numbers[1:]: + current_lcm = lcm(current_lcm, number) + return current_lcm diff --git a/extensions/CHECK/sd-forge-couple/scripts/couple_mapping.py b/extensions/CHECK/sd-forge-couple/scripts/couple_mapping.py new file mode 100644 index 0000000000000000000000000000000000000000..ffe63cd6957bb843f3c37ed80fb670822106fd93 --- /dev/null +++ b/extensions/CHECK/sd-forge-couple/scripts/couple_mapping.py @@ -0,0 +1,96 @@ +from modules.prompt_parser import SdConditioning +from scripts.couple_ui import parse_mapping +import torch + + +def empty_tensor(H: int, W: int): + return torch.zeros((H, W)).unsqueeze(0) + + +def advanced_mapping(sd_model, couples: list, WIDTH: int, HEIGHT: int, mapping: list): + data = parse_mapping(mapping) + assert len(couples) == len(data) + + ARGs: dict = {} + IS_SDXL: bool = hasattr( + sd_model.forge_objects.unet.model.diffusion_model, "label_emb" + ) + + for tile_index in range(len(data)): + mask = torch.zeros((HEIGHT, WIDTH)) + + (X, Y, W) = data[tile_index] + x_from = int(WIDTH * X[0]) + x_to = int(WIDTH * X[1]) + y_from = int(HEIGHT * Y[0]) + y_to = int(HEIGHT * Y[1]) + weight = W + + # ===== Cond ===== + texts = SdConditioning([couples[tile_index]], False, WIDTH, HEIGHT, None) + cond = sd_model.get_learned_conditioning(texts) + pos_cond = [[cond["crossattn"]]] if IS_SDXL else [[cond]] + # ===== Cond ===== + + # ===== Mask ===== + mask[y_from:y_to, x_from:x_to] = weight + # ===== Mask ===== + + ARGs[f"cond_{tile_index + 1}"] = pos_cond + ARGs[f"mask_{tile_index + 1}"] = mask.unsqueeze(0) + + return ARGs + + +def basic_mapping( + sd_model, + couples: list, + WIDTH: int, + HEIGHT: int, + LINE_COUNT: int, + IS_HORIZONTAL: bool, + background: str, + TILE_SIZE: int, + TILE_WEIGHT: float, + BG_WEIGHT: float, +): + + ARGs: dict = {} + IS_SDXL: bool = hasattr( + sd_model.forge_objects.unet.model.diffusion_model, "label_emb" + ) + + for tile in range(LINE_COUNT): + mask = torch.zeros((HEIGHT, WIDTH)) + + # ===== Cond ===== + texts = SdConditioning([couples[tile]], False, WIDTH, HEIGHT, None) + cond = sd_model.get_learned_conditioning(texts) + pos_cond = [[cond["crossattn"]]] if IS_SDXL else [[cond]] + # ===== Cond ===== + + # ===== Mask ===== + if background == "First Line": + if tile == 0: + mask = torch.ones((HEIGHT, WIDTH)) * BG_WEIGHT + else: + if IS_HORIZONTAL: + mask[:, (tile - 1) * TILE_SIZE : tile * TILE_SIZE] = TILE_WEIGHT + else: + mask[(tile - 1) * TILE_SIZE : tile * TILE_SIZE, :] = TILE_WEIGHT + else: + if IS_HORIZONTAL: + mask[:, tile * TILE_SIZE : (tile + 1) * TILE_SIZE] = TILE_WEIGHT + else: + mask[tile * TILE_SIZE : (tile + 1) * TILE_SIZE, :] = TILE_WEIGHT + # ===== Mask ===== + + ARGs[f"cond_{tile + 1}"] = pos_cond + ARGs[f"mask_{tile + 1}"] = mask.unsqueeze(0) + + if background == "Last Line": + ARGs[f"mask_{LINE_COUNT}"] = ( + torch.ones((HEIGHT, WIDTH)) * BG_WEIGHT + ).unsqueeze(0) + + return ARGs diff --git a/extensions/CHECK/sd-forge-couple/scripts/couple_ui.py b/extensions/CHECK/sd-forge-couple/scripts/couple_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..b358af960a6fad0ebbb78a52fbd912c19fc0120a --- /dev/null +++ b/extensions/CHECK/sd-forge-couple/scripts/couple_ui.py @@ -0,0 +1,167 @@ +from PIL import Image, ImageDraw +import gradio as gr + + +def parse_mapping(data: list) -> list: + mapping = [] + + for [X, Y, W] in data: + if not X.strip(): + continue + + mapping.append( + ( + (float(X.split(":")[0]), float(X.split(":")[1])), + (float(Y.split(":")[0]), float(Y.split(":")[1])), + float(W), + ) + ) + + return mapping + + +def validata_mapping(data: list) -> bool: + try: + for [X, Y, W] in data: + if not X.strip(): + continue + + float(X.split(":")[0]) + float(X.split(":")[1]) + float(Y.split(":")[0]) + float(Y.split(":")[1]) + float(W) + + return True + + except AssertionError: + print("\n\n[Couple] Incorrect number of : in Mapping...\n\n") + return False + except ValueError: + print("\n\n[Couple] Non-Number in Mapping...\n\n") + return False + + +def visualize_mapping(p_WIDTH: int, p_HEIGHT: int, data: list) -> Image: + if not (validata_mapping(data)): + return None + + COLORS = ("red", "orange", "yellow", "green", "blue", "violet", "purple", "white") + + matt = Image.new("RGB", (p_WIDTH, p_HEIGHT), "black") + draw = ImageDraw.Draw(matt) + + mapping = parse_mapping(data) + + print("\nAdv. Preview:") + for tile_index in range(len(mapping)): + color_index = tile_index % len(COLORS) + + (X, Y, W) = mapping[tile_index] + x_from = int(p_WIDTH * X[0]) + x_to = int(p_WIDTH * X[1]) + y_from = int(p_HEIGHT * Y[0]) + y_to = int(p_HEIGHT * Y[1]) + weight = W + + print(f" [{y_from:4d}:{y_to:4d}, {x_from:4d}:{x_to:4d}] = {weight:.2f}") + draw.rectangle( + [(x_from, y_from), (x_to, y_to)], outline=COLORS[color_index], width=2 + ) + + return matt + + +def couple_UI(script, title: str): + with gr.Accordion(label=title, open=False): + with gr.Row(): + enable = gr.Checkbox(label="Enable", elem_id="fc_enable") + + mode = gr.Radio( + ["Basic", "Advanced"], + label="Region Assignment", + value="Basic", + ) + + separator = gr.Textbox( + label="Couple Separator", + lines=1, + max_lines=1, + placeholder="Leave empty to use newline", + ) + + with gr.Group() as basic_settings: + with gr.Row(): + direction = gr.Radio( + ["Horizontal", "Vertical"], + label="Tile Direction", + value="Horizontal", + ) + + background = gr.Radio( + ["None", "First Line", "Last Line"], + label="Global Effect", + value="None", + ) + + with gr.Group(visible=False, elem_id="fc_adv") as adv_settings: + mapping = gr.Dataframe( + label="Mapping", + headers=["x", "y", "weight"], + datatype="str", + row_count=(2, "dynamic"), + col_count=(3, "fixed"), + interactive=True, + type="array", + value=[["0:0.5", "0.0:1.0", "1.0"], ["0.5:1.0", "0.0:1.0", "1.0"]], + ) + + preview_img = gr.Image( + image_mode="RGB", + label="Mapping Preview", + type="pil", + interactive=False, + height=512, + ) + + with gr.Row(): + preview_width = gr.Number(value=1024, label="Width", precision=0) + preview_height = gr.Number(value=1024, label="Height", precision=0) + + preview_btn = gr.Button("Preview Mapping", elem_id="fc_preview") + + preview_btn.click( + visualize_mapping, + [preview_width, preview_height, mapping], + preview_img, + ) + + def on_radio_change(choice): + if choice == "Basic": + return [ + gr.Group.update(visible=True), + gr.Group.update(visible=False), + ] + else: + return [ + gr.Group.update(visible=False), + gr.Group.update(visible=True), + ] + + mode.change(on_radio_change, mode, [basic_settings, adv_settings]) + + script.paste_field_names = [] + script.infotext_fields = [ + (enable, "forge_couple"), + (direction, "forge_couple_direction"), + (background, "forge_couple_background"), + (separator, "forge_couple_separator"), + (mode, "forge_couple_mode"), + (mapping, "forge_couple_mapping"), + ] + + for comp, name in script.infotext_fields: + comp.do_not_save_to_config = True + script.paste_field_names.append(name) + + return [enable, direction, background, separator, mode, mapping] diff --git a/extensions/CHECK/sd-forge-couple/scripts/forge_couple.py b/extensions/CHECK/sd-forge-couple/scripts/forge_couple.py new file mode 100644 index 0000000000000000000000000000000000000000..f81ff8471c7de147bdabfa29251a5ec2eb26c569 --- /dev/null +++ b/extensions/CHECK/sd-forge-couple/scripts/forge_couple.py @@ -0,0 +1,149 @@ +from modules import scripts +import re + +from scripts.couple_mapping import empty_tensor, basic_mapping, advanced_mapping +from scripts.couple_ui import couple_UI, validata_mapping, parse_mapping + +from scripts.attention_couple import AttentionCouple +forgeAttentionCouple = AttentionCouple() + +VERSION = "1.3.0" + + +class ForgeCouple(scripts.Script): + + def __init__(self): + self.couples: list = None + + def title(self): + return "Forge Couple" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, *args, **kwargs): + return couple_UI(self, f"{self.title()} {VERSION}") + + def parse_networks(self, prompt: str) -> str: + """LoRAs are already parsed""" + pattern = re.compile(r"<.*?>") + cleaned = re.sub(pattern, "", prompt) + + return cleaned + + def after_extra_networks_activate( + self, + p, + enable: bool, + direction: str, + background: str, + separator: str, + mode: str, + mapping: list, + *args, + **kwargs, + ): + if not enable: + return + + if not separator.strip(): + separator = "\n" + + couples = [] + + chunks = kwargs["prompts"][0].split(separator) + for chunk in chunks: + prompt = self.parse_networks(chunk).strip() + + if not prompt.strip(): + # Skip Empty Lines + continue + + couples.append(prompt) + + if len(couples) < (3 if background != "None" else 2): + print("\n\n[Couple] Not Enough Lines in Prompt...\n\n") + self.couples = None + return + + if (mode == "Advanced") and (len(couples) != len(parse_mapping(mapping))): + print("\n\n[Couple] Number of Couples and Mapping is not the same...\n\n") + self.couples = None + return + + if (mode == "Advanced") and not validata_mapping(mapping): + self.couples = None + return + + self.couples = couples + + def process_before_every_sampling( + self, + p, + enable: bool, + direction: str, + background: str, + separator: str, + mode: str, + mapping: list, + *args, + **kwargs, + ): + + if not enable or not self.couples: + return + + # ===== Infotext ===== + p.extra_generation_params["forge_couple"] = True + p.extra_generation_params["forge_couple_separator"] = "\n" if not separator.strip() else separator + p.extra_generation_params["forge_couple_mode"] = mode + if mode == "Basic": + p.extra_generation_params["forge_couple_direction"] = direction + p.extra_generation_params["forge_couple_background"] = background + else: + p.extra_generation_params["forge_couple_mapping"] = mapping + # ===== Infotext ===== + + # ===== Init ===== + unet = p.sd_model.forge_objects.unet + + WIDTH: int = p.width + HEIGHT: int = p.height + IS_HORIZONTAL: bool = direction == "Horizontal" + + LINE_COUNT: int = len(self.couples) + TILE_COUNT: int = LINE_COUNT - (background != "None") + + if mode == "Basic": + TILE_WEIGHT: float = 1.25 if background == "None" else 1.0 + BG_WEIGHT: float = 0.0 if background == "None" else 0.5 + + TILE_SIZE: int = ( + (WIDTH if IS_HORIZONTAL else HEIGHT) - 1 + ) // TILE_COUNT + 1 + # ===== Init ===== + + # ===== Tiles ===== + if mode == "Basic": + ARGs = basic_mapping( + p.sd_model, + self.couples, + WIDTH, + HEIGHT, + LINE_COUNT, + IS_HORIZONTAL, + background, + TILE_SIZE, + TILE_WEIGHT, + BG_WEIGHT, + ) + + else: + ARGs = advanced_mapping(p.sd_model, self.couples, WIDTH, HEIGHT, mapping) + # ===== Tiles ===== + + assert len(ARGs.keys()) // 2 == LINE_COUNT + + base_mask = empty_tensor(HEIGHT, WIDTH) + patched_unet = forgeAttentionCouple.patch_unet(unet, base_mask, ARGs) + p.sd_model.forge_objects.unet = patched_unet diff --git a/extensions/CHECK/sd-forge-couple/style.css b/extensions/CHECK/sd-forge-couple/style.css new file mode 100644 index 0000000000000000000000000000000000000000..beb29654c5f1f9e4d650d4f69d9c486f1efeede4 --- /dev/null +++ b/extensions/CHECK/sd-forge-couple/style.css @@ -0,0 +1,22 @@ +#fc_adv { + flex-direction: row; + justify-content: center; +} + +#fc_adv>div { + gap: 2em; + width: 100%; +} + +#fc_adv .gradio-image { + width: 100%; +} + +#fc_enable label { + height: 100%; +} + +#fc_preview { + margin: 0.25em 1em; + border-radius: 0.5em; +} diff --git a/extensions/CHECK/sd-webui-agentattention/.gitignore b/extensions/CHECK/sd-webui-agentattention/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c18dd8d83ceed1806b50b0aaa46beb7e335fff13 --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/extensions/CHECK/sd-webui-agentattention/LICENSE b/extensions/CHECK/sd-webui-agentattention/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7 --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/extensions/CHECK/sd-webui-agentattention/README.md b/extensions/CHECK/sd-webui-agentattention/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6137c0ac668f4afbbede1a12e67144f50144ed53 --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/README.md @@ -0,0 +1,34 @@ +# sd-webui-agentattention +### An unofficial implementation of Agent Attention in Automatic1111 WebUI. +Speed up image generation with improved image quality using Agent Attention. + +![image](samples/xyz_grid-2419-1-bicycle.png) + +### Feature List / Todo +- [x] SD 1.5 Support +- [x] SD XL Support (Requires Max Downsample > 1. If anybody knows the ideal settings to get this to work, please open an Issue!) + +### Issues / Pull Requests are welcome! + +### Credits +- The authors of the original paper for their method (https://arxiv.org/abs/2312.08874): + ``` + @misc{han2023agent, + title={Agent Attention: On the Integration of Softmax and Linear Attention}, + author={Dongchen Han and Tianzhu Ye and Yizeng Han and Zhuofan Xia and Shiji Song and Gao Huang}, + year={2023}, + eprint={2312.08874}, + archivePrefix={arXiv}, + primaryClass={cs.CV} + } + ``` +- This extension uses code from the official AgentAttention repository: https://github.com/LeapLabTHU/Agent-Attention +- @udon-universe's extension templates (https://github.com/udon-universe/stable-diffusion-webui-extension-templates) + +### More samples +#### SD XL +![image](samples/xyz_grid-2428-1-desk.jpg) +#### SD 1.5 +![image](samples/xyz_grid-2418-1-bell%20pepper.png) +![image](samples/xyz_grid-2415-1-desk.png) +![image](samples/xyz_grid-2417-1-goldfinch,%20Carduelis%20carduelis.png) diff --git a/extensions/CHECK/sd-webui-agentattention/agentsd/README.md b/extensions/CHECK/sd-webui-agentattention/agentsd/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2ac7f0c73df49bfae03aca7f44b8480959eb7aaa --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/agentsd/README.md @@ -0,0 +1,83 @@ +# Agent Attention for Stable Diffusion + +When applied to Stable Diffusion, our agent attention accelerates generation and substantially enhances image generation quality **without any additional training**. + + +## AgentSD +We practically apply agent attention to [ToMeSD model](https://github.com/dbolya/tomesd). ToMeSD reduces the number of tokens before attention calculation in Stable Diffusion, enhancing generation speed. Nonetheless, the post-merge token count remains considerable, resulting in continued complexity and latency. Hence, we replace the Softmax attention employed in ToMeSD model with our agent attention to further enhance speed. + +Here are results with [FID](https://github.com/mseitzer/pytorch-fid) scores vs. time and memory usage (lower is better) when employing Stable Diffusion v1.5 to generate 2,000 $512^2$ images of ImageNet-1k classes using 50 PLMS diffusion steps on a single RTX4090 GPU: + +| Method | r% | FID ↓ | Time (s/im) ↓ | Memory (GB/im) ↓ | +| --------------------- | ---- | :------------------------- | :------------------------ | :---------------------- | +| Stable Diffusion v1.5 | 0 | 28.84 (_baseline_) | 2.62 (_baseline_) | 3.13 (_baseline_) | +| ToMeSD | 10 | 28.64 | 2.40 | 2.55 | +| | 20 | 28.68 | 2.15 | 2.03 | +| | 30 | 28.82 | 1.90 | 2.09 | +| | 40 | 28.74 | 1.71 | 1.69 | +| | 50 | 29.01 | 1.53 | 1.47 | +| **AgentSD** | 10 | 27.79 (↓**1.05** _better_) | 1.97 (**1.33x** _faster_) | 1.77 (**1.77x** _less_) | +| | 20 | 27.77 (↓**1.07** _better_) | 1.80 (**1.45x** _faster_) | 1.60 (**1.95x** _less_) | +| | 30 | 28.03 (↓**0.81** _better_) | 1.65 (**1.59x** _faster_) | 2.05 (**1.53x** _less_) | +| | 40 | 28.15 (↓**0.69** _better_) | 1.54 (**1.70x** _faster_) | 1.55 (**2.02x** _less_) | +| | 50 | 28.42 (↓**0.42** _better_) | 1.42 (**1.84x** _faster_) | 1.21 (**2.59x** _less_) | + +ToMeSD accelerates Stable Diffusion while maintaining similar image quality. AgentSD not only **further accelerates** ToMeSD but also **significantly enhances** image generation quality **without extra training!** + +## Dependencies + +- PyTorch >= 1.12.1 + + +## Usage +Place the [agentsd](./) folder in your project and apply AgentSD to any Stable Diffusion model with: +```py +import agentsd +if step == 0: + # Apply Agent Attention and ToMe during early 20 diffusion steps + agentsd.apply_patch(model, sx=4, sy=4, ratio=0.4, agent_ratio=0.95) +elif step == 20: + # Apply ToMe in later diffusion steps + agentsd.remove_patch(model) + agentsd.apply_patch(model, sx=2, sy=2, ratio=0.4, agent_ratio=0.5) +``` +### Example +To apply AgentSD to SDv1 PLMS sampler, add the following to [this line](https://github.com/runwayml/stable-diffusion/blob/08ab4d326c96854026c4eb3454cd3b02109ee982/ldm/models/diffusion/plms.py#L143): +```py +import agentsd +if i == 0: + agentsd.apply_patch(self.model, sx=4, sy=4, ratio=0.4, agent_ratio=0.95) +elif i == 20: + agentsd.remove_patch(self.model) + agentsd.apply_patch(self.model, sx=2, sy=2, ratio=0.4, agent_ratio=0.5) +``` +To apply AgentSD to SDv2 DDIM sampler, add the following to [this line](https://github.com/Stability-AI/stablediffusion/blob/cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf/ldm/models/diffusion/ddim.py#L152) (setting ``attn_precision="fp32"`` to avoid [numerical instabilities on the v2.1 model](https://github.com/Stability-AI/stablediffusion/tree/main?tab=readme-ov-file#news)): + +```py +import agentsd +if i == 0: + agentsd.apply_patch(self.model, sx=4, sy=4, ratio=0.4, agent_ratio=0.95, attn_precision="fp32") +elif i == 20: + agentsd.remove_patch(self.model) + agentsd.apply_patch(self.model, sx=2, sy=2, ratio=0.4, agent_ratio=0.5, attn_precision="fp32") +``` + +## TODO + + - [x] [Stable Diffusion v1](https://github.com/runwayml/stable-diffusion) + - [x] [Stable Diffusion v2](https://github.com/Stability-AI/stablediffusion) + - [x] [Latent Diffusion](https://github.com/CompVis/latent-diffusion) + - [ ] [Diffusers](https://github.com/huggingface/diffusers) + +## Citation + +If you find this repo helpful, please consider citing us. + +```latex +@article{han2023agent, + title={Agent Attention: On the Integration of Softmax and Linear Attention}, + author={Han, Dongchen and Ye, Tianzhu and Han, Yizeng and Xia, Zhuofan and Song, Shiji and Huang, Gao}, + journal={arXiv preprint arXiv:2312.08874}, + year={2023} +} +``` diff --git a/extensions/CHECK/sd-webui-agentattention/agentsd/__init__.py b/extensions/CHECK/sd-webui-agentattention/agentsd/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0edd0ea76ace6baeb5bf26a5b86305c50a3d17da --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/agentsd/__init__.py @@ -0,0 +1,14 @@ +''' +This code from the following repository: https://github.com/LeapLabTHU/Agent-Attention + +@article{han2023agent, + title={Agent Attention: On the Integration of Softmax and Linear Attention}, + author={Han, Dongchen and Ye, Tianzhu and Han, Yizeng and Xia, Zhuofan and Song, Shiji and Huang, Gao}, + journal={arXiv preprint arXiv:2312.08874}, + year={2023} +} +''' +from . import merge, patch +from .patch import apply_patch, remove_patch + +__all__ = ["merge", "patch", "apply_patch", "remove_patch"] \ No newline at end of file diff --git a/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/__init__.cpython-310.pyc b/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5acc00c981689511426a48196e96d7043fe7048 Binary files /dev/null and b/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/__init__.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/merge.cpython-310.pyc b/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/merge.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48722b4e79e7a99efe6100120c9c7bd20a828dc9 Binary files /dev/null and b/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/merge.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/patch.cpython-310.pyc b/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/patch.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f07957f6dcad569151509320001983d85710d0f9 Binary files /dev/null and b/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/patch.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/utils.cpython-310.pyc b/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83b06f1f8c524280d959f88390046fb23369e649 Binary files /dev/null and b/extensions/CHECK/sd-webui-agentattention/agentsd/__pycache__/utils.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-agentattention/agentsd/merge.py b/extensions/CHECK/sd-webui-agentattention/agentsd/merge.py new file mode 100644 index 0000000000000000000000000000000000000000..0305844aca872d19710f816381734f76b97afe7f --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/agentsd/merge.py @@ -0,0 +1,153 @@ +''' +This code from the following repository: https://github.com/LeapLabTHU/Agent-Attention + +@article{han2023agent, + title={Agent Attention: On the Integration of Softmax and Linear Attention}, + author={Han, Dongchen and Ye, Tianzhu and Han, Yizeng and Xia, Zhuofan and Song, Shiji and Huang, Gao}, + journal={arXiv preprint arXiv:2312.08874}, + year={2023} +} +''' +import torch +from typing import Tuple, Callable + + +def do_nothing(x: torch.Tensor, mode:str=None): + return x + + +def do_nothing_2(x: torch.Tensor, mode:str=None): + return x, None + + +def mps_gather_workaround(input, dim, index): + if input.shape[-1] == 1: + return torch.gather( + input.unsqueeze(-1), + dim - 1 if dim < 0 else dim, + index.unsqueeze(-1) + ).squeeze(-1) + else: + return torch.gather(input, dim, index) + + +def bipartite_soft_matching_random2d(metric: torch.Tensor, + w: int, h: int, sx: int, sy: int, r: int, agent_r: int, + no_rand: bool = False, + generator: torch.Generator = None) -> Tuple[Callable, Callable]: + """ + Partitions the tokens into src and dst and merges r tokens from src to dst. + Dst tokens are partitioned by choosing one randomy in each (sx, sy) region. + + Args: + - metric [B, N, C]: metric to use for similarity + - w: image width in tokens + - h: image height in tokens + - sx: stride in the x dimension for dst, must divide w + - sy: stride in the y dimension for dst, must divide h + - r: number of tokens to remove (by merging) + - agent_r: number of tokens to remove (by merging) when producing agent tokens + - no_rand: if true, disable randomness (use top left corner only) + - rand_seed: if no_rand is false, and if not None, sets random seed. + """ + B, N, _ = metric.shape + + if r <= 0: + return do_nothing, do_nothing + + gather = mps_gather_workaround if metric.device.type == "mps" else torch.gather + + with torch.no_grad(): + hsy, wsx = h // sy, w // sx + + # For each sy by sx kernel, randomly assign one token to be dst and the rest src + if no_rand: + rand_idx = torch.zeros(hsy, wsx, 1, device=metric.device, dtype=torch.int64) + else: + rand_idx = torch.randint(sy*sx, size=(hsy, wsx, 1), device=generator.device, generator=generator).to(metric.device) + + # The image might not divide sx and sy, so we need to work on a view of the top left if the idx buffer instead + idx_buffer_view = torch.zeros(hsy, wsx, sy*sx, device=metric.device, dtype=torch.int64) + idx_buffer_view.scatter_(dim=2, index=rand_idx, src=-torch.ones_like(rand_idx, dtype=rand_idx.dtype)) + idx_buffer_view = idx_buffer_view.view(hsy, wsx, sy, sx).transpose(1, 2).reshape(hsy * sy, wsx * sx) + + # Image is not divisible by sx or sy so we need to move it into a new buffer + if (hsy * sy) < h or (wsx * sx) < w: + idx_buffer = torch.zeros(h, w, device=metric.device, dtype=torch.int64) + idx_buffer[:(hsy * sy), :(wsx * sx)] = idx_buffer_view + else: + idx_buffer = idx_buffer_view + + # We set dst tokens to be -1 and src to be 0, so an argsort gives us dst|src indices + rand_idx = idx_buffer.reshape(1, -1, 1).argsort(dim=1) + + # We're finished with these + del idx_buffer, idx_buffer_view + + # rand_idx is currently dst|src, so split them + num_dst = hsy * wsx + a_idx = rand_idx[:, num_dst:, :] # src + b_idx = rand_idx[:, :num_dst, :] # dst + + def split(x): + C = x.shape[-1] + src = gather(x, dim=1, index=a_idx.expand(B, N - num_dst, C)) + dst = gather(x, dim=1, index=b_idx.expand(B, num_dst, C)) + return src, dst + + # Cosine similarity between A and B + metric = metric / metric.norm(dim=-1, keepdim=True) + a, b = split(metric) + scores = a @ b.transpose(-1, -2) + + # Can't reduce more than the # tokens in src + r = min(a.shape[1], r) + + # Find the most similar greedily + node_max, node_idx = scores.max(dim=-1) + edge_idx = node_max.argsort(dim=-1, descending=True)[..., None] + + unm_idx = edge_idx[..., r:, :] # Unmerged Tokens + src_idx = edge_idx[..., :r, :] # Merged Tokens + dst_idx = gather(node_idx[..., None], dim=-2, index=src_idx) + + agent_r = min(a.shape[1], agent_r) + if (metric.shape[1] - agent_r) * 2 < (metric.shape[1] - r): + agent_unm_idx = edge_idx[..., agent_r:, :] + agent_src_idx = edge_idx[..., :agent_r, :] + agent_dst_idx = gather(node_idx[..., None], dim=-2, index=agent_src_idx) + else: + agent_unm_idx, agent_src_idx, agent_dst_idx = None, None, None + + def merge(x: torch.Tensor, mode="mean") -> Tuple[torch.Tensor, torch.Tensor]: + src, dst = split(x) + n, t1, c = src.shape + + unm = gather(src, dim=-2, index=unm_idx.expand(n, t1 - r, c)) + src_ = gather(src, dim=-2, index=src_idx.expand(n, r, c)) + dst_ = dst.scatter_reduce(-2, dst_idx.expand(n, r, c), src_, reduce=mode) + + if agent_unm_idx is not None: + agent_unm = gather(src, dim=-2, index=agent_unm_idx.expand(n, t1 - agent_r, c)) + agent_src = gather(src, dim=-2, index=agent_src_idx.expand(n, agent_r, c)) + agent_dst = dst.scatter_reduce(-2, agent_dst_idx.expand(n, agent_r, c), agent_src, reduce=mode) + return torch.cat([unm, dst_], dim=1), torch.cat([agent_unm, agent_dst], dim=1) + else: + return torch.cat([unm, dst_], dim=1), None + + def unmerge(x: torch.Tensor) -> torch.Tensor: + unm_len = unm_idx.shape[1] + unm, dst = x[..., :unm_len, :], x[..., unm_len:, :] + _, _, c = unm.shape + + src = gather(dst, dim=-2, index=dst_idx.expand(B, r, c)) + + # Combine back to the original shape + out = torch.zeros(B, N, c, device=x.device, dtype=x.dtype) + out.scatter_(dim=-2, index=b_idx.expand(B, num_dst, c), src=dst) + out.scatter_(dim=-2, index=gather(a_idx.expand(B, a_idx.shape[1], 1), dim=1, index=unm_idx).expand(B, unm_len, c), src=unm) + out.scatter_(dim=-2, index=gather(a_idx.expand(B, a_idx.shape[1], 1), dim=1, index=src_idx).expand(B, r, c), src=src) + + return out + + return merge, unmerge diff --git a/extensions/CHECK/sd-webui-agentattention/agentsd/patch.py b/extensions/CHECK/sd-webui-agentattention/agentsd/patch.py new file mode 100644 index 0000000000000000000000000000000000000000..a45c01d0584da4e58cdd30d986a7b0a1a7d13885 --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/agentsd/patch.py @@ -0,0 +1,424 @@ +''' +This code from the following repository: https://github.com/LeapLabTHU/Agent-Attention + +@article{han2023agent, + title={Agent Attention: On the Integration of Softmax and Linear Attention}, + author={Han, Dongchen and Ye, Tianzhu and Han, Yizeng and Xia, Zhuofan and Song, Shiji and Huang, Gao}, + journal={arXiv preprint arXiv:2312.08874}, + year={2023} +} +''' +import torch +import math +from typing import Type, Dict, Any, Tuple, Callable + +from . import merge +from .utils import isinstance_str, init_generator +from torch import nn, einsum +from einops import rearrange, repeat +from inspect import isfunction + + +def compute_merge(x: torch.Tensor, tome_info: Dict[str, Any]) -> Tuple[Callable, ...]: + original_h, original_w = tome_info["size"] + original_tokens = original_h * original_w + downsample = int(math.ceil(math.sqrt(original_tokens // x.shape[1]))) + + args = tome_info["args"] + + if downsample <= args["max_downsample"]: + w = int(math.ceil(original_w / downsample)) + h = int(math.ceil(original_h / downsample)) + r = int(x.shape[1] * args["ratio"]) + agent_r = int(x.shape[1] * args["agent_ratio"]) + + # Re-init the generator if it hasn't already been initialized or device has changed. + if args["generator"] is None: + args["generator"] = init_generator(x.device) + elif args["generator"].device != x.device: + args["generator"] = init_generator(x.device, fallback=args["generator"]) + + # If the batch size is odd, then it's not possible for prompted and unprompted images to be in the same + # batch, which causes artifacts with use_rand, so force it to be off. + use_rand = False if x.shape[0] % 2 == 1 else args["use_rand"] + m, u = merge.bipartite_soft_matching_random2d(x, w, h, args["sx"], args["sy"], r, agent_r, + no_rand=not use_rand, generator=args["generator"]) + else: + m, u = (merge.do_nothing_2, merge.do_nothing) + + m_a, u_a = (m, u) if args["merge_attn"] else (merge.do_nothing_2, merge.do_nothing) + m_c, u_c = (m, u) if args["merge_crossattn"] else (merge.do_nothing_2, merge.do_nothing) + m_m, u_m = (m, u) if args["merge_mlp"] else (merge.do_nothing_2, merge.do_nothing) + + return m_a, m_c, m_m, u_a, u_c, u_m # Okay this is probably not very good + + + + + + + +def make_tome_block(block_class: Type[torch.nn.Module], old_forward) -> Type[torch.nn.Module]: + """ + Make a patched class on the fly so we don't have to import any specific modules. + This patch applies AgentSD and ToMe to the forward function of the block. + """ + + class ToMeBlock(block_class): + # Save for unpatching later + _parent = block_class + _old_forward = old_forward + + def _forward(self, x: torch.Tensor, context: torch.Tensor = None) -> torch.Tensor: + m_a, m_c, m_m, u_a, u_c, u_m = compute_merge(x, self._tome_info) + + # This is where the meat of the computation happens + y = self.norm1(x) + feature, agent = m_a(y) + x = u_a(self.attn1(feature, agent=agent, context=context if self.disable_self_attn else None)) + x + y = self.norm2(x) + feature, agent = m_c(y) + x = u_c(self.attn2(feature, agent=agent, context=context)) + x + y = self.norm3(x) + feature, _ = m_m(y) + x = u_m(self.ff(feature)) + x + + return x + + return ToMeBlock + + +def exists(val): + return val is not None + + +def default(val, d): + if exists(val): + return val + return d() if isfunction(d) else d + + +def make_agent_attn(block_class: Type[torch.nn.Module], k_scale2, k_shortcut, attn_precision=None) -> Type[torch.nn.Module]: + """ + This patch applies AgentSD to the forward function of the block. + """ + + class AgentAttention(block_class): + # Save for unpatching later + _parent = block_class + + def set_new_params(self): + self.k_scale2 = k_scale2 + self.k_shortcut = k_shortcut + self.attn_precision = attn_precision + + def forward(self, x, agent=None, context=None, mask=None, *args, **kwargs): + if agent is not None: + if agent.shape[1] * 2 < x.shape[1]: + k_scale2 = self.k_scale2 + k_shortcut = self.k_shortcut + + h = self.heads + + q = self.to_q(x) + context = default(context, x) + k = self.to_k(context) + v = self.to_v(context) + agent = self.to_q(agent) + + q, k, v, agent = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q, k, v, agent)) + if exists(mask): + print('Mask not supported yet!') + + # force cast to fp32 to avoid overflowing + if self.attn_precision == "fp32": + with torch.autocast(enabled=False, device_type='cuda'): + agent, k = agent.float(), k.float() + sim1 = einsum('b i d, b j d -> b i j', agent, k) * self.scale + del k + else: + sim1 = einsum('b i d, b j d -> b i j', agent, k) * self.scale + + # attention, what we cannot get enough of + attn1 = sim1.softmax(dim=-1) + agent_feature = einsum('b i j, b j d -> b i d', attn1, v) + + # force cast to fp32 to avoid overflowing + if self.attn_precision == "fp32": + with torch.autocast(enabled=False, device_type='cuda'): + q = q.float() + sim2 = einsum('b i d, b j d -> b i j', q, agent) * self.scale ** k_scale2 + del q, agent + else: + sim2 = einsum('b i d, b j d -> b i j', q, agent) * self.scale ** k_scale2 + + # attention, what we cannot get enough of + attn2 = sim2.softmax(dim=-1) + out = einsum('b i j, b j d -> b i d', attn2, agent_feature) + + out = out * 1.0 + v * k_shortcut + + out = rearrange(out, '(b h) n d -> b n (h d)', h=h) + return self.to_out(out) + + h = self.heads + + q = self.to_q(x) + context = default(context, x) + k = self.to_k(context) + v = self.to_v(context) + + q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q, k, v)) + + sim = einsum('b i d, b j d -> b i j', q, k) * self.scale + + if exists(mask): + mask = rearrange(mask, 'b ... -> b (...)') + max_neg_value = -torch.finfo(sim.dtype).max + mask = repeat(mask, 'b j -> (b h) () j', h=h) + sim.masked_fill_(~mask, max_neg_value) + + # attention, what we cannot get enough of + attn = sim.softmax(dim=-1) + + out = einsum('b i j, b j d -> b i d', attn, v) + out = rearrange(out, '(b h) n d -> b n (h d)', h=h) + return self.to_out(out) + + return AgentAttention + + + + + + +def make_diffusers_tome_block(block_class: Type[torch.nn.Module], old_forward) -> Type[torch.nn.Module]: + """ + Make a patched class for a diffusers model. + This patch applies ToMe to the forward function of the block. + """ + class ToMeBlock(block_class): + # Save for unpatching later + _parent = block_class + _old_forward = old_forward + + def forward( + self, + hidden_states, + attention_mask=None, + encoder_hidden_states=None, + encoder_attention_mask=None, + timestep=None, + cross_attention_kwargs=None, + class_labels=None, + ) -> torch.Tensor: + # (1) ToMe + m_a, m_c, m_m, u_a, u_c, u_m = compute_merge(hidden_states, self._tome_info) + + if self.use_ada_layer_norm: + norm_hidden_states = self.norm1(hidden_states, timestep) + elif self.use_ada_layer_norm_zero: + norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1( + hidden_states, timestep, class_labels, hidden_dtype=hidden_states.dtype + ) + else: + norm_hidden_states = self.norm1(hidden_states) + + # (2) ToMe m_a + norm_hidden_states = m_a(norm_hidden_states) + + # 1. Self-Attention + cross_attention_kwargs = cross_attention_kwargs if cross_attention_kwargs is not None else {} + attn_output = self.attn1( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states if self.only_cross_attention else None, + attention_mask=attention_mask, + **cross_attention_kwargs, + ) + if self.use_ada_layer_norm_zero: + attn_output = gate_msa.unsqueeze(1) * attn_output + + # (3) ToMe u_a + hidden_states = u_a(attn_output) + hidden_states + + if self.attn2 is not None: + norm_hidden_states = ( + self.norm2(hidden_states, timestep) if self.use_ada_layer_norm else self.norm2(hidden_states) + ) + # (4) ToMe m_c + norm_hidden_states = m_c(norm_hidden_states) + + # 2. Cross-Attention + attn_output = self.attn2( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=encoder_attention_mask, + **cross_attention_kwargs, + ) + # (5) ToMe u_c + hidden_states = u_c(attn_output) + hidden_states + + # 3. Feed-forward + norm_hidden_states = self.norm3(hidden_states) + + if self.use_ada_layer_norm_zero: + norm_hidden_states = norm_hidden_states * (1 + scale_mlp[:, None]) + shift_mlp[:, None] + + # (6) ToMe m_m + norm_hidden_states = m_m(norm_hidden_states) + + ff_output = self.ff(norm_hidden_states) + + if self.use_ada_layer_norm_zero: + ff_output = gate_mlp.unsqueeze(1) * ff_output + + # (7) ToMe u_m + hidden_states = u_m(ff_output) + hidden_states + + return hidden_states + + return ToMeBlock + + + + + + +def hook_tome_model(model: torch.nn.Module): + """ Adds a forward pre hook to get the image size. This hook can be removed with remove_patch. """ + def hook(module, args): + module._tome_info["size"] = (args[0].shape[2], args[0].shape[3]) + return None + + model._tome_info["hooks"].append(model.register_forward_pre_hook(hook)) + + + + + + + + +def apply_patch( + model: torch.nn.Module, + ratio: float = 0.5, + max_downsample: int = 1, + sx: int = 2, sy: int = 2, + agent_ratio: float = 0.8, + k_scale2=0.3, + k_shortcut=0.075, + attn_precision=None, + use_rand: bool = True, + merge_attn: bool = True, + merge_crossattn: bool = False, + merge_mlp: bool = False): + """ + Patches a stable diffusion model with AgentSD. + Apply this to the highest level stable diffusion object (i.e., it should have a .model.diffusion_model). + + Important Args: + - model: A top level Stable Diffusion module to patch in place. Should have a ".model.diffusion_model" + - ratio: The ratio of tokens to merge. I.e., 0.4 would reduce the total number of tokens by 40%. + The maximum value for this is 1-(1/(sx*sy)). By default, the max is 0.75 (I recommend <= 0.5 though). + Higher values result in more speed-up, but with more visual quality loss. + - agent_ratio: The ratio of tokens to merge when producing agent tokens. + + Args to tinker with if you want: + - max_downsample [1, 2, 4, or 8]: Apply AgentSD to layers with at most this amount of downsampling. + E.g., 1 only applies to layers with no downsampling (4/15) while + 8 applies to all layers (15/15). I recommend a value of 1 or 2. + - sx, sy: The stride for computing dst sets (see paper). A higher stride means you can merge more tokens, + but the default of (2, 2) works well in most cases. Doesn't have to divide image size. + - k_scale2: The scale used for the second attention is head_dim ** (-0.5 * k_scale2) + - k_shortcut: The ratio used in O = sigma(QA^T) sigma(AK^T) V + k * V. + - attn_precision: Set attn_precision="fp32" to avoid numerical instabilities on SD v2.1 model. + - use_rand: Whether or not to allow random perturbations when computing dst sets (see paper). Usually + you'd want to leave this on, but if you're having weird artifacts try turning this off. + - merge_attn: Whether or not to merge tokens for attention (recommended). + - merge_crossattn: Whether or not to merge tokens for cross attention (not recommended). + - merge_mlp: Whether or not to merge tokens for the mlp layers (very not recommended). + """ + + # Make sure the module is not currently patched + remove_patch(model) + + is_diffusers = isinstance_str(model, "DiffusionPipeline") or isinstance_str(model, "ModelMixin") + + if not is_diffusers: + if not hasattr(model, "model") or not hasattr(model.model, "diffusion_model"): + # Provided model not supported + raise RuntimeError("Provided model was not a Stable Diffusion / Latent Diffusion model, as expected.") + diffusion_model = model.model.diffusion_model + else: + # Supports "pipe.unet" and "unet" + diffusion_model = model.unet if hasattr(model, "unet") else model + + diffusion_model._tome_info = { + "size": None, + "hooks": [], + "args": { + "ratio": ratio, + "max_downsample": max_downsample, + "sx": sx, "sy": sy, + "agent_ratio": agent_ratio, + "use_rand": use_rand, + "generator": None, + "merge_attn": merge_attn, + "merge_crossattn": merge_crossattn, + "merge_mlp": merge_mlp + } + } + hook_tome_model(diffusion_model) + + for _, module in diffusion_model.named_modules(): + # If for some reason this has a different name, create an issue and I'll fix it + if isinstance_str(module, "BasicTransformerBlock"): + module._old_class_= [module.__class__] + _old_forward = None + make_tome_block_fn = make_diffusers_tome_block if is_diffusers else make_tome_block + module.__class__ = make_tome_block_fn(module.__class__, _old_forward) + module._tome_info = diffusion_model._tome_info + module._old_attn1 = [module.attn1.__class__] + module._old_attn2 = [module.attn2.__class__] + module.attn1.__class__ = make_agent_attn(module.attn1.__class__, k_scale2=k_scale2, k_shortcut=k_shortcut, attn_precision=attn_precision) + module.attn2.__class__ = make_agent_attn(module.attn2.__class__, k_scale2=k_scale2, k_shortcut=k_shortcut, attn_precision=attn_precision) + module.attn1.set_new_params() + module.attn2.set_new_params() + + # Something introduced in SD 2.0 (LDM only) + if not hasattr(module, "disable_self_attn") and not is_diffusers: + module.disable_self_attn = False + + # Something needed for older versions of diffusers + if not hasattr(module, "use_ada_layer_norm_zero") and is_diffusers: + module.use_ada_layer_norm = False + module.use_ada_layer_norm_zero = False + + return model + + + + + +def remove_patch(model: torch.nn.Module): + """ Removes a patch from a AgentSD Diffusion module if it was already patched. """ + # For diffusers + model = model.unet if hasattr(model, "unet") else model + + for _, module in model.named_modules(): + if hasattr(module, "_tome_info"): + for hook in module._tome_info["hooks"]: + hook.remove() + module._tome_info["hooks"].clear() + + if module.__class__.__name__ == "ToMeBlock": + if hasattr(module, "_old__class__"): + module.__class__ = module._old__class__[0] + else: + module.__class__ = module._parent + if hasattr(module, "_old_attn1"): + module.attn1.__class__ = module._old_attn1[0] + if hasattr(module, "_old_attn2"): + module.attn2.__class__ = module._old_attn2[0] + + return model diff --git a/extensions/CHECK/sd-webui-agentattention/agentsd/utils.py b/extensions/CHECK/sd-webui-agentattention/agentsd/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..5d3e533693b3d5ec5cc9f5cd6029b902cb088fe7 --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/agentsd/utils.py @@ -0,0 +1,42 @@ +''' +This code from the following repository: https://github.com/LeapLabTHU/Agent-Attention + +@article{han2023agent, + title={Agent Attention: On the Integration of Softmax and Linear Attention}, + author={Han, Dongchen and Ye, Tianzhu and Han, Yizeng and Xia, Zhuofan and Song, Shiji and Huang, Gao}, + journal={arXiv preprint arXiv:2312.08874}, + year={2023} +} +''' +import torch + + +def isinstance_str(x: object, cls_name: str): + """ + Checks whether x has any class *named* cls_name in its ancestry. + Doesn't require access to the class's implementation. + + Useful for patching! + """ + + for _cls in x.__class__.__mro__: + if _cls.__name__ == cls_name: + return True + + return False + + +def init_generator(device: torch.device, fallback: torch.Generator=None): + """ + Forks the current default random generator given device. + """ + if device.type == "cpu": + return torch.Generator(device="cpu").set_state(torch.get_rng_state()) + elif device.type == "cuda": + return torch.Generator(device=device).set_state(torch.cuda.get_rng_state()) + else: + if fallback is None: + return init_generator(torch.device("cpu")) + else: + return fallback + \ No newline at end of file diff --git a/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2415-1-desk.png b/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2415-1-desk.png new file mode 100644 index 0000000000000000000000000000000000000000..c265af3b5f16c980b65191bc608d19c91ca8729d --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2415-1-desk.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5873e4d1abc8d6ded47b229e32d8e847d3edf5a1b4c1ed78cc8c1e479084f2e +size 3102005 diff --git a/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2417-1-goldfinch, Carduelis carduelis.png b/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2417-1-goldfinch, Carduelis carduelis.png new file mode 100644 index 0000000000000000000000000000000000000000..ba7165e38372808a786f0db8c9f414bde3f1720a --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2417-1-goldfinch, Carduelis carduelis.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4c2d2ece39db3d617788535660b78311866fdc48a1ea1979fa5fb67900fca0f +size 2979669 diff --git a/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2418-1-bell pepper.png b/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2418-1-bell pepper.png new file mode 100644 index 0000000000000000000000000000000000000000..765e07907108031d4652d9a2c38b946aa8123eb6 --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2418-1-bell pepper.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e30d0d4f62be4c9cf182bb0c911cae96c08164e92ce48c48a6e65359a7f2622 +size 3243190 diff --git a/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2419-1-bicycle.png b/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2419-1-bicycle.png new file mode 100644 index 0000000000000000000000000000000000000000..661da7219076326d765dcbc5ac3206e4f80859b3 --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2419-1-bicycle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:717fb37f4a07d4cc41e2c7b04497a380622469f3bd2c60763a742c8bf0571c75 +size 4184269 diff --git a/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2428-1-desk.jpg b/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2428-1-desk.jpg new file mode 100644 index 0000000000000000000000000000000000000000..622f3f50925dcf77f0be3094c83a251a7e4fde5b --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/samples/xyz_grid-2428-1-desk.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49bbe109a2b7b88dff10f703d4d3f816d0bac3d431f28ea22aa8c4440fe5d2c4 +size 1042791 diff --git a/extensions/CHECK/sd-webui-agentattention/scripts/__pycache__/agat.cpython-310.pyc b/extensions/CHECK/sd-webui-agentattention/scripts/__pycache__/agat.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2572f5e6b3dd576783ad6e9ae3717eefb4e58d13 Binary files /dev/null and b/extensions/CHECK/sd-webui-agentattention/scripts/__pycache__/agat.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-agentattention/scripts/agat.py b/extensions/CHECK/sd-webui-agentattention/scripts/agat.py new file mode 100644 index 0000000000000000000000000000000000000000..5862f38d318a6013c55d2c7cb3effcce0df16082 --- /dev/null +++ b/extensions/CHECK/sd-webui-agentattention/scripts/agat.py @@ -0,0 +1,277 @@ +import logging +from os import environ +import modules.scripts as scripts +import gradio as gr +import numpy as np +from collections import OrderedDict +from typing import Union +import agentsd + +from modules import script_callbacks, rng, shared +from modules.script_callbacks import CFGDenoiserParams + +import torch + +logger = logging.getLogger(__name__) +logger.setLevel(environ.get("SD_WEBUI_LOG_LEVEL", logging.INFO)) + +""" +An implementation of Agent Attention for stable-diffusion-webui: https://github.com/LeapLabTHU/Agent-Attention + +@misc{han2023agent, + title={Agent Attention: On the Integration of Softmax and Linear Attention}, + author={Dongchen Han and Tianzhu Ye and Yizeng Han and Zhuofan Xia and Shiji Song and Gao Huang}, + year={2023}, + eprint={2312.08874}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} + +Author: v0xie +GitHub URL: https://github.com/v0xie/sd-webui-agentattention + +""" + +# TODO: Refactor parameters into a class +# class PassSettings: +# def __init__(self, sx, sy, ratio, agent_ratio): +# self.sx = sx +# self.sy = sy +# self.ratio = ratio +# self.agent_ratio = agent_ratio + +class AgentAttentionExtensionScript(scripts.Script): + # Extension title in menu UI + def title(self): + return "Agent Attention" + + # Decide to show menu in txt2img or img2img + def show(self, is_img2img): + return scripts.AlwaysVisible + + # Setup menu ui detail + def ui(self, is_img2img): + with gr.Accordion('AgentAttention', open=False): + active = gr.Checkbox(value=False, default=False, label="Active", elem_id='aa_active') + with gr.Row(): + hires_fix_only = gr.Checkbox(value=False, default=False, label="Apply to Hires. Fix Only", elem_id = 'aa_hires_fix_only') + use_fp32 = gr.Checkbox(value=False, default=False, label="Use FP32 Precision (for SD2.1)", elem_id = 'aa_use_fp32') + use_sp = gr.Checkbox(value=False, default=False, label="Use Second Pass", elem_id = 'aa_use_sp') + sp_step = gr.Slider(value = 20, minimum = 0, maximum = 100, step = 1, label="Second Pass Step", elem_id = 'aa_sp_step') + max_downsample = gr.Radio(choices=[1,2,4,8], value=1, default=1, label="Max Downsample", elem_id = 'aa_max_downsample', info="For SDXL set to values > 1") + with gr.Accordion('First Pass', open=False): + sx = gr.Slider(value = 4, minimum = 0, maximum = 10, step = 1, label="sx", elem_id = 'aa_sx') + sy = gr.Slider(value = 4, minimum = 0, maximum = 10, step = 1, label="sy", elem_id = 'aa_sy') + ratio = gr.Slider(value = 0.4, minimum = 0.0, maximum = 1.0, step = 0.01, label="Ratio", elem_id = 'aa_ratio') + agent_ratio = gr.Slider(value = 0.95, minimum = 0.0, maximum = 1.0, step = 0.01, label="Agent Ratio", elem_id = 'aa_agent_ratio') + with gr.Accordion('Second Pass', open=False): + sp_sx = gr.Slider(value = 2, minimum = 0, maximum = 10, step = 1, label="sx", elem_id = 'aa_sp_sx') + sp_sy = gr.Slider(value = 2, minimum = 0, maximum = 10, step = 1, label="sy", elem_id = 'aa_sp_sy') + sp_ratio = gr.Slider(value = 0.4, minimum = 0.0, maximum = 1.0, step = 0.01, label="Ratio", elem_id = 'aa_sp_ratio') + sp_agent_ratio = gr.Slider(value = 0.5, minimum = 0.0, maximum = 1.0, step = 0.01, label="Agent Ratio", elem_id = 'aa_sp_agent_ratio') + with gr.Accordion('Advanced', open=False): + btn_remove_patch = gr.Button(value="Remove Patch", elem_id='aa_remove_patch') + btn_remove_patch.click(self.remove_patch) + + active.do_not_save_to_config = True + use_sp.do_not_save_to_config = True + sp_step.do_not_save_to_config = True + sx.do_not_save_to_config = True + sy.do_not_save_to_config = True + ratio.do_not_save_to_config = True + agent_ratio.do_not_save_to_config = True + sp_sx.do_not_save_to_config = True + sp_sy.do_not_save_to_config = True + sp_ratio.do_not_save_to_config = True + sp_agent_ratio.do_not_save_to_config = True + use_fp32.do_not_save_to_config = True + max_downsample.do_not_save_to_config = True + hires_fix_only.do_not_save_to_config = True + self.infotext_fields = [ + (active, lambda d: gr.Checkbox.update(value='AgAt Active' in d)), + (use_sp, 'AgAt Use Second Pass'), + (sp_step, 'AgAt Second Pass Step'), + (sx, 'AgAt First Pass sx'), + (sy, 'AgAt First Pass sy'), + (ratio, 'AgAt First Pass Ratio'), + (agent_ratio, 'AgAt First Pass Agent Ratio'), + (sp_sx, 'AgAt Second Pass sx'), + (sp_sy, 'AgAt Second Pass sy'), + (sp_ratio, 'AgAt Second Pass Ratio'), + (sp_agent_ratio, 'AgAt Second Pass Agent Ratio'), + (use_fp32, 'AgAt Use FP32 Precision'), + (max_downsample, 'AgAt Max Downsample'), + (hires_fix_only, 'AgAt Apply to Hires. Fix Only'), + ] + self.paste_field_names = [ + 'aa_active', + 'aa_use_sp', + 'aa_sp_step', + 'aa_sx', + 'aa_sy', + 'aa_ratio', + 'aa_agent_ratio', + 'aa_sp_sx', + 'aa_sp_sy', + 'aa_sp_ratio', + 'aa_sp_agent_ratio', + 'aa_use_fp32', + 'aa_max_downsample' + 'aa_hires_fix_only' + ] + + return [active, use_sp, sp_step, sx, sy, ratio, agent_ratio, sp_sx, sp_sy, sp_ratio, sp_agent_ratio, use_fp32, max_downsample, hires_fix_only] + + def before_process_batch(self, p, active, use_sp, sp_step, sx, sy, ratio, agent_ratio, sp_sx, sp_sy, sp_ratio, sp_agent_ratio, use_fp32, max_downsample, hires_fix_only, *args, **kwargs): + active = getattr(p, "aa_active", active) + if active is False: + return + + hires_fix_only = getattr(p, "aa_hires_fix_only", hires_fix_only) + if hires_fix_only is True: + + p.extra_generation_params = { + "AgAt Active": active, + "AgAt Apply to Hires. Fix Only": hires_fix_only, + } + logger.debug('Hires. Fix Only is True, skipping') + return + + return self.setup_hook(p, active, use_sp, sp_step, sx, sy, ratio, agent_ratio, sp_sx, sp_sy, sp_ratio, sp_agent_ratio, use_fp32, max_downsample, hires_fix_only) + + def setup_hook(self, p, active, use_sp, sp_step, sx, sy, ratio, agent_ratio, sp_sx, sp_sy, sp_ratio, sp_agent_ratio, use_fp32, max_downsample, hires_fix_only): + active = getattr(p, "aa_active", active) + if active is False: + return + use_sp = getattr(p, "aa_use_sp", use_sp) + sp_step = getattr(p, "aa_sp_step", sp_step) + sx = getattr(p, "aa_sx", sx) + sy = getattr(p, "aa_sy", sy) + ratio = getattr(p, "aa_ratio", ratio) + agent_ratio = getattr(p, "aa_agent_ratio", agent_ratio) + sp_sx = getattr(p, "aa_sp_sx", sp_sx) + sp_sy = getattr(p, "aa_sp_sy", sp_sy) + sp_ratio = getattr(p, "aa_sp_ratio", sp_ratio) + sp_agent_ratio = getattr(p, "aa_sp_agent_ratio", sp_agent_ratio) + use_fp32 = getattr(p, "aa_use_fp32", use_fp32) + max_downsample = getattr(p, "aa_max_downsample", max_downsample) + hires_fix_only = getattr(p, "aa_hires_fix_only", hires_fix_only) + + p.extra_generation_params.update({ + "AgAt Active": active, + "AgAt Use Second Pass": use_sp, + "AgAt Second Pass Step": sp_step, + "AgAt First Pass sx": sx, + "AgAt First Pass sy": sy, + "AgAt First Pass Ratio": ratio, + "AgAt First Pass Agent Ratio": agent_ratio, + "AgAt Second Pass sx": sp_sx, + "AgAt Second Pass sy": sp_sy, + "AgAt Second Pass Ratio": sp_ratio, + "AgAt Second Pass Agent Ratio": sp_agent_ratio, + "AgAt Use FP32 Precision": use_fp32, + "AgAt Max Downsample": max_downsample, + "AgAt Apply to Hires. Fix Only": hires_fix_only, + }) + self.create_hook(p, active, use_sp, sp_step, sx, sy, ratio, agent_ratio, sp_sx, sp_sy, sp_ratio, sp_agent_ratio, use_fp32, max_downsample, hires_fix_only) + + def create_hook(self, p, active, use_sp, sp_step, sx, sy, ratio, agent_ratio, sp_sx, sp_sy, sp_ratio, sp_agent_ratio, use_fp32, max_downsample, hires_fix_only): + # Use lambda to call the callback function with the parameters to avoid global variables + y = lambda params: self.on_cfg_denoiser_callback(params, active=active, use_sp=use_sp, sp_step=sp_step, sx=sx, sy=sy, ratio=ratio, agent_ratio=agent_ratio, sp_sx=sp_sx, sp_sy=sp_sy, sp_ratio=sp_ratio, sp_agent_ratio=sp_agent_ratio, use_fp32=use_fp32, max_downsample=max_downsample, hires_fix_only=hires_fix_only) + + logger.debug('Hooked callbacks') + script_callbacks.on_cfg_denoiser(y) + script_callbacks.on_script_unloaded(self.unhook_callbacks) + + def postprocess_batch(self, p, active, use_sp, sp_step, sx, sy, ratio, agent_ratio, sp_sx, sp_sy, sp_ratio, sp_agent_ratio, use_fp32, max_downsample, hires_fix_only, *args, **kwargs): + self.unhook_callbacks() + + def unhook_callbacks(self): + logger.debug('Unhooked callbacks') + self.remove_patch() + script_callbacks.remove_current_script_callbacks() + + def apply_patch(self, sx=2, sy=2, ratio=0.4, agent_ratio=0.95, use_fp32=False, max_downsample=1): + logger.debug('Applied patch with sx: %d, sy: %d, ratio: %f, agent_ratio: %f, use_fp32: %s, max_downsample: %d', sx, sy, ratio, agent_ratio, use_fp32, max_downsample) + agentsd.apply_patch(shared.sd_model, sx=sx, sy=sy, ratio=ratio, agent_ratio=agent_ratio, attn_precision='fp32' if use_fp32 else None, max_downsample=max_downsample) + + def remove_patch(self): + logger.debug('Removed patch') + agentsd.remove_patch(shared.sd_model) + + def on_cfg_denoiser_callback(self, params: CFGDenoiserParams, active, use_sp, sp_step, sx, sy, ratio, agent_ratio, sp_sx, sp_sy, sp_ratio, sp_agent_ratio, use_fp32, max_downsample, hires_fix_only, *args, **kwargs): + sampling_step = params.sampling_step + + if sampling_step == 0: + self.remove_patch() + self.apply_patch(sx=sx, sy=sy, ratio=ratio, agent_ratio=agent_ratio, use_fp32=use_fp32, max_downsample=max_downsample) + + if sampling_step == sp_step: + self.remove_patch() + if use_sp: + self.apply_patch(sx=sp_sx, sy=sp_sy, ratio=sp_ratio, agent_ratio=sp_agent_ratio, use_fp32=use_fp32, max_downsample=max_downsample) + + def before_hr(self, p, *args, **kwargs): + self.unhook_callbacks() + + params = getattr(p, "extra_generation_params", None) + if not params: + logger.error("Missing attribute extra_generation_params") + return + + active = params.get("AgAt Active", False) + if active is False: + return + + apply_to_hr_pass = params.get("AgAt Apply to Hires. Fix Only", False) + if apply_to_hr_pass is False: + logger.debug("Disabled for hires. fix") + return + + self.setup_hook(p, *args, **kwargs) + + +# XYZ Plot +# Based on @mcmonkey4eva's XYZ Plot implementation here: https://github.com/mcmonkeyprojects/sd-dynamic-thresholding/blob/master/scripts/dynamic_thresholding.py +def aa_apply_override(field, boolean: bool = False): + def fun(p, x, xs): + if boolean: + x = True if x.lower() == "true" else False + setattr(p, field, x) + return fun + +def aa_apply_field(field): + def fun(p, x, xs): + if not hasattr(p, "aa_active"): + setattr(p, "aa_active", True) + setattr(p, field, x) + + return fun + +def make_axis_options(): + xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ in ('scripts.xyz_grid', 'xyz_grid.py')][0].module + extra_axis_options = { + xyz_grid.AxisOption("[AgentAttention] Active", str, aa_apply_override('aa_active', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[AgentAttention] Use Second Pass", str, aa_apply_override('aa_use_sp', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[AgentAttention] Second Pass Step", int, aa_apply_field("aa_sp_step")), + xyz_grid.AxisOption("[AgentAttention] First Pass sx", int, aa_apply_field("aa_sx")), + xyz_grid.AxisOption("[AgentAttention] First Pass sy", int, aa_apply_field("aa_sy")), + xyz_grid.AxisOption("[AgentAttention] First Pass Ratio", float, aa_apply_field("aa_ratio")), + xyz_grid.AxisOption("[AgentAttention] First Pass Agent Ratio", float, aa_apply_field("aa_agent_ratio")), + xyz_grid.AxisOption("[AgentAttention] Second Pass sx", int, aa_apply_field("aa_sp_sx")), + xyz_grid.AxisOption("[AgentAttention] Second Pass sy", int, aa_apply_field("aa_sp_sy")), + xyz_grid.AxisOption("[AgentAttention] Second Pass Ratio", float, aa_apply_field("aa_sp_ratio")), + xyz_grid.AxisOption("[AgentAttention] Second Pass Agent Ratio", float, aa_apply_field("aa_sp_agent_ratio")), + xyz_grid.AxisOption("[AgentAttention] Use FP32", str, aa_apply_override('aa_use_fp32', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[AgentAttention] Max Downsample", int, aa_apply_field('aa_max_downsample')), + } + if not any("[AgentAttention]" in x.label for x in xyz_grid.axis_options): + xyz_grid.axis_options.extend(extra_axis_options) + +def callback_before_ui(): + try: + make_axis_options() + except: + logger.exception("AgentAttention: Error while making axis options") + +script_callbacks.on_before_ui(callback_before_ui) diff --git a/extensions/CHECK/sd-webui-color-correction-extras/.gitignore b/extensions/CHECK/sd-webui-color-correction-extras/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..dc7e9057257b5967fc2c14f95f1e50135307c8c7 --- /dev/null +++ b/extensions/CHECK/sd-webui-color-correction-extras/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +.vscode +.directory diff --git a/extensions/CHECK/sd-webui-color-correction-extras/README.md b/extensions/CHECK/sd-webui-color-correction-extras/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8235288bf82513b023f59a5df850e713c8a14034 --- /dev/null +++ b/extensions/CHECK/sd-webui-color-correction-extras/README.md @@ -0,0 +1,9 @@ +# Color Correction Extras + +This simple extention adds native color correction feature from [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) into its "Extras" tab + +So, you can use this feature in postprocessing + +![](images/img1.jpg) + +If you have installed [StableSR](https://github.com/pkuliyi2015/sd-webui-stablesr) extention, you can choose its color correction methods from dropbox "Method". It's optional diff --git a/extensions/CHECK/sd-webui-color-correction-extras/images/img1.jpg b/extensions/CHECK/sd-webui-color-correction-extras/images/img1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dd3cc9f7a00bc793d3d6785e2ff0fb59edc07314 Binary files /dev/null and b/extensions/CHECK/sd-webui-color-correction-extras/images/img1.jpg differ diff --git a/extensions/CHECK/sd-webui-color-correction-extras/metadata.ini b/extensions/CHECK/sd-webui-color-correction-extras/metadata.ini new file mode 100644 index 0000000000000000000000000000000000000000..1d89c388ea5d53a02afae28004aaca88e646e1ea --- /dev/null +++ b/extensions/CHECK/sd-webui-color-correction-extras/metadata.ini @@ -0,0 +1,81 @@ +# This section contains information about the extension itself. +# This section is optional. +[Extension] + +# A canonical name of the extension. +# Only lowercase letters, numbers, dashes and underscores are allowed. +# This is a unique identifier of the extension, and the loader will refuse to +# load two extensions with the same name. If the name is not supplied, the +# name of the extension directory is used. Other extensions can use this +# name to refer to this extension in the file. +Name = sd-webui-color-correction-extras + +# A comma-or-space-separated list of extensions that this extension requires +# to be installed and enabled. +# The loader will generate a warning if any of the extensions in this list is +# not installed or disabled. +; Requires = sd-webui-controlnet + +[scripts] +After = sd-webui-stablesr + +; # Declaring relationships of folders +; # +; # This section declares relations of all files in `scripts` directory. +; # By changing the section name, it can also be used on other directories +; # walked by `load_scripts` function (for example `javascript` and `localization`). +; # This section is optional. +; [scripts] + +; # A comma-or-space-separated list of extensions that files in this folder requires +; # to be present. +; # It is only allowed to specify an extension here. +; # The loader will generate a warning if any of the extensions in this list is +; # not installed or disabled. +; Requires = another-extension, yet-another-extension + +; # A comma-or-space-separated list of extensions that files in this folder wants +; # to be loaded before. +; # It is only allowed to specify an extension here. +; # The loading order of all files in the specified folder will be moved so that +; # the files in the current extension are loaded before the files in the same +; # folder in the listed extension. +; Before = another-extension, yet-another-extension + +; # A comma-or-space-separated list of extensions that files in this folder wants +; # to be loaded after. +; # Other details are the same as `Before` key. +; After = another-extension, yet-another-extension + +; # Declaring relationships of a specific file +; # +; # This section declares relations of a specific file to files in the same +; # folder of other extensions. +; # By changing the section name, it can also be used on other directories +; # walked by `load_scripts` function (for example `javascript` and `localization`). +; # This section is optional. +; [scripts/another-script.py] + +; # A comma-or-space-separated list of extensions/files that this file requires +; # to be present. +; # The `Requires` key in the folder section will be prepended to this list. +; # The loader will generate a warning if any of the extensions/files in this list is +; # not installed or disabled. +; # It is allowed to specify either an extension or a specific file. +; # When referencing a file, the folder name must be omitted. +; # +; # For example, the `yet-another-extension/another-script.py` item refers to +; # `scripts/another-script.py` in `yet-another-extension`. +; Requires = another-extension, yet-another-extension/another-script.py, xyz_grid.py + +; # A comma-or-space-separated list of extensions that this file wants +; # to be loaded before. +; # The `Before` key in the folder section will be prepended to this list. +; # The loading order of this file will be moved so that this file is +; # loaded before the referenced file in the list. +; Before = another-extension, yet-another-extension/another-script.py, xyz_grid.py + +; # A comma-or-space-separated list of extensions that this file wants +; # to be loaded after. +; # Other details are the same as `Before` key. +; After = another-extension, yet-another-extension/another-script.py, xyz_grid.py diff --git a/extensions/CHECK/sd-webui-color-correction-extras/scripts/__pycache__/color_correction_extras.cpython-310.pyc b/extensions/CHECK/sd-webui-color-correction-extras/scripts/__pycache__/color_correction_extras.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e12fc0b62a73b288a93878acd53e922488b96fb Binary files /dev/null and b/extensions/CHECK/sd-webui-color-correction-extras/scripts/__pycache__/color_correction_extras.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-color-correction-extras/scripts/color_correction_extras.py b/extensions/CHECK/sd-webui-color-correction-extras/scripts/color_correction_extras.py new file mode 100644 index 0000000000000000000000000000000000000000..a4c3f45f1ea086d745404fa1c182c67d16a434f8 --- /dev/null +++ b/extensions/CHECK/sd-webui-color-correction-extras/scripts/color_correction_extras.py @@ -0,0 +1,111 @@ +from modules.processing import apply_color_correction, setup_color_correction +import gradio as gr +from modules import scripts_postprocessing +import copy + +NAME = 'Color Correction' +METHODS = None + +def extraImagesAvaliable(): + pp = scripts_postprocessing.PostprocessedImage(None) + return hasattr(pp, 'extra_images') + +if hasattr(scripts_postprocessing.ScriptPostprocessing, 'process_firstpass'): # webui >= 1.7 + from modules.ui_components import InputAccordion +else: + InputAccordion = None + + +colorfix = None +try: + from modules import colorfix +except ImportError: + try: + from srmodule import colorfix + except ImportError: + pass + + +def a1111_color_correction(targetImage, sampleImage): + return apply_color_correction(setup_color_correction(sampleImage), targetImage) + + +def applyColorCorrectionMethod(method, targetImage, sampleImage): + if method == 'A1111': + color_correction_func = a1111_color_correction + else: + if targetImage.mode == "RGBA": + targetImage = targetImage.convert(mode = "RGB") + if sampleImage.mode == "RGBA": + sampleImage = sampleImage.convert(mode = "RGB") + if targetImage.size != sampleImage.size: + sampleImage = sampleImage.resize(targetImage.size) + + if method == 'Wavelet': + color_correction_func = colorfix.wavelet_color_fix + else: + color_correction_func = colorfix.adain_color_fix + + print('Appling color correction', method) + return color_correction_func(targetImage, sampleImage) + + +class ColorCorrectionExtras(scripts_postprocessing.ScriptPostprocessing): + name = NAME + order = 21000 + + def __init__(self): + self.init_image = None + + def ui(self): + global METHODS + with ( + InputAccordion(False, label=NAME) if InputAccordion + else gr.Accordion(NAME, open=False) + as enable + ): + if not InputAccordion: + enable = gr.Checkbox(False, label="Enable") + img = gr.Image(label="Sample Image", source="upload", interactive=True, type="pil", elem_id="image") + gr.Markdown("*if not image provided, init image is used*") + with gr.Row(): + if colorfix: + METHODS = ['A1111', 'Wavelet', 'AdaIN'] + method = gr.Dropdown(METHODS, label="Method", value='A1111') + else: + METHODS = ['A1111'] + method = gr.Textbox(value='A1111', visible=False) + extraProcessVisiable = extraImagesAvaliable() + if len(METHODS) == 1: + extraProcessVisiable = False + extraProcess = gr.Checkbox(False, label="Extra use all methods", visible=extraProcessVisiable) + args = { + 'img': img, + 'enable': enable, + 'method' : method, + 'extraProcess' : extraProcess, + } + return args + + + def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, **args): + self.init_image = pp.image.copy() + + + def process(self, pp: scripts_postprocessing.PostprocessedImage, **args): + if args['enable'] == False: + return + targetImage = copy.copy(pp.image) + sampleImage = (args and args['img']) or self.init_image + method = args['method'] + extraProcess = args['extraProcess'] + + pp.image = applyColorCorrectionMethod(method, targetImage, sampleImage) + pp.info[NAME] = method + + if extraProcess and len(METHODS) > 1 and extraImagesAvaliable(): + for extraMethod in METHODS: + if extraMethod == method: + continue + image = applyColorCorrectionMethod(extraMethod, targetImage, sampleImage) + pp.extra_images.append(image) diff --git a/extensions/CHECK/sd-webui-cutoff/.gitignore b/extensions/CHECK/sd-webui-cutoff/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..08e511ddba82b25b27b3e6485ee0120e9c6200bc --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +/.vs diff --git a/extensions/CHECK/sd-webui-cutoff/LICENSE b/extensions/CHECK/sd-webui-cutoff/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d1e1072ee5e1d109c15b6fd18756aedc2a401840 --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/LICENSE @@ -0,0 +1 @@ +MIT License diff --git a/extensions/CHECK/sd-webui-cutoff/README.md b/extensions/CHECK/sd-webui-cutoff/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5bb725a10ec4c149b70b25b9ae8afef31cb43995 --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/README.md @@ -0,0 +1,174 @@ +# Cutoff - Cutting Off Prompt Effect + +![cover](./images/cover.jpg) + +
+Update Info + +Upper is newer. + +
+
026ff95a492a533a4a6e5fb2959c2324258c232c
+
SDXL support.
+
527ed922b2c4f8d2620376589dfce0f9f4b622ad
+
Add support for the newer version of WebUI.
+
20e87ce264338b824296b7559679ed1bb0bdacd7
+
Skip empty targets.
+
03bfe60162ba418e18dbaf8f1b9711fd62195ef3
+
Add Disable for Negative prompt option. Default is True.
+
f0990088fed0f5013a659cacedb194313a398860
+
Accept an empty prompt.
+
+
+ +## What is this? + +This is an extension for [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) which limits the tokens' influence scope. + +SDv1, SDv2 and SDXL are supported. + +## Usage + +1. Select `Enabled` checkbox. +2. Input words which you want to limit scope in `Target tokens`. +3. Generate images. + +## Note + +If the generated image was corrupted or something like that, try to change the `Weight` value or change the interpolation method to `SLerp`. Interpolation method can be found in `Details`. + +### `Details` section + +
+
Disable for Negative prompt.
+
If enabled, Cutoff will not work for the negative prompt. Default is true.
+
Cutoff strongly.
+
See description below. Default is false.
+
Interpolation method
+
How "padded" and "original" vectors will be interpolated. Default is Lerp.
+
Padding token
+
What token will be padded instead of Target tokens. Default is _ (underbar).
+
+ +## Examples + +### SDv1 + +``` +7th_anime_v3_A-fp16 / kl-f8-anime2 / DPM++ 2M Karras / 15 steps / 512x768 +Prompt: a cute girl, white shirt with green tie, red shoes, blue hair, yellow eyes, pink skirt +Negative Prompt: (low quality, worst quality:1.4), nsfw +Target tokens: white, green, red, blue, yellow, pink +``` + +Sample 1. + +![sample 1](./images/sample-1.png) + +Sample 2. (use `SLerp` for interpolation) + +![sample 2](./images/sample-2.png) + +Sample 3. + +![sample 3](./images/sample-3.png) + +### SDXL + +It seems that the Stability AI's base model of SDXL is much improved on token separation. So the effect of `cutoff` is limited. + +``` +(some models) / sdxl_vae / DPM++ 3M SDE / 50 steps / 768x1344 +Prompt: full body shot of a cute girl, wearing white shirt with green tie, red shoes, blue hair, yellow eyes, pink skirt +Negative Prompt: (low quality, worst quality:1.4), nsfw, close up +Target tokens: white, green, red, blue, yellow, pink +Cutoff weight: 1.0 +``` + +Sample 4. (Model = `sd_xl_base_1.0`) + +![sample 4](./images/sample-4_small.png) + +Sample 5. (Model = `hassakuXLSfwNsfw_alphaV07`) + +![sample 5](./images/sample-5_small.png) + +## How it works + +- [Japanese](#japanese) +- [English](#english) + +or see [#5](https://github.com/hnmr293/sd-webui-cutoff/issues/5). + +![idea](./images/idea.png) + +### Japanese + +プロンプトをCLIPに通して得られる (77, 768) 次元の埋め込み表現(?正式な用語は分かりません)について、 +ごく単純には、77個の行ベクトルはプロンプト中の75個のトークン(+開始トークン+終了トークン)に対応していると考えられる。 + +※上図は作図上、この説明とは行と列を入れ替えて描いている。 + +このベクトルには単語単体の意味だけではなく、文章全体の、例えば係り結びなどの情報を集約したものが入っているはずである。 + +ここで `a cute girl, pink hair, red shoes` というプロンプトを考える。 +普通、こういったプロンプトの意図は + +1. `pink` は `hair` だけに係っており `shoes` には係っていない。 +2. 同様に `red` も `hair` には係っていない。 +3. `a cute girl` は全体に係っていて欲しい。`hair` や `shoes` は女の子に合うものが出て欲しい。 + +……というもののはずである。 + +しかしながら、[EvViz2](https://github.com/hnmr293/sd-webui-evviz2) などでトークン間の関係を見ると、そううまくはいっていないことが多い。 +つまり、`shoes` の位置のベクトルに `pink` の影響が出てしまっていたりする。 + +一方で上述の通り `a cute girl` の影響は乗っていて欲しいわけで、どうにかして、特定のトークンの影響を取り除けるようにしたい。 + +この拡張では、指定されたトークンを *padding token* に書き換えることでそれを実現している。 + +たとえば `red shoes` の部分に対応して `a cute girl, _ hair, red shoes` というプロンプトを生成する。`red` と `shoes` に対応する位置のベクトルをここから生成したもので上書きしてやることで、`pink` の影響を除外している。 + +これを `pink` の側から見ると、自分の影響が `pink hair` の範囲内に制限されているように見える。What is this? の "limits the tokens' influence scope" はそういう意味。 + +ところで `a cute girl` の方は、`pink hair, red shoes` の影響を受けていてもいいし受けなくてもいいような気がする。 +そこでこの拡張では、こういうどちらでもいいプロンプトに対して + +1. `a cute girl, pink hair, red shoes` +2. `a cute girl, _ hair, _ shoes` + +のどちらを適用するか選べるようにしている。`Details` の `Cutoff strongly` がそれで、オフのとき1.を、オンのとき2.を、それぞれ選ぶようになっている。 +元絵に近いのが出るのはオフのとき。デフォルトもこちらにしてある。 + +### English + +NB. The following text is a translation of the Japanese text above by [DeepL](https://www.deepl.com/translator). + +For the (77, 768) dimensional embedded representation (I don't know the formal terminology), one could simply assume that the 77 row vectors correspond to the 75 tokens (+ start token and end token) in the prompt. + +Note: The above figure is drawn with the rows and columns interchanged from this explanation. + +This vector should contain not only the meanings of individual words, but also the aggregate information of the whole sentence, for example, the connection between words. + +Consider the prompt `a cute girl, pink hair, red shoes`. Normally, the intent of such a prompt would be + +- `pink` is only for `hair`, not `shoes`. +- Similarly, `red` does not refer to `hair`. +- We want `a cute girl` to be about the whole thing, and we want the `hair` and `shoes` to match the girl. + +However, when we look at the relationship between tokens in [EvViz2](https://github.com/hnmr293/sd-webui-evviz2) and other tools, we see that it is not always that way. In other words, the position vector of the `shoes` may be affected by `pink`. + +On the other hand, as mentioned above, we want the influence of `a cute girl` to be present, so we want to be able to somehow remove the influence of a specific token. + +This extension achieves this by rewriting the specified tokens as a *padding token*. + +For example, for the `red shoes` part, we generate the prompt `a cute girl, _ hair, red shoes`, and by overwriting the position vectors corresponding to `red` and `shoes` with those generated from this prompt, we remove the influence of `pink`. + +From `pink`'s point of view, it appears that its influence is limited to the `pink hair`'s scope. + +By the way, `a cute girl` may or may not be influenced by `pink hair` and `red shoes`. So, in this extension, for such a prompt that can be either + +1. `a cute girl, pink hair, red shoes` +2. `a cute girl, _ hair, _ shoes` + +The `Cutoff strongly` in the `Details` section allows you to select 1 when it is off and 2 when it is on. The one that comes out closer to the original image is "off". The default is also set this way. diff --git a/extensions/CHECK/sd-webui-cutoff/images/cover.jpg b/extensions/CHECK/sd-webui-cutoff/images/cover.jpg new file mode 100644 index 0000000000000000000000000000000000000000..59072fe9205825e4620ae01c2e2d909284e1337d --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/images/cover.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:129a1d562085cd9af3c46ad42ce2a3133e5306d03a7c7e37942b634d55de3666 +size 1955200 diff --git a/extensions/CHECK/sd-webui-cutoff/images/idea.png b/extensions/CHECK/sd-webui-cutoff/images/idea.png new file mode 100644 index 0000000000000000000000000000000000000000..1e44a6a99246be77689f115b1e13c300e1cfdc66 Binary files /dev/null and b/extensions/CHECK/sd-webui-cutoff/images/idea.png differ diff --git a/extensions/CHECK/sd-webui-cutoff/images/sample-1.png b/extensions/CHECK/sd-webui-cutoff/images/sample-1.png new file mode 100644 index 0000000000000000000000000000000000000000..8bf8d451a522e3c97f258f7e982c3fee7467d63f --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/images/sample-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4e36a8555ed4818a166fe7c86c4b91ba67d5255c6ef51ad206b163f1aa779c6 +size 5929523 diff --git a/extensions/CHECK/sd-webui-cutoff/images/sample-2.png b/extensions/CHECK/sd-webui-cutoff/images/sample-2.png new file mode 100644 index 0000000000000000000000000000000000000000..2bbf2cf2f3aed2c169152014d2f331b55f0104ce --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/images/sample-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0546d2f8d3ea624b87839f8a0698a33db3465b3919540fc8c1f51b7467055455 +size 1016674 diff --git a/extensions/CHECK/sd-webui-cutoff/images/sample-3.png b/extensions/CHECK/sd-webui-cutoff/images/sample-3.png new file mode 100644 index 0000000000000000000000000000000000000000..232b671c818b5d9e9374a01daee4e0e1d0e9c666 --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/images/sample-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1d43f8e30d9078417a8ba22fc28dec5a16279adad189c917efb7a99b8706a4d +size 1212008 diff --git a/extensions/CHECK/sd-webui-cutoff/images/sample-4.png b/extensions/CHECK/sd-webui-cutoff/images/sample-4.png new file mode 100644 index 0000000000000000000000000000000000000000..a0644fefab1741fe9944f6662e6c33540c4d4f3d --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/images/sample-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e0a3e881de33c21494890c24562ec8290706d30f897a39705bc7e119b20a8ba +size 9624958 diff --git a/extensions/CHECK/sd-webui-cutoff/images/sample-4_small.png b/extensions/CHECK/sd-webui-cutoff/images/sample-4_small.png new file mode 100644 index 0000000000000000000000000000000000000000..c5452146500bb451cdcb9223868f8314720246ec --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/images/sample-4_small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66153c9aa290adbdb6a968b3be994c1c85cb15a0d74fd964906c3a790382a034 +size 2719740 diff --git a/extensions/CHECK/sd-webui-cutoff/images/sample-5.png b/extensions/CHECK/sd-webui-cutoff/images/sample-5.png new file mode 100644 index 0000000000000000000000000000000000000000..911917345db78b8f3a87eb5be1a02fc2d63ed53c --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/images/sample-5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de08187d88dc1e4177888e1683c46b71ef4d5694d1629c0db4b43b1b4d176cdb +size 9359682 diff --git a/extensions/CHECK/sd-webui-cutoff/images/sample-5_small.png b/extensions/CHECK/sd-webui-cutoff/images/sample-5_small.png new file mode 100644 index 0000000000000000000000000000000000000000..26abafeafe47ca40527f44eb9689f12f4ec297e8 --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/images/sample-5_small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8c9bbb376ca62494ad04de3483320ecbdac7ab0797c3a9282ffa633284f0f88 +size 2723197 diff --git a/extensions/CHECK/sd-webui-cutoff/scripts/__pycache__/cutoff.cpython-310.pyc b/extensions/CHECK/sd-webui-cutoff/scripts/__pycache__/cutoff.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..204041b6d12a94e53491645293a601d52ffeb3a5 Binary files /dev/null and b/extensions/CHECK/sd-webui-cutoff/scripts/__pycache__/cutoff.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-cutoff/scripts/cutoff.py b/extensions/CHECK/sd-webui-cutoff/scripts/cutoff.py new file mode 100644 index 0000000000000000000000000000000000000000..46e9240e4de47b57e981c7a577e3ae7f23c575f0 --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/scripts/cutoff.py @@ -0,0 +1,284 @@ +from collections import defaultdict +from typing import Union, List, Tuple, Dict + +import numpy as np +import torch +from torch import Tensor, nn +import gradio as gr + +from modules.processing import StableDiffusionProcessing +from modules import scripts + +from scripts.cutofflib.sdhook import SDHook +from scripts.cutofflib.embedding import CLIP, CLIP_SDXL, generate_prompts, token_to_block +from scripts.cutofflib.utils import log, set_debug +from scripts.cutofflib.xyz import init_xyz + +NAME = 'Cutoff' +PAD = '_' + +def check_neg(s: str, negative_prompt: str, all_negative_prompts: Union[List[str],None]): + if s == negative_prompt: + return True + + if all_negative_prompts is not None: + return s in all_negative_prompts + + return False + +def slerp(t, v0, v1, DOT_THRESHOLD=0.9995): + # cf. https://memo.sugyan.com/entry/2022/09/09/230645 + + inputs_are_torch = False + input_device = v0.device + if not isinstance(v0, np.ndarray): + inputs_are_torch = True + v0 = v0.cpu().numpy() + v1 = v1.cpu().numpy() + + dot = np.sum(v0 * v1 / (np.linalg.norm(v0) * np.linalg.norm(v1))) + if np.abs(dot) > DOT_THRESHOLD: + v2 = (1 - t) * v0 + t * v1 + else: + theta_0 = np.arccos(dot) + sin_theta_0 = np.sin(theta_0) + theta_t = theta_0 * t + sin_theta_t = np.sin(theta_t) + s0 = np.sin(theta_0 - theta_t) / sin_theta_0 + s1 = sin_theta_t / sin_theta_0 + v2 = s0 * v0 + s1 * v1 + + if inputs_are_torch: + v2 = torch.from_numpy(v2).to(input_device) + + return v2 + + +class Hook(SDHook): + + def __init__( + self, + enabled: bool, + targets: List[str], + padding: Union[str,int], + weight: float, + disable_neg: bool, + strong: bool, + interpolate: str, + ): + super().__init__(enabled) + self.targets = targets + self.padding = padding + self.weight = float(weight) + self.disable_neg = disable_neg + self.strong = strong + self.intp = interpolate + + def interpolate(self, t1: Tensor, t2: Tensor, w): + if self.intp == 'lerp': + return torch.lerp(t1, t2, w) + else: + return slerp(w, t1, t2) + + def hook_clip(self, p: StableDiffusionProcessing, clip: nn.Module): + + skip = False + + def hook(mod: nn.Module, inputs: Tuple[Union[List[str],Dict[str,Tensor]]], output: Union[Tensor,Dict[str,Tensor]]): + nonlocal skip + + if skip: + # called from below + return + + if not hasattr(p.sd_model, 'is_sdxl') or not p.sd_model.is_sdxl: + # SD + assert isinstance(mod, CLIP) + prompts = inputs[0] + output = output.clone() + output_vector = output + def process(prompts): + return mod(prompts) + else: + # SDXL + assert isinstance(mod, CLIP_SDXL) + prompts = inputs[0]['txt'] + output = { k : v.clone() for k, v in output.items() } + assert 'crossattn' in output, f'output keys: {", ".join(output.keys())}' + output_vector = output['crossattn'] + def process(prompts): + new_inputs = dict() + for k, v in inputs[0].items(): + if isinstance(v, Tensor): + new_inputs[k] = torch.cat([v]*len(prompts), 0) + else: + new_inputs[k] = [v]*len(prompts) + new_inputs['txt'] = prompts + vs = mod(new_inputs) + return vs['crossattn'] + + assert len(prompts) == output_vector.shape[0], f"number of prompts different than expected: {len(prompts)} != {output_vector.shape[0]}" + + # Check wether we are processing Negative prompt or not. + # I firmly believe there is no one who uses a negative prompt + # exactly identical to a prompt. + if self.disable_neg: + if all(check_neg(x, p.negative_prompt, p.all_negative_prompts) for x in prompts): + # Now we are processing Negative prompt and skip it. + return + + for pidx, prompt in enumerate(prompts): + tt = token_to_block(mod, prompt) + + cutoff = generate_prompts(mod, prompt, self.targets, self.padding) + switch_base = np.full_like(cutoff.sw, self.strong) + switch = np.full_like(cutoff.sw, True) + active = cutoff.active_blocks() + + prompt_to_tokens = defaultdict(lambda: []) + for tidx, (token, block_index) in enumerate(tt): + if block_index in active: + sw = switch.copy() + sw[block_index] = False + prompt = cutoff.text(sw) + else: + prompt = cutoff.text(switch_base) + prompt_to_tokens[prompt].append((tidx, token)) + + #log(prompt_to_tokens) + + ks = list(prompt_to_tokens.keys()) + if len(ks) == 0: + # without any (negative) prompts + ks.append('') + + try: + # + skip = True + vs = process(ks) + finally: + skip = False + + tensor = output_vector[pidx, :, :] # e.g. (77, 768) + for k, t in zip(ks, vs): + assert tensor.shape == t.shape, f"tensor and t must have same shape\ntensor: {tensor.shape}\n t:{t.shape}" + for tidx, token in prompt_to_tokens[k]: + log(f'{tidx:03} {token.token:<16} {k}') + tensor[tidx, :] = self.interpolate(tensor[tidx,:], t[tidx,:], self.weight) + + return output + + self.hook_layer(clip, hook) + + +class Script(scripts.Script): + + def __init__(self): + super().__init__() + self.last_hooker: Union[SDHook,None] = None + + def title(self): + return NAME + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with gr.Accordion(NAME, open=False): + enabled = gr.Checkbox(label='Enabled', value=False) + targets = gr.Textbox(label='Target tokens (comma separated)', placeholder='red, blue') + weight = gr.Slider(minimum=-1.0, maximum=2.0, step=0.01, value=0.5, label='Weight') + with gr.Accordion('Details', open=False): + disable_neg = gr.Checkbox(value=True, label='Disable for Negative prompt.') + strong = gr.Checkbox(value=False, label='Cutoff strongly.') + padding = gr.Textbox(label='Padding token (ID or single token)') + lerp = gr.Radio(choices=['Lerp', 'SLerp'], value='Lerp', label='Interpolation method') + + debug = gr.Checkbox(value=False, label='Debug log') + debug.change(fn=set_debug, inputs=[debug], outputs=[]) + + return [ + enabled, + targets, + weight, + disable_neg, + strong, + padding, + lerp, + debug, + ] + + def process( + self, + p: StableDiffusionProcessing, + enabled: bool, + targets_: str, + weight: Union[float,int], + disable_neg: bool, + strong: bool, + padding: Union[str,int], + intp: str, + debug: bool, + ): + set_debug(debug) + + if self.last_hooker is not None: + self.last_hooker.__exit__(None, None, None) + self.last_hooker = None + + if not enabled: + return + + if targets_ is None or len(targets_) == 0: + return + + targets = [x.strip() for x in targets_.split(',')] + targets = [x for x in targets if len(x) != 0] + + if len(targets) == 0: + return + + if padding is None: + padding = PAD + elif isinstance(padding, str): + if len(padding) == 0: + padding = PAD + else: + try: + padding = int(padding) + except: + if not padding.endswith(''): + padding += '' + + weight = float(weight) + intp = intp.lower() + + self.last_hooker = Hook( + enabled=True, + targets=targets, + padding=padding, + weight=weight, + disable_neg=disable_neg, + strong=strong, + interpolate=intp, + ) + + self.last_hooker.setup(p) + self.last_hooker.__enter__() + + p.extra_generation_params.update({ + f'{NAME} enabled': enabled, + f'{NAME} targets': targets, + f'{NAME} padding': padding, + f'{NAME} weight': weight, + f'{NAME} disable_for_neg': disable_neg, + f'{NAME} strong': strong, + f'{NAME} interpolation': intp, + }) + + if hasattr(p, 'cached_c'): + p.cached_c = [None, None] + if hasattr(p, 'cached_uc'): + p.cached_uc = [None, None] + +init_xyz(Script, NAME) diff --git a/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/embedding.cpython-310.pyc b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/embedding.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dae2a2ee12abb93c5f955751321b9fe11d863a40 Binary files /dev/null and b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/embedding.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/sdhook.cpython-310.pyc b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/sdhook.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e609e0d940bfcee12e7c65131604caf71c1b143 Binary files /dev/null and b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/sdhook.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/utils.cpython-310.pyc b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f553be687f4de85a1af1feea309d0ab74d88b680 Binary files /dev/null and b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/utils.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/xyz.cpython-310.pyc b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/xyz.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bb83f539651b16e7087c998123acd9e98bb87d2 Binary files /dev/null and b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/__pycache__/xyz.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/embedding.py b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/embedding.py new file mode 100644 index 0000000000000000000000000000000000000000..39f6ce82d13e46415837f181cc99aa394e194554 --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/embedding.py @@ -0,0 +1,242 @@ +from dataclasses import dataclass +from itertools import product +import re +from typing import Union, List, Tuple +import numpy as np +import open_clip +from modules.sd_hijack_clip import FrozenCLIPEmbedderWithCustomWordsBase as CLIP +try: + from sgm.modules import GeneralConditioner as CLIP_SDXL +except: + print("[Cutoff] failed to load `sgm.modules.GeneralConditioner`") + CLIP_SDXL = int +from modules import prompt_parser, shared +from scripts.cutofflib.utils import log + +class ClipWrapper: + def __init__(self, te: Union[CLIP,CLIP_SDXL]): + self.te = te + self.v1 = hasattr(te.wrapped, 'tokenizer') + self.sdxl = hasattr(te, 'embedders') + self.t = ( + te.wrapped.tokenizer if self.v1 + else open_clip.tokenizer._tokenizer + ) + + def token_to_id(self, token: str) -> int: + if self.v1: + return self.t._convert_token_to_id(token) # type: ignore + else: + return self.t.encoder[token] + + def id_to_token(self, id: int) -> str: + if self.v1: + return self.t.convert_ids_to_tokens(id) # type: ignore + else: + return self.t.decoder[id] + + def ids_to_tokens(self, ids: List[int]) -> List[str]: + if self.v1: + return self.t.convert_ids_to_tokens(ids) # type: ignore + else: + return [self.t.decoder[id] for id in ids] + + def token(self, token: Union[int,str]): + if isinstance(token, int): + return Token(token, self.id_to_token(token)) + else: + return Token(self.token_to_id(token), token) + + @property + def id_start(self): + if self.sdxl: + return self.te.embedders[0].id_start + else: + return self.te.id_start + + @property + def id_end(self): + if self.sdxl: + return self.te.embedders[0].id_end + else: + return self.te.id_end + + @property + def hijack(self): + if self.sdxl: + return self.te.embedders[0].hijack + else: + return self.te.hijack + + +@dataclass +class Token: + id: int + token: str + +class CutoffPrompt: + + @staticmethod + def _cutoff(prompt: str, clip: CLIP, tokens: List[str], padding: str): + def token_count(text: str): + te = ClipWrapper(clip) + tt = token_to_block(clip, text) + # tt[0] == te.id_start (<|startoftext|>) + for index, (t, _) in enumerate(tt): + if t.id == te.id_end: # <|endoftext|> + return index - 1 + return 0 # must not happen... + + re_targets = [ re.compile(r'\b' + re.escape(x) + r'\b') for x in tokens ] + replacer = [ ' ' + ' '.join([padding] * token_count(x)) + ' ' for x in tokens ] + + rows: List[Tuple[str,str]] = [] + for block in prompt.split(','): + b0 = block + for r, p in zip(re_targets, replacer): + block = r.sub(p, block) + b1 = block + rows.append((b0, b1)) + + return rows + + def __init__(self, prompt: str, clip: CLIP, tokens: List[str], padding: str): + self.prompt = prompt + rows = CutoffPrompt._cutoff(prompt, clip, tokens, padding) + self.base = np.array([x[0] for x in rows]) + self.cut = np.array([x[1] for x in rows]) + self.sw = np.array([False] * len(rows)) + + @property + def block_count(self): + return self.base.shape[0] + + def switch(self, block_index: int, to: Union[bool,None] = None): + if to is None: + to = not self.sw[block_index] + self.sw[block_index] = to + return to + + def text(self, sw=None): + if sw is None: + sw = self.sw + blocks = np.where(sw, self.cut, self.base) + return ','.join(blocks) + + def active_blocks(self) -> np.ndarray: + indices, = (self.base != self.cut).nonzero() + return indices + + def generate(self): + indices = self.active_blocks() + for diff_sw in product([False, True], repeat=indices.shape[0]): + sw = np.full_like(self.sw, False) + sw[indices] = diff_sw + yield diff_sw, self.text(sw) + + +def generate_prompts( + clip: CLIP, + prompt: str, + targets: List[str], + padding: Union[str,int,Token], +) -> CutoffPrompt: + + te = ClipWrapper(clip) + + if not isinstance(padding, Token): + o_pad = padding + padding = te.token(padding) + if padding.id == te.id_end: + raise ValueError(f'`{o_pad}` is not a valid token.') + + result = CutoffPrompt(prompt, clip, targets, padding.token.replace('', '')) + + log(f'[Cutoff] replace: {", ".join(targets)}') + log(f'[Cutoff] to: {padding.token} ({padding.id})') + log(f'[Cutoff] original: {prompt}') + for i, (_, pp) in enumerate(result.generate()): + log(f'[Cutoff] #{i}: {pp}') + + return result + + +def token_to_block(clip: CLIP, prompt: str): + te = ClipWrapper(clip) + + # cf. sd_hijack_clip.py + + parsed = prompt_parser.parse_prompt_attention(prompt) + tokenized: List[List[int]] = clip.tokenize([text for text, _ in parsed]) + + CHUNK_LENGTH = 75 + id_start = te.token(te.id_start) # type: ignore + id_end = te.token(te.id_end) # type: ignore + comma = te.token(',') + + last_comma = -1 + current_block = 0 + current_tokens: List[Tuple[Token,int]] = [] + result: List[Tuple[Token,int]] = [] + + def next_chunk(): + nonlocal current_tokens, last_comma + + to_add = CHUNK_LENGTH - len(current_tokens) + if 0 < to_add: + current_tokens += [(id_end, -1)] * to_add + + current_tokens = [(id_start, -1)] + current_tokens + [(id_end, -1)] + + last_comma = -1 + result.extend(current_tokens) + current_tokens = [] + + for tokens, (text, weight) in zip(tokenized, parsed): + if text == 'BREAK' and weight == -1: + next_chunk() + continue + + p = 0 + while p < len(tokens): + token = tokens[p] + + if token == comma.id: + last_comma = len(current_tokens) + current_block += 1 + + elif ( + shared.opts.comma_padding_backtrack != 0 + and len(current_tokens) == CHUNK_LENGTH + and last_comma != -1 + and len(current_tokens) - last_comma <= shared.opts.comma_padding_backtrack + ): + break_location = last_comma + 1 + reloc_tokens = current_tokens[break_location:] + current_tokens = current_tokens[:break_location] + next_chunk() + current_tokens = reloc_tokens + + if len(current_tokens) == CHUNK_LENGTH: + next_chunk() + + embedding, embedding_length_in_tokens = te.hijack.embedding_db.find_embedding_at_position(tokens, p) + if embedding is None: + if token == comma.id: + current_tokens.append((te.token(token), -1)) + else: + current_tokens.append((te.token(token), current_block)) + p += 1 + continue + + emb_len = int(embedding.vec.shape[0]) + if len(current_tokens) + emb_len > CHUNK_LENGTH: + next_chunk() + + current_tokens += [(te.token(0), current_block)] * emb_len + p += embedding_length_in_tokens + + if len(current_tokens) > 0: + next_chunk() + + return result diff --git a/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/sdhook.py b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/sdhook.py new file mode 100644 index 0000000000000000000000000000000000000000..5be791c596e907ae835e41e337c919d96502e613 --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/sdhook.py @@ -0,0 +1,275 @@ +import sys +from typing import Any, Callable, Union + +from torch import nn +from torch.utils.hooks import RemovableHandle + +from ldm.modules.diffusionmodules.openaimodel import ( + TimestepEmbedSequential, +) +from ldm.modules.attention import ( + SpatialTransformer, + BasicTransformerBlock, + CrossAttention, + MemoryEfficientCrossAttention, +) +from ldm.modules.diffusionmodules.openaimodel import ( + ResBlock, +) +from modules.processing import StableDiffusionProcessing +from modules import shared + +class ForwardHook: + + def __init__(self, module: nn.Module, fn: Callable[[nn.Module, Callable[..., Any], Any], Any]): + self.o = module.forward + self.fn = fn + self.module = module + self.module.forward = self.forward + + def remove(self): + if self.module is not None and self.o is not None: + self.module.forward = self.o + self.module = None + self.o = None + self.fn = None + + def forward(self, *args, **kwargs): + if self.module is not None and self.o is not None: + if self.fn is not None: + return self.fn(self.module, self.o, *args, **kwargs) + return None + + +class SDHook: + + def __init__(self, enabled: bool): + self._enabled = enabled + self._handles: list[Union[RemovableHandle,ForwardHook]] = [] + + @property + def enabled(self): + return self._enabled + + @property + def batch_num(self): + return shared.state.job_no + + @property + def step_num(self): + return shared.state.current_image_sampling_step + + def __enter__(self): + if self.enabled: + pass + + def __exit__(self, exc_type, exc_value, traceback): + if self.enabled: + for handle in self._handles: + handle.remove() + self._handles.clear() + self.dispose() + + def dispose(self): + pass + + def setup( + self, + p: StableDiffusionProcessing + ): + if not self.enabled: + return + + wrapper = getattr(p.sd_model, "model", None) + + unet: Union[nn.Module,None] = getattr(wrapper, "diffusion_model", None) if wrapper is not None else None + vae: Union[nn.Module,None] = getattr(p.sd_model, "first_stage_model", None) + clip: Union[nn.Module,None] = getattr(p.sd_model, "cond_stage_model", None) + + assert unet is not None, "p.sd_model.diffusion_model is not found. broken model???" + self._do_hook(p, p.sd_model, unet=unet, vae=vae, clip=clip) # type: ignore + self.on_setup() + + def on_setup(self): + pass + + def _do_hook( + self, + p: StableDiffusionProcessing, + model: Any, + unet: Union[nn.Module,None], + vae: Union[nn.Module,None], + clip: Union[nn.Module,None] + ): + assert model is not None, "empty model???" + + if clip is not None: + self.hook_clip(p, clip) + + if unet is not None: + self.hook_unet(p, unet) + + if vae is not None: + self.hook_vae(p, vae) + + def hook_vae( + self, + p: StableDiffusionProcessing, + vae: nn.Module + ): + pass + + def hook_unet( + self, + p: StableDiffusionProcessing, + unet: nn.Module + ): + pass + + def hook_clip( + self, + p: StableDiffusionProcessing, + clip: nn.Module + ): + pass + + def hook_layer( + self, + module: Union[nn.Module,Any], + fn: Callable[[nn.Module, tuple, Any], Any] + ): + if not self.enabled: + return + + assert module is not None + assert isinstance(module, nn.Module) + self._handles.append(module.register_forward_hook(fn)) + + def hook_layer_pre( + self, + module: Union[nn.Module,Any], + fn: Callable[[nn.Module, tuple], Any] + ): + if not self.enabled: + return + + assert module is not None + assert isinstance(module, nn.Module) + self._handles.append(module.register_forward_pre_hook(fn)) + + def hook_forward( + self, + module: Union[nn.Module,Any], + fn: Callable[[nn.Module, Callable[..., Any], Any], Any] + ): + assert module is not None + assert isinstance(module, nn.Module) + self._handles.append(ForwardHook(module, fn)) + + def log(self, msg: str): + print(msg, file=sys.stderr) + + +# enumerate SpatialTransformer in TimestepEmbedSequential +def each_transformer(unet_block: TimestepEmbedSequential): + for block in unet_block.children(): + if isinstance(block, SpatialTransformer): + yield block + +# enumerate BasicTransformerBlock in SpatialTransformer +def each_basic_block(trans: SpatialTransformer): + for block in trans.transformer_blocks.children(): + if isinstance(block, BasicTransformerBlock): + yield block + +# enumerate Attention Layers in TimestepEmbedSequential +# each_transformer + each_basic_block +def each_attns(unet_block: TimestepEmbedSequential): + for n, trans in enumerate(each_transformer(unet_block)): + for depth, basic_block in enumerate(each_basic_block(trans)): + # attn1: Union[CrossAttention,MemoryEfficientCrossAttention] + # attn2: Union[CrossAttention,MemoryEfficientCrossAttention] + + attn1, attn2 = basic_block.attn1, basic_block.attn2 + assert isinstance(attn1, CrossAttention) or isinstance(attn1, MemoryEfficientCrossAttention) + assert isinstance(attn2, CrossAttention) or isinstance(attn2, MemoryEfficientCrossAttention) + + yield n, depth, attn1, attn2 + +def each_unet_attn_layers(unet: nn.Module): + def get_attns(layer_index: int, block: TimestepEmbedSequential, format: str): + for n, d, attn1, attn2 in each_attns(block): + kwargs = { + 'layer_index': layer_index, + 'trans_index': n, + 'block_index': d + } + yield format.format(attn_name='sattn', **kwargs), attn1 + yield format.format(attn_name='xattn', **kwargs), attn2 + + def enumerate_all(blocks: nn.ModuleList, format: str): + for idx, block in enumerate(blocks.children()): + if isinstance(block, TimestepEmbedSequential): + yield from get_attns(idx, block, format) + + inputs: nn.ModuleList = unet.input_blocks # type: ignore + middle: TimestepEmbedSequential = unet.middle_block # type: ignore + outputs: nn.ModuleList = unet.output_blocks # type: ignore + + yield from enumerate_all(inputs, 'IN{layer_index:02}_{trans_index:02}_{block_index:02}_{attn_name}') + yield from get_attns(0, middle, 'M{layer_index:02}_{trans_index:02}_{block_index:02}_{attn_name}') + yield from enumerate_all(outputs, 'OUT{layer_index:02}_{trans_index:02}_{block_index:02}_{attn_name}') + + +def each_unet_transformers(unet: nn.Module): + def get_trans(layer_index: int, block: TimestepEmbedSequential, format: str): + for n, trans in enumerate(each_transformer(block)): + kwargs = { + 'layer_index': layer_index, + 'block_index': n, + 'block_name': 'trans', + } + yield format.format(**kwargs), trans + + def enumerate_all(blocks: nn.ModuleList, format: str): + for idx, block in enumerate(blocks.children()): + if isinstance(block, TimestepEmbedSequential): + yield from get_trans(idx, block, format) + + inputs: nn.ModuleList = unet.input_blocks # type: ignore + middle: TimestepEmbedSequential = unet.middle_block # type: ignore + outputs: nn.ModuleList = unet.output_blocks # type: ignore + + yield from enumerate_all(inputs, 'IN{layer_index:02}_{block_index:02}_{block_name}') + yield from get_trans(0, middle, 'M{layer_index:02}_{block_index:02}_{block_name}') + yield from enumerate_all(outputs, 'OUT{layer_index:02}_{block_index:02}_{block_name}') + + +def each_resblock(unet_block: TimestepEmbedSequential): + for block in unet_block.children(): + if isinstance(block, ResBlock): + yield block + +def each_unet_resblock(unet: nn.Module): + def get_resblock(layer_index: int, block: TimestepEmbedSequential, format: str): + for n, res in enumerate(each_resblock(block)): + kwargs = { + 'layer_index': layer_index, + 'block_index': n, + 'block_name': 'resblock', + } + yield format.format(**kwargs), res + + def enumerate_all(blocks: nn.ModuleList, format: str): + for idx, block in enumerate(blocks.children()): + if isinstance(block, TimestepEmbedSequential): + yield from get_resblock(idx, block, format) + + inputs: nn.ModuleList = unet.input_blocks # type: ignore + middle: TimestepEmbedSequential = unet.middle_block # type: ignore + outputs: nn.ModuleList = unet.output_blocks # type: ignore + + yield from enumerate_all(inputs, 'IN{layer_index:02}_{block_index:02}_{block_name}') + yield from get_resblock(0, middle, 'M{layer_index:02}_{block_index:02}_{block_name}') + yield from enumerate_all(outputs, 'OUT{layer_index:02}_{block_index:02}_{block_name}') + diff --git a/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/utils.py b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..8a2962521792409bb4f154d343db132928a9343d --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/utils.py @@ -0,0 +1,11 @@ +import sys + +_debug = False + +def set_debug(is_debug: bool): + global _debug + _debug = is_debug + +def log(s: str): + if _debug: + print(s, file=sys.stderr) diff --git a/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/xyz.py b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/xyz.py new file mode 100644 index 0000000000000000000000000000000000000000..208aac8588f89adb2b7b722d2e73170da324d118 --- /dev/null +++ b/extensions/CHECK/sd-webui-cutoff/scripts/cutofflib/xyz.py @@ -0,0 +1,126 @@ +import os +from typing import Union, List, Callable + +from modules import scripts +from modules.processing import StableDiffusionProcessing, StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img + + +def __set_value(p: StableDiffusionProcessing, script: type, index: int, value): + args = list(p.script_args) + + if isinstance(p, StableDiffusionProcessingTxt2Img): + all_scripts = scripts.scripts_txt2img.scripts + else: + all_scripts = scripts.scripts_img2img.scripts + + froms = [x.args_from for x in all_scripts if isinstance(x, script)] + for idx in froms: + assert idx is not None + args[idx + index] = value + + p.script_args = type(p.script_args)(args) + + +def to_bool(v: str): + if len(v) == 0: return False + v = v.lower() + if 'true' in v: return True + if 'false' in v: return False + + try: + w = int(v) + return bool(w) + except: + acceptable = ['True', 'False', '1', '0'] + s = ', '.join([f'`{v}`' for v in acceptable]) + raise ValueError(f'value must be one of {s}.') + + +class AxisOptions: + + def __init__(self, AxisOption: type, axis_options: list): + self.AxisOption = AxisOption + self.target = axis_options + self.options = [] + + def __enter__(self): + self.options.clear() + return self + + def __exit__(self, ex_type, ex_value, trace): + if ex_type is not None: + return + + for opt in self.options: + self.target.append(opt) + + self.options.clear() + + def create(self, name: str, type_fn: Callable, action: Callable, choices: Union[List[str],None]): + if choices is None or len(choices) == 0: + opt = self.AxisOption(name, type_fn, action) + else: + opt = self.AxisOption(name, type_fn, action, choices=lambda: choices) + return opt + + def add(self, axis_option): + self.target.append(axis_option) + + +__init = False + +def init_xyz(script: type, ext_name: str): + global __init + + if __init: + return + + for data in scripts.scripts_data: + name = os.path.basename(data.path) + if name != 'xy_grid.py' and name != 'xyz_grid.py': + continue + + if not hasattr(data.module, 'AxisOption'): + continue + + if not hasattr(data.module, 'axis_options'): + continue + + AxisOption = data.module.AxisOption + axis_options = data.module.axis_options + + if not isinstance(AxisOption, type): + continue + + if not isinstance(axis_options, list): + continue + + try: + create_options(ext_name, script, AxisOption, axis_options) + except: + pass + + __init = True + + +def create_options(ext_name: str, script: type, AxisOptionClass: type, axis_options: list): + with AxisOptions(AxisOptionClass, axis_options) as opts: + def define(param: str, index: int, type_fn: Callable, choices: List[str] = []): + def fn(p, x, xs): + __set_value(p, script, index, x) + + name = f'[{ext_name}] {param}' + return opts.create(name, type_fn, fn, choices) + + options = [ + define('Enabled', 0, to_bool, choices=['false', 'true']), + define('Targets', 1, str), + define('Weight', 2, float), + define('Disable for Negative Prompt', 3, to_bool, choices=['false', 'true']), + define('Strong', 4, to_bool, choices=['false', 'true']), + define('Padding', 5, str), + define('Interpolation', 6, str, choices=['Lerp', 'SLerp']), + ] + + for opt in options: + opts.add(opt) diff --git a/extensions/CHECK/sd-webui-detail-daemon/.gitattributes b/extensions/CHECK/sd-webui-detail-daemon/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..dfe0770424b2a19faf507a501ebfc23be8f54e7b --- /dev/null +++ b/extensions/CHECK/sd-webui-detail-daemon/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/extensions/CHECK/sd-webui-detail-daemon/.gitignore b/extensions/CHECK/sd-webui-detail-daemon/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c18dd8d83ceed1806b50b0aaa46beb7e335fff13 --- /dev/null +++ b/extensions/CHECK/sd-webui-detail-daemon/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/extensions/CHECK/sd-webui-detail-daemon/LICENSE b/extensions/CHECK/sd-webui-detail-daemon/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..17549f2a830a05a46deb26141f7be6d54860ddbb --- /dev/null +++ b/extensions/CHECK/sd-webui-detail-daemon/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Sahand Ahmadian + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/CHECK/sd-webui-detail-daemon/README.md b/extensions/CHECK/sd-webui-detail-daemon/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ebd9ab20d4964fa6f5ed0f48d7f8ff872de86b1a --- /dev/null +++ b/extensions/CHECK/sd-webui-detail-daemon/README.md @@ -0,0 +1,82 @@ +# Detail Daemon +This is an extension for [Stable Diffusion Web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui), which allows users to adjust the amount of detail/smoothness in an image, during the sampling steps. + +It uses no LORAs, ControlNets, etc., and as a result its performance is not biased towards any certain style and it introduces no new stylistic or semantic features of its own into the generation. This also means that it can work with any model and on any type of image. + +*Model: SSD-1B*
+![a close up portrait of a cyberpunk knight-1Lv-0](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/561c33d9-9a5d-4cfc-bee8-de9126b280c1) +*Left: Less detail, Middle: Original, Right: More detail*
+ +*Model: SD 1.5 (finetuned)*
+![face of a cute cat love heart symbol-Zn6-0](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/9fbfb39f-81fb-4951-8f32-20eab410020a) +*Left: Less detail, Middle: Original, Right: More detail*
+ + +## How It Works +Detail Daemon works by manipulating the original noise levels at every sampling step, according to a customizable schedule. + +### In Theory +The noise levels (sigmas, i.e. the standard deviation of the noise) tell the model how much noise it should expect, and try to remove, at each denoising step. A higher sigma value at a certain denoising step tells the model to denoise more aggressively at that step and vice versa. + +With a common sigmas schedule, the sigmas start at very high values at the beginning of the denoising process, then quickly fall to low values in the middle, and to very low values towards the end of the process. This curve (along with the timesteps schedule, but that's a story for another day) is what makes it so that larger features (low frequencies) of the image are defined at the earlier steps, and towards the end of the process you can only see minor changes in the smaller features (high frequencies). We'll get back to this later. + +Now, if we pass the model a sigmas schedule with values lower than the original, at each step the model will denoise less, resulting a noisier output latent at that step. But then in the steps after that, the model does its best to make sense of this extra noise and turn it into image features. So in theory, *when done in modesty*, this would result in a more detailed image. If you push it too hard, the model won't be able to handle the extra noise added at each step and the end result will devolve into pure noise. So modesty is key. + +### But in Practice +Modesty only gets you so far! Also, wtf are those? As the examples below show, you can't really add that much detail to the image before it either breaks down, and/or becomes a totally different thing. + +*SD 1.5*
+![Modesty](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/2f011a28-0948-48f8-b171-350add6fdd67) +Original sigmas (left) multiplied by .9, .85, .8
+ +*SDXL*
+![1](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/eff2356e-a6dd-4a4e-9c7e-861dec7713eb) +Original sigmas (left) multiplied by .95, .9, .85, .875, .8
+ +That's because: +1. We're constantly adding noise and not giving the model enough time to deal with it +2. We are manipulating the early steps where the low frequency features of the image (color, composition, etc.) are defined + +### Enter the Schedule +What we usually mean by "detail" falls within the mid to high frequency range, which correspond to the middle to late steps in the sampling process. So if we skip the early steps to leave the main features of the image intact, and the late steps to give the model some time to turn the extra noise into useful detail, we'll have something like this: + +![3](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/cd47e882-8b56-4321-8c47-c0d689562780) + +Then we could make our schedule a bit fancier and have it target specific steps corresponding to different sized details: + +![4](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/ea5027d2-3359-4733-afb4-5ae4a1218f38) + +Which steps correspond to which exact frequency range depends on the model you're using, the sampler, your prompt (specially if you're using Prompt Editing and stuff), and probably a bunch of other things. There are also fancier things you can (and should) do with the schedule, like pushing the sigmas too low for some heavy extra noise and then too high to clean up the excess and leave some nice details. So you need to do some tweaking to figure out the best schedule for each image you generate, or at least the ones that need their level of detail adjusted. But ideally you should be spending countless hours of your life sculpting the perfect detail adjustment schedule for every image, cuz that's why we're here. + +I'll soon provide specific examples addressing different scenarios and some of the techniques I've come up with. (note to self: move these to the wiki page) + +## Installation +Open SD WebUI > Go to Extensions tab > Go to Available tab > Click Load from: > Find Detail Daemon > Click Install + +Or Go to Install from URL tab > Paste this repo's URL into the first field > Click Install + +Or go to your WebUI folder and manually clone this repo into your extensions folder: + +`git clone "https://github.com/muerrilla/sd-webui-detail-daemon" extensions/sd-webui-detail-daemon` + +## Getting Started +After installation you can find the extension in your txt2img and img2img tabs. +![2024-07-08 01_43_21-011366](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/045574cb-465c-4991-83c4-d02f803a330b) +### Sliders: +The sliders (and that one checkbox) set the amount of adjustment (positive values → add detail, negative values → remove detail) and the sampling steps during which it is applied (i.e. the schedule). So the X axis of the graph is your sampling steps, normalized to the (0,1) range, and the Y axis is the amount of adjustment. The rest is pretty self-explanatory I think. Just drag things and look at the graph for changes. +### Numbers: +The three number inputs at the buttom are provided because sometimes the slider max/mins are too limiting. +### Modes: +The `cond` and `uncond` modes affect only their respective latents, while `both` affects both (duh!). The `cond` and `uncond` modes are less intense and also allow changes to be applied at earlier steps without diverging too far from the original generation, since the other latent stays intact. + +There's also a minor twist: in the `both` mode if `detail amount` is positive both cond and uncond latents become more detailed. So the more detailed cond latent will try to push the generation towards more detail, while the more detailed uncond latent will try to push towards less detail. This causes more new features/artifacts to pop into the image in this mode. + +### Tips: +I'll write up some proper docs on how best to set the parameters, as soon as possible. For now you gotta play around with the sliders and figure out how the shape of the schedule affects the image. I suggest you set your live preview update period to every frame, or every other frame, so you could see clearly what's going on at every step of the sampling process and how Detail Daemon affects it, till you get a good grasp of how this thing works. + +## Notes: +- Doesn't support Compositional Diffusion (i.e. the AND syntax) properly. Specially if you have a batch size > 1 or negative weights in your prompts, and the mode is set to `cond` or `uncond`. +- It's probably impossible to use or very hard to control with few-step models (Turbo, Lightning, etc.). Edit: It's managable. +- It works with Forge (`cond` and `uncond` modes are not supported). +- It's not the same as AlignYourSteps, FreeU, etc. +- It is similar (in what it sets out to do, not in how it does it) to the [ReSharpen Extension](https://github.com/Haoming02/sd-webui-resharpen) by Haoming. diff --git a/extensions/CHECK/sd-webui-detail-daemon/scripts/__pycache__/detail_daemon.cpython-310.pyc b/extensions/CHECK/sd-webui-detail-daemon/scripts/__pycache__/detail_daemon.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7158728cfb19a5659b3edaa1961896ff2caf983b Binary files /dev/null and b/extensions/CHECK/sd-webui-detail-daemon/scripts/__pycache__/detail_daemon.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-detail-daemon/scripts/detail_daemon.py b/extensions/CHECK/sd-webui-detail-daemon/scripts/detail_daemon.py new file mode 100644 index 0000000000000000000000000000000000000000..d63d9900ab1d8a9223fd397cf64d062889907f2d --- /dev/null +++ b/extensions/CHECK/sd-webui-detail-daemon/scripts/detail_daemon.py @@ -0,0 +1,313 @@ +import os +import gradio as gr +import numpy as np +from tqdm import tqdm + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt + +import modules.scripts as scripts +from modules.script_callbacks import on_cfg_denoiser, remove_callbacks_for_function, on_infotext_pasted +from modules.ui_components import InputAccordion + + +def parse_infotext(infotext, params): + try: + d = {} + for s in params['Detail Daemon'].split(','): + k, _, v = s.partition(':') + d[k.strip()] = v.strip() + params['Detail Daemon'] = d + except Exception: + pass + + +on_infotext_pasted(parse_infotext) + + +class Script(scripts.Script): + + def title(self): + return "Detail Daemon" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with InputAccordion(False, label="Detail Daemon", elem_id=self.elem_id('detail-daemon')) as gr_enabled: + with gr.Row(): + with gr.Column(scale=2): + gr_amount_slider = gr.Slider(minimum=-1.00, maximum=1.00, step=.01, value=0.10, label="Detail Amount") + gr_start = gr.Slider(minimum=0.0, maximum=1.0, step=.01, value=0.2, label="Start") + gr_end = gr.Slider(minimum=0.0, maximum=1.0, step=.01, value=0.8, label="End") + gr_bias = gr.Slider(minimum=0.0, maximum=1.0, step=.01, value=0.5, label="Bias") + with gr.Column(scale=1, min_width=275): + preview = self.visualize(False, 0.2, 0.8, 0.5, 0.1, 1, 0, 0, 0, True) + gr_vis = gr.Plot(value=preview, elem_classes=['detail-daemon-vis'], show_label=False) + with gr.Accordion("More Knobs:", elem_classes=['detail-daemon-more-accordion'], open=False): + with gr.Row(): + with gr.Column(scale=2): + with gr.Row(): + gr_start_offset_slider = gr.Slider(minimum=-1.00, maximum=1.00, step=.01, value=0.00, label="Start Offset", min_width=60) + gr_end_offset_slider = gr.Slider(minimum=-1.00, maximum=1.00, step=.01, value=0.00, label="End Offset", min_width=60) + with gr.Row(): + gr_exponent = gr.Slider(minimum=0.0, maximum=10.0, step=.05, value=1.0, label="Exponent", min_width=60) + gr_fade = gr.Slider(minimum=0.0, maximum=1.0, step=.05, value=0.0, label="Fade", min_width=60) + # Because the slider max and min are sometimes too limiting: + with gr.Row(): + gr_amount = gr.Number(value=0.10, precision=4, step=.01, label="Amount", min_width=60) + gr_start_offset = gr.Number(value=0.0, precision=4, step=.01, label="Start Offset", min_width=60) + gr_end_offset = gr.Number(value=0.0, precision=4, step=.01, label="End Offset", min_width=60) + with gr.Column(scale=1, min_width=275): + gr_mode = gr.Dropdown(["both", "cond", "uncond"], value="uncond", label="Mode", show_label=True, min_width=60, elem_classes=['detail-daemon-mode']) + gr_smooth = gr.Checkbox(label="Smooth", value=True, min_width=60, elem_classes=['detail-daemon-smooth']) + gr.Markdown("## [Ⓗ Help](https://github.com/muerrilla/sd-webui-detail-daemon)", elem_classes=['detail-daemon-help']) + + gr_amount_slider.release(None, gr_amount_slider, gr_amount, _js="(x) => x") + gr_amount.change(None, gr_amount, gr_amount_slider, _js="(x) => x") + + gr_start_offset_slider.release(None, gr_start_offset_slider, gr_start_offset, _js="(x) => x") + gr_start_offset.change(None, gr_start_offset, gr_start_offset_slider, _js="(x) => x") + + gr_end_offset_slider.release(None, gr_end_offset_slider, gr_end_offset, _js="(x) => x") + gr_end_offset.change(None, gr_end_offset, gr_end_offset_slider, _js="(x) => x") + + vis_args = [gr_enabled, gr_start, gr_end, gr_bias, gr_amount, gr_exponent, gr_start_offset, gr_end_offset, gr_fade, gr_smooth] + for vis_arg in vis_args: + if isinstance(vis_arg, gr.components.Slider): + vis_arg.release(fn=self.visualize, show_progress=False, inputs=vis_args, outputs=[gr_vis]) + else: + vis_arg.change(fn=self.visualize, show_progress=False, inputs=vis_args, outputs=[gr_vis]) + + def extract_infotext(d: dict, key, old_key): + if 'Detail Daemon' in d: + return d['Detail Daemon'].get(key) + return d.get(old_key) + + self.infotext_fields = [ + (gr_enabled, lambda d: True if ('Detail Daemon' in d or 'DD_enabled' in d) else False), + (gr_mode, lambda d: extract_infotext(d, 'mode', 'DD_mode')), + (gr_amount, lambda d: extract_infotext(d, 'amount', 'DD_amount')), + (gr_start, lambda d: extract_infotext(d, 'st', 'DD_start')), + (gr_end, lambda d: extract_infotext(d, 'ed', 'DD_end')), + (gr_bias, lambda d: extract_infotext(d, 'bias', 'DD_bias')), + (gr_exponent, lambda d: extract_infotext(d, 'exp', 'DD_exponent')), + (gr_start_offset, lambda d: extract_infotext(d, 'st_offset', 'DD_start_offset')), + (gr_end_offset, lambda d: extract_infotext(d, 'ed_offset', 'DD_end_offset')), + (gr_fade, lambda d: extract_infotext(d, 'fade', 'DD_fade')), + (gr_smooth, lambda d: extract_infotext(d, 'smooth', 'DD_smooth')), + ] + return [gr_enabled, gr_mode, gr_start, gr_end, gr_bias, gr_amount, gr_exponent, gr_start_offset, gr_end_offset, gr_fade, gr_smooth] + + def process(self, p, enabled, mode, start, end, bias, amount, exponent, start_offset, end_offset, fade, smooth): + + enabled = getattr(p, "DD_enabled", enabled) + mode = getattr(p, "DD_mode", mode) + amount = getattr(p, "DD_amount", amount) + start = getattr(p, "DD_start", start) + end = getattr(p, "DD_end", end) + bias = getattr(p, "DD_bias", bias) + exponent = getattr(p, "DD_exponent", exponent) + start_offset = getattr(p, "DD_start_offset", start_offset) + end_offset = getattr(p, "DD_end_offset", end_offset) + fade = getattr(p, "DD_fade", fade) + smooth = getattr(p, "DD_smooth", smooth) + + if enabled: + if p.sampler_name == "DPM adaptive": + tqdm.write(f'\033[33mWARNING:\033[0m Detail Daemon does not work with {p.sampler_name}') + return + # Restart can be handled better, later maybe + + actual_steps = (p.steps * 2 - 1) if p.sampler_name in ['DPM++ SDE', 'DPM++ 2S a', 'Heun', 'DPM2', 'DPM2 a', 'Restart'] else p.steps + self.schedule = self.make_schedule(actual_steps, start, end, bias, amount, exponent, start_offset, end_offset, fade, smooth) + self.mode = mode + self.cfg_scale = p.cfg_scale + self.batch_size = p.batch_size + on_cfg_denoiser(self.denoiser_callback) + self.callback_added = True + p.extra_generation_params['Detail Daemon'] = f'mode:{mode},amount:{amount},st:{start},ed:{end},bias:{bias},exp:{exponent},st_offset:{start_offset},ed_offset:{end_offset},fade:{fade},smooth:{1 if smooth else 0}' + tqdm.write('\033[32mINFO:\033[0m Detail Daemon is enabled') + else: + if hasattr(self, 'callback_added'): + remove_callbacks_for_function(self.denoiser_callback) + delattr(self, 'callback_added') + # tqdm.write('\033[90mINFO: Detail Daemon callback removed\033[0m') + + def before_process_batch(self, p, *args, **kwargs): + self.is_hires = False + + def postprocess(self, p, processed, *args): + if hasattr(self, 'callback_added'): + remove_callbacks_for_function(self.denoiser_callback) + delattr(self, 'callback_added') + # tqdm.write('\033[90mINFO: Detail Daemon callback removed\033[0m') + + def before_hr(self, p, *args): + self.is_hires = True + enabled = args[0] + if enabled: + tqdm.write(f'\033[33mINFO:\033[0m Detail Daemon does not work during Hires Fix') + + def denoiser_callback(self, params): + if self.is_hires: + return + idx = params.denoiser.step + multiplier = self.schedule[idx] * .1 + mode = self.mode + if params.sigma.size(0) == 1: + mode = "both" + if idx == 0: + tqdm.write(f'\033[33mWARNING:\033[0m Forge does not support `cond` and `uncond` modes, using `both` instead') + if mode == "cond": + for i in range(self.batch_size): + params.sigma[i] *= 1 - multiplier + elif mode == "uncond": + for i in range(self.batch_size): + params.sigma[self.batch_size + i] *= 1 + multiplier + else: + params.sigma *= 1 - multiplier * self.cfg_scale + + def make_schedule(self, steps, start, end, bias, amount, exponent, start_offset, end_offset, fade, smooth): + start = min(start, end) + mid = start + bias * (end - start) + multipliers = np.zeros(steps) + + start_idx, mid_idx, end_idx = [int(round(x * (steps - 1))) for x in [start, mid, end]] + + start_values = np.linspace(0, 1, mid_idx - start_idx + 1) + if smooth: + start_values = 0.5 * (1 - np.cos(start_values * np.pi)) + start_values = start_values ** exponent + if start_values.any(): + start_values *= (amount - start_offset) + start_values += start_offset + + end_values = np.linspace(1, 0, end_idx - mid_idx + 1) + if smooth: + end_values = 0.5 * (1 - np.cos(end_values * np.pi)) + end_values = end_values ** exponent + if end_values.any(): + end_values *= (amount - end_offset) + end_values += end_offset + + multipliers[start_idx:mid_idx+1] = start_values + multipliers[mid_idx:end_idx+1] = end_values + multipliers[:start_idx] = start_offset + multipliers[end_idx+1:] = end_offset + multipliers *= 1 - fade + + return multipliers + + def visualize(self, enabled, start, end, bias, amount, exponent, start_offset, end_offset, fade, smooth): + try: + steps = 50 + values = self.make_schedule(steps, start, end, bias, amount, exponent, start_offset, end_offset, fade, smooth) + mean = sum(values)/steps + peak = np.clip(max(abs(values)), -1, 1) + if start > end: + start = end + mid = start + bias * (end - start) + opacity = .1 + (1 - fade) * 0.7 + plot_color = (0.5, 0.5, 0.5, opacity) if not enabled else ((1 - peak)**2, 1, 0, opacity) if mean >= 0 else (1, (1 - peak)**2, 0, opacity) + plt.rcParams.update({ + "text.color": plot_color, + "axes.labelcolor": plot_color, + "axes.edgecolor": plot_color, + "figure.facecolor": (0.0, 0.0, 0.0, 0.0), + "axes.facecolor": (0.0, 0.0, 0.0, 0.0), + "ytick.labelsize": 6, + "ytick.labelcolor": plot_color, + "ytick.color": plot_color, + }) + fig, ax = plt.subplots(figsize=(2.15, 2.00), layout="constrained") + ax.plot(range(steps), values, color=plot_color) + ax.axhline(y=0, color=plot_color, linestyle='dotted') + ax.axvline(x=mid * (steps - 1), color=plot_color, linestyle='dotted') + ax.tick_params(right=False, color=plot_color) + ax.set_xticks([i * (steps - 1) / 10 for i in range(10)][1:]) + ax.set_xticklabels([]) + ax.set_ylim([-1, 1]) + ax.set_xlim([0, steps-1]) + plt.close() + self.last_vis = fig + return fig + except Exception: + if self.last_vis is not None: + return self.last_vis + return + + +def xyz_support(): + for scriptDataTuple in scripts.scripts_data: + if os.path.basename(scriptDataTuple.path) == 'xyz_grid.py': + xy_grid = scriptDataTuple.module + + def confirm_mode(p, xs): + for x in xs: + if x not in ['both', 'cond', 'uncond']: + raise RuntimeError(f'Invalid Detail Daemon Mode: {x}') + mode = xy_grid.AxisOption( + '[Detail Daemon] Mode', + str, + xy_grid.apply_field('DD_mode'), + confirm=confirm_mode + ) + amount = xy_grid.AxisOption( + '[Detail Daemon] Amount', + float, + xy_grid.apply_field('DD_amount') + ) + start = xy_grid.AxisOption( + '[Detail Daemon] Start', + float, + xy_grid.apply_field('DD_start') + ) + end = xy_grid.AxisOption( + '[Detail Daemon] End', + float, + xy_grid.apply_field('DD_end') + ) + bias = xy_grid.AxisOption( + '[Detail Daemon] Bias', + float, + xy_grid.apply_field('DD_bias') + ) + exponent = xy_grid.AxisOption( + '[Detail Daemon] Exponent', + float, + xy_grid.apply_field('DD_exponent') + ) + start_offset = xy_grid.AxisOption( + '[Detail Daemon] Start Offset', + float, + xy_grid.apply_field('DD_start_offset') + ) + end_offset = xy_grid.AxisOption( + '[Detail Daemon] End Offset', + float, + xy_grid.apply_field('DD_end_offset') + ) + fade = xy_grid.AxisOption( + '[Detail Daemon] Fade', + float, + xy_grid.apply_field('DD_fade') + ) + xy_grid.axis_options.extend([ + mode, + amount, + start, + end, + bias, + exponent, + start_offset, + end_offset, + fade, + ]) + + +try: + xyz_support() +except Exception as e: + print(f'Error trying to add XYZ plot options for Detail Daemon', e) diff --git a/extensions/CHECK/sd-webui-detail-daemon/style.css b/extensions/CHECK/sd-webui-detail-daemon/style.css new file mode 100644 index 0000000000000000000000000000000000000000..b91d21367b3cea8a3f82a47dca2dc8ed8a94bf12 --- /dev/null +++ b/extensions/CHECK/sd-webui-detail-daemon/style.css @@ -0,0 +1,20 @@ +.detail-daemon-more-accordion { + margin-top: 1em !important; +} + +.detail-daemon-mode { + margin-left: 3em !important; + width: 75% !important; +} + +.detail-daemon-smooth { + margin-left: 3em !important; +} + +.detail-daemon-vis { + margin: auto !important; +} + +.detail-daemon-help { + margin: auto 1.5em !important; +} diff --git a/extensions/CHECK/sd-webui-diffusion-cg/.gitignore b/extensions/CHECK/sd-webui-diffusion-cg/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..bee8a64b79a99590d5303307144172cfe824fbf7 --- /dev/null +++ b/extensions/CHECK/sd-webui-diffusion-cg/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/CHANGELOG.md b/extensions/CHECK/sd-webui-diffusion-cg/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..b1d22ca3ef866d6dc4ba380d10e48ce509b237d7 --- /dev/null +++ b/extensions/CHECK/sd-webui-diffusion-cg/CHANGELOG.md @@ -0,0 +1,44 @@ +### v1.1.0 - 2024 May.06 +- Add **X/Y/Z Plot** Support + +### v1.0.0 - 2024 Mar.07 +- Extension "**Released**" + +### v0.5.0 - 2024 Jan.09 +- Add **Infotext** + +### v0.4.2 - 2023 Dec.19 +- Optimization via `.detach()` + +### v0.4.1 - 2023 Dec.11 +- Update **Normalization** Logic + +### v0.4.0 - 2023 Dec.08 +- Implement Color **Parameters** + +### v0.3.0 - 2023 Dec.01 +- Add **Default** Settings + +### v0.2.3 - 2023 Dec.01 +- **Clarify** README + +### v0.2.2 - 2023 Nov.25 +- No longer process if a **Mask** exists + +### v0.2.1 - 2023 Nov.23 +- Add **Effect Strength** Settings + +### v0.2.0 - 2023 Nov.21 +- Better **SDXL** Support + +### v0.1.3 - 2023 Nov.20 +- Improved **Compatibility** +- +### v0.1.2 - 2023 Nov.20 +- Improved(?) **Normalization** + +### v0.1.1 - 2023 Nov.20 +- Improved(?) **Normalization** + +### v0.1.0 - 2023 Nov.16 +- Public **Alpha** Released~ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/LICENSE b/extensions/CHECK/sd-webui-diffusion-cg/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..91b74bef797843ad1245014dc2865ff6cd4ac688 --- /dev/null +++ b/extensions/CHECK/sd-webui-diffusion-cg/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Haoming + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/CHECK/sd-webui-diffusion-cg/README.md b/extensions/CHECK/sd-webui-diffusion-cg/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5f48e45f5bdbf112b33bb4fa70053a0d2451db6c --- /dev/null +++ b/extensions/CHECK/sd-webui-diffusion-cg/README.md @@ -0,0 +1,93 @@ +# SD Webui Diffusion Color Grading +This is an Extension for the [Automatic1111 Webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui), which performs *Color Grading* during the generation, +producing a more **neutral** and **balanced**, but also **vibrant** and **contrasty** color. + +> This is the fruition of the joint research between [TimothyAlexisVass](https://github.com/TimothyAlexisVass) with their findings, and me with my experience in developing [Vectorscope CC](https://github.com/Haoming02/sd-webui-vectorscope-cc) + +**Note:** This Extension is disabled during [ADetailer](https://github.com/Bing-su/adetailer) phase to prevent inconsistent colors + +This Extension comes with two main features, **Recenter** and **Normalization**: + +### Recenter + +
Abstract
+ +TimothyAlexisVass discovered that, the value of the latent noise Tensor often starts off-centered, and the mean of each channel tends to drift away from `0`. +Therefore, I tried to write an Extension to guide the mean back to `0`. For **SDXL**, pushing the mean of each channel to `0` yields decent results. + +But for **SD 1.5**, I found out that for some Checkpoints this often produces a green tint, suggesting that the "center" of each channel might not necessarily be at `0`. +After experimenting with hundreds of images, I located a set of values for a rather **neutral and balanced** tone. + +
Effects
+ +When you enable the feature, the output images will not have a biased color tint, and all colors will distribute more evenly; +Additionally, the brightness will be adjusted so that bright areas are not overblown and dark areas are not clipped, +producing a similar effect like the HDR photos taken by smartphones. + +
Samples
+ +

+ + +
Off | On
+

+ +

+ + +
Off | On
+

+ +### Normalization + +
Abstract
+ +By encoding images into latent noise with VAE, TimothyAlexisVass discovered that the values for VAE to decode are usually within a certain range, +and thus theorized that if the final latent noise has a smaller value range, then some precision is essentailly wasted. +This gave me an idea to write a function that can make the latent noise utilize the full color depth, +making the final output more **vibrant** and **contrasty**. + +
Effects
+ +When you enable the feature, the latent noise will attempt to span across the value ranges if possible, before getting decoded by the VAE. +As a result, bright areas will get brighter and dark areas will get darker; Additional details may also be introduced in these areas. + +> This feature is currently disabled during Hires. fix pass + +### Combined +You can also enable both features at the same time, thus creating some really stunning results! + +

+ +

+ +### SDXL Support +Since the [*](#stable-diffusion-structures)internal structure (channel) and the color range of `SDXL` is different from those of `SD 1.5`, +this Extension cannot simply work for both of them using the same values. Nevertheless, you can now toggle the version to SDXL and try out the effects: + +

+ + +
+ + +
Off | On
+

+ +## Settings +In the `Diffusion CG` section of the **Settings** tab, you can make either feature default to Enabled, +as well as setting the Stable Diffusion Version to start with. + +## To Do +- [X] Parameter Settings +- [X] Better SDXL Support +- [X] Generation InfoText +- [ ] Better Algorithms + > Currently, for extreme cases *(**eg.** a bowl of oranges)*, the overall colors will be overcompensating + +
+ +## Stable Diffusion Structures +The `Tensor` of the latent noise has a dimention of `[batch, 4, height / 8, width / 8]`. +- For **SD 1.5:** From my trial and error when developing [Vectorscope CC](https://github.com/Haoming02/sd-webui-vectorscope-cc), each of the 4 channels essentially represents the `-K`, `-M`, `C`, `Y` color for the **CMYK** color model. +- For **SDXL:** According to TimothyAlexisVass's [Blogpost](https://huggingface.co/blog/TimothyAlexisVass/explaining-the-sdxl-latent-space), the first 3 channels are similar to **YCbCr** color model, while the 4th channel is the pattern/structure. diff --git a/extensions/CHECK/sd-webui-diffusion-cg/samples/c_a_off.jpg b/extensions/CHECK/sd-webui-diffusion-cg/samples/c_a_off.jpg new file mode 100644 index 0000000000000000000000000000000000000000..998af4cf2a5538797ea47d15dbf405ee219d6081 Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/samples/c_a_off.jpg differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/samples/c_a_on.jpg b/extensions/CHECK/sd-webui-diffusion-cg/samples/c_a_on.jpg new file mode 100644 index 0000000000000000000000000000000000000000..91481a9f54c322a6940c16184249d6f8bcb236ca Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/samples/c_a_on.jpg differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/samples/c_r_off.jpg b/extensions/CHECK/sd-webui-diffusion-cg/samples/c_r_off.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4a33daab58672b9484acd077de4334c55b2bc422 Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/samples/c_r_off.jpg differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/samples/c_r_on.jpg b/extensions/CHECK/sd-webui-diffusion-cg/samples/c_r_on.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d63e0273021875576d2903cea1e1a8f5f999d2fc Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/samples/c_r_on.jpg differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_off_1.jpg b/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_off_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9fa2d914afafb08d3558f5f39546e875a0a4fd20 Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_off_1.jpg differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_off_2.jpg b/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_off_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6451d17792804b1c7ac3b0e985050fc31e0b20b0 Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_off_2.jpg differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_on_1.jpg b/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_on_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0b2565f26d2c4848bef06c60240c2975e6591c63 Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_on_1.jpg differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_on_2.jpg b/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_on_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f97d38f323e4689d0437486f5109a271fac614b3 Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/samples/cn_xl_on_2.jpg differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/samples/cna.jpg b/extensions/CHECK/sd-webui-diffusion-cg/samples/cna.jpg new file mode 100644 index 0000000000000000000000000000000000000000..431bb7cb622171ccc0faf3970cc2aaedf2a1dc16 Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/samples/cna.jpg differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/scripts/__pycache__/cg_settings.cpython-310.pyc b/extensions/CHECK/sd-webui-diffusion-cg/scripts/__pycache__/cg_settings.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0186941d76c4fc5514e86fa2d1c0a4104bdbf55a Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/scripts/__pycache__/cg_settings.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/scripts/__pycache__/cg_xyz.cpython-310.pyc b/extensions/CHECK/sd-webui-diffusion-cg/scripts/__pycache__/cg_xyz.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..419d8f2cb9c6ce4ce264f5764669529e8c2ae7e7 Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/scripts/__pycache__/cg_xyz.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/scripts/__pycache__/diffusion_cg.cpython-310.pyc b/extensions/CHECK/sd-webui-diffusion-cg/scripts/__pycache__/diffusion_cg.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a12576a306357637226f9312226137d9d8fe8c42 Binary files /dev/null and b/extensions/CHECK/sd-webui-diffusion-cg/scripts/__pycache__/diffusion_cg.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-diffusion-cg/scripts/cg_settings.py b/extensions/CHECK/sd-webui-diffusion-cg/scripts/cg_settings.py new file mode 100644 index 0000000000000000000000000000000000000000..e64fb22fcc29c1d94f9200a005f08bd185bc4a30 --- /dev/null +++ b/extensions/CHECK/sd-webui-diffusion-cg/scripts/cg_settings.py @@ -0,0 +1,12 @@ +from modules import script_callbacks, shared +import gradio as gr + +Section = ('diffusion_cg', "Diffusion CG") + +def on_ui_settings(): + shared.opts.add_option("always_center", shared.OptionInfo("None", "Always Perform Recenter on:", gr.Radio, lambda: {"choices": ["None", "txt2img", "img2img", "Both"]}, section=Section).needs_reload_ui()) + shared.opts.add_option("always_normalize", shared.OptionInfo("None", "Always Perform Normalization on:", gr.Radio, lambda: {"choices": ["None", "txt2img", "img2img", "Both"]}, section=Section).needs_reload_ui()) + shared.opts.add_option("default_arch", shared.OptionInfo("1.5", "Default Stable Diffusion Version", gr.Radio, lambda: {"choices": ["1.5", "XL"]}, section=Section).needs_reload_ui()) + shared.opts.add_option("show_center_opt", shared.OptionInfo(False, "[Advanced] Show ReCenter Settings", section=Section).needs_reload_ui()) + +script_callbacks.on_ui_settings(on_ui_settings) diff --git a/extensions/CHECK/sd-webui-diffusion-cg/scripts/cg_xyz.py b/extensions/CHECK/sd-webui-diffusion-cg/scripts/cg_xyz.py new file mode 100644 index 0000000000000000000000000000000000000000..d8096bed31859e7a0beb5f6a8bc2789ef4da4f62 --- /dev/null +++ b/extensions/CHECK/sd-webui-diffusion-cg/scripts/cg_xyz.py @@ -0,0 +1,51 @@ +from modules import shared, scripts + + +def grid_reference(): + for data in scripts.scripts_data: + if data.script_class.__module__ in ( + "scripts.xyz_grid", + "xyz_grid.py", + ) and hasattr(data, "module"): + return data.module + + raise SystemError("Could not find X/Y/Z Plot...") + + +def xyz_support(cache: dict): + xyz_grid = grid_reference() + + def apply_field(field): + def _(p, x, xs): + cache.update({field: x}) + + return _ + + def choices_bool(): + return ["False", "True"] + + extra_axis_options = [ + xyz_grid.AxisOption( + "[Diff. CG] Enable", str, apply_field("enableG"), choices=choices_bool + ), + xyz_grid.AxisOption("[Diff. CG] ReCenter", float, apply_field("rc_str")), + xyz_grid.AxisOption( + "[Diff. CG] Normalization", + str, + apply_field("enableN"), + choices=choices_bool, + ), + ] + + if getattr(shared.opts, "show_center_opt", False): + extra_axis_options += [ + xyz_grid.AxisOption("[Diff. CG] C", float, apply_field("C")), + xyz_grid.AxisOption("[Diff. CG] M", float, apply_field("M")), + xyz_grid.AxisOption("[Diff. CG] Y", float, apply_field("Y")), + xyz_grid.AxisOption("[Diff. CG] K", float, apply_field("K")), + xyz_grid.AxisOption("[Diff. CG] L", float, apply_field("L")), + xyz_grid.AxisOption("[Diff. CG] a", float, apply_field("a")), + xyz_grid.AxisOption("[Diff. CG] b", float, apply_field("b")), + ] + + xyz_grid.axis_options.extend(extra_axis_options) diff --git a/extensions/CHECK/sd-webui-diffusion-cg/scripts/diffusion_cg.py b/extensions/CHECK/sd-webui-diffusion-cg/scripts/diffusion_cg.py new file mode 100644 index 0000000000000000000000000000000000000000..21a951f4334c42edf8da1aaf13565efd415834c9 --- /dev/null +++ b/extensions/CHECK/sd-webui-diffusion-cg/scripts/diffusion_cg.py @@ -0,0 +1,384 @@ +from modules.sd_samplers_kdiffusion import KDiffusionSampler +from modules import shared, scripts, script_callbacks +import gradio as gr + +from scripts.cg_xyz import xyz_support + +VERSION = "v1.1.0" + +DYNAMIC_RANGE = [3.25, 2.5, 2.5, 2.5] + +Default_LUTs = {"C": 0.01, "M": 0.5, "Y": -0.13, "K": 0} + + +def normalize_tensor(x, r): + X = x.detach().clone() + + ratio = r / max(abs(float(X.min())), abs(float(X.max()))) + X *= max(ratio, 0.99) + + return X + + +original_callback = KDiffusionSampler.callback_state + + +def center_callback(self, d): + if not self.diffcg_enable or getattr(self.p, "_ad_inner", False): + return original_callback(self, d) + + X = d["denoised"].detach().clone() + batchSize = X.size(0) + channels = len(self.LUTs) + + for b in range(batchSize): + for c in range(channels): + + if self.diffcg_recenter_strength > 0.0: + d["denoised"][b][c] += ( + self.LUTs[c] - X[b][c].mean() + ) * self.diffcg_recenter_strength + + if self.diffcg_normalize and (d["i"] + 1) > self.diffcg_last_step // 2: + d["denoised"][b][c] = normalize_tensor(X[b][c], DYNAMIC_RANGE[c]) + + return original_callback(self, d) + + +KDiffusionSampler.callback_state = center_callback + + +# ["None", "txt2img", "img2img", "Both"] +ac = getattr(shared.opts, "always_center", "None") +an = getattr(shared.opts, "always_normalize", "None") +def_sd = getattr(shared.opts, "default_arch", "1.5") +adv_opt = getattr(shared.opts, "show_center_opt", False) + +c_t2i = ac in ("txt2img", "Both") +c_i2i = ac in ("img2img", "Both") +n_t2i = an in ("txt2img", "Both") +n_i2i = an in ("img2img", "Both") + + +class DiffusionCG(scripts.Script): + def __init__(self): + self.xyzCache = {} + xyz_support(self.xyzCache) + + def title(self): + return "DiffusionCG" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with gr.Accordion(f"Diffusion CG {VERSION}", open=False): + with gr.Row(): + enableG = gr.Checkbox( + label="Enable (Global)", + value=( + ((not is_img2img) and (c_t2i or n_t2i)) + or (is_img2img and (c_i2i or n_i2i)) + ), + ) + sd_ver = gr.Radio( + ["1.5", "XL"], value=def_sd, label="Stable Diffusion Version" + ) + + with gr.Row(): + with gr.Group(): + gr.Markdown('

Recenter

') + + if not is_img2img: + v = 1.0 if c_t2i else 0.0 + else: + v = 1.0 if c_i2i else 0.0 + + rc_str = gr.Slider( + label="Effect Strength", + minimum=0.0, + maximum=1.0, + step=0.2, + value=v, + ) + + with gr.Group(): + gr.Markdown('

Normalization

') + enableN = gr.Checkbox( + label="Activate", + value=(((not is_img2img) and n_t2i) or (is_img2img and n_i2i)), + ) + + with gr.Accordion("Recenter Settings", visible=adv_opt, open=False): + with gr.Group(visible=(def_sd == "1.5")) as setting15: + C = gr.Slider( + label="C", + minimum=-1.00, + maximum=1.00, + step=0.01, + value=Default_LUTs["C"], + ) + M = gr.Slider( + label="M", + minimum=-1.00, + maximum=1.00, + step=0.01, + value=Default_LUTs["M"], + ) + Y = gr.Slider( + label="Y", + minimum=-1.00, + maximum=1.00, + step=0.01, + value=Default_LUTs["Y"], + ) + K = gr.Slider( + label="K", + minimum=-1.00, + maximum=1.00, + step=0.01, + value=Default_LUTs["K"], + ) + + with gr.Group(visible=(def_sd == "XL")) as settingXL: + L = gr.Slider( + label="L", minimum=-1.00, maximum=1.00, step=0.01, value=0.0 + ) + a = gr.Slider( + label="a", minimum=-1.00, maximum=1.00, step=0.01, value=0.0 + ) + b = gr.Slider( + label="b", minimum=-1.00, maximum=1.00, step=0.01, value=0.0 + ) + + def on_radio_change(choice): + if choice == "1.5": + return [ + gr.Group.update(visible=True), + gr.Group.update(visible=False), + ] + else: + return [ + gr.Group.update(visible=False), + gr.Group.update(visible=True), + ] + + sd_ver.change(on_radio_change, sd_ver, [setting15, settingXL]) + + self.paste_field_names = [ + (rc_str, "ReCenter Str"), + (enableN, "Normalization"), + (sd_ver, "SD_ver"), + ] + self.infotext_fields = [ + (rc_str, "ReCenter Str"), + (enableN, "Normalization"), + (sd_ver, "SD_ver"), + ] + + if adv_opt: + self.paste_field_names += [ + ( + C, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[0]) + if len(d.get("LUTs", "").split(",")) == 4 + else gr.update() + ), + ), + ( + M, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[1]) + if len(d.get("LUTs", "").split(",")) == 4 + else gr.update() + ), + ), + ( + Y, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[2]) + if len(d.get("LUTs", "").split(",")) == 4 + else gr.update() + ), + ), + ( + K, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[3]) + if len(d.get("LUTs", "").split(",")) == 4 + else gr.update() + ), + ), + ( + L, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[0]) + if len(d.get("LUTs", "").split(",")) == 3 + else gr.update() + ), + ), + ( + a, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[1]) + if len(d.get("LUTs", "").split(",")) == 3 + else gr.update() + ), + ), + ( + b, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[2]) + if len(d.get("LUTs", "").split(",")) == 3 + else gr.update() + ), + ), + ] + self.infotext_fields += [ + ( + C, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[0]) + if len(d.get("LUTs", "").split(",")) == 4 + else gr.update() + ), + ), + ( + M, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[1]) + if len(d.get("LUTs", "").split(",")) == 4 + else gr.update() + ), + ), + ( + Y, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[2]) + if len(d.get("LUTs", "").split(",")) == 4 + else gr.update() + ), + ), + ( + K, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[3]) + if len(d.get("LUTs", "").split(",")) == 4 + else gr.update() + ), + ), + ( + L, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[0]) + if len(d.get("LUTs", "").split(",")) == 3 + else gr.update() + ), + ), + ( + a, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[1]) + if len(d.get("LUTs", "").split(",")) == 3 + else gr.update() + ), + ), + ( + b, + lambda d: ( + float(d["LUTs"].strip("[]").split(",")[2]) + if len(d.get("LUTs", "").split(",")) == 3 + else gr.update() + ), + ), + ] + + for comp in [enableG, sd_ver, rc_str, enableN, C, M, Y, K, L, a, b]: + comp.do_not_save_to_config = True + + return [enableG, sd_ver, rc_str, enableN, C, M, Y, K, L, a, b] + + def before_hr(self, p, *args): + KDiffusionSampler.diffcg_normalize = False + + def process( + self, + p, + enableG: bool, + sd_ver: str, + rc_str: float, + enableN: bool, + C: float, + M: float, + Y: float, + K: float, + L: float, + a: float, + b: float, + ): + + if "enableG" in self.xyzCache.keys(): + enableG = self.xyzCache["enableG"].lower().strip() == "true" + del self.xyzCache["enableG"] + + KDiffusionSampler.diffcg_enable = enableG + if not enableG: + if len(self.xyzCache.keys()) > 0: + print("\n[Diff. CG] X [X/Y/Z Plot] Extension is not Enabled!\n") + self.xyzCache.clear() + return p + + if "rc_str" in self.xyzCache.keys(): + rc_str = float(self.xyzCache["rc_str"]) + if "enableN" in self.xyzCache.keys(): + enableN = self.xyzCache["enableN"].lower().strip() == "true" + + if adv_opt: + C = self.xyzCache.get("C", C) + M = self.xyzCache.get("M", M) + Y = self.xyzCache.get("Y", Y) + K = self.xyzCache.get("K", K) + L = self.xyzCache.get("L", L) + a = self.xyzCache.get("a", a) + b = self.xyzCache.get("b", b) + + if sd_ver == "1.5": + KDiffusionSampler.LUTs = [-K, -M, C, Y] + else: + KDiffusionSampler.LUTs = [L, -a, b] + + KDiffusionSampler.diffcg_recenter_strength = rc_str + KDiffusionSampler.diffcg_normalize = enableN + + if ( + not hasattr(p, "enable_hr") + and not shared.opts.img2img_fix_steps + and getattr(p, "denoising_strength", 1.0) < 1.0 + ): + KDiffusionSampler.diffcg_last_step = int(p.steps * p.denoising_strength) + 1 + else: + KDiffusionSampler.diffcg_last_step = p.steps + + p.extra_generation_params.update( + { + "ReCenter Str": rc_str, + "Normalization": enableN, + "SD_ver": sd_ver, + } + ) + + if adv_opt: + if def_sd == "1.5": + p.extra_generation_params["LUTs"] = f"[{C}, {M}, {Y}, {K}]" + else: + p.extra_generation_params["LUTs"] = f"[{L}, {a}, {b}]" + + self.xyzCache.clear() + + +def restore_callback(): + KDiffusionSampler.callback_state = original_callback + + +script_callbacks.on_script_unloaded(restore_callback) diff --git a/extensions/CHECK/sd-webui-fabric/.gitignore b/extensions/CHECK/sd-webui-fabric/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..98fd9f005983a482e628d5a7e37c65b4485f8e6b --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/.gitignore @@ -0,0 +1,162 @@ +.DS_Store + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/extensions/CHECK/sd-webui-fabric/LICENSE b/extensions/CHECK/sd-webui-fabric/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..76e2c7de91cfec0f16447d30b0d25518688b01a0 --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Dimitri von Rütte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/CHECK/sd-webui-fabric/README.md b/extensions/CHECK/sd-webui-fabric/README.md new file mode 100644 index 0000000000000000000000000000000000000000..005b68193371a73fcbacd20403a204b0139970a6 --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/README.md @@ -0,0 +1,127 @@ +# FABRIC Plugin for Stable Diffusion WebUI + +Official FABRIC implementation for [automatic1111/stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui). Steer the model towards generating desirable results by simply liking/disliking images. These feedback images can be generated or provided by you and will make the model generate images that look more/less like the feedback. Instead of meticulously iterating on your prompt until you get what you're looking for, with FABRIC you can simply "show" the model what you want and don't want. + +📜 Paper: https://arxiv.org/abs/2307.10159 + +🎨 Project page: https://sd-fabric.github.io + +ComfyUI node (by [@ssitu](https://github.com/ssitu)): https://github.com/ssitu/ComfyUI_fabric + +![demo](static/fabric_demo.gif) + +## Releases and Changelog +- [09.03.2024] 🛡️ v0.6.6: Adds "burnout protection", which helps prevent low-quality results when a large number of feedback images is used. +- [07.03.2024] 🔨 v0.6.5: Fixes compatibility with [WebUI Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge). +- [07.03.2024] ✨ v0.6.4: SDXL support has been added. For optimal results, lowering the feedback strength is recommended (0.5 seems to be a good starting point). +- [29.08.2023] 🏎️ v0.6.0: Up to 2x faster and 4x less VRAM usage thanks to [Token Merging](https://github.com/dbolya/tomesd/tree/main) (tested with 16 feedback images and a batch size of 4), moderate gains for fewer feedback images (10% speedup for 2 images, 30% for 8 images). Enable the Token Merging option to take advantage of this. +- [22.08.2023] 🗃️ v0.5.0: Adds support for presets. Makes generated images using FABRIC more reproducible by loading the correct (previously used) feedback images when using "send to text2img/img2img". + +## Installation + +1. Open the "Extensions" tab +2. Open the "Install from URL" tab +3. Copy-paste `https://github.com/dvruette/sd-webui-fabric.git` into "URL for extension's git repository" and press "Install" +4. Switch to the "Installed" tab and press "Apply and restart UI" +5. (optional) Since FABRIC is quite VRAM intensive, using `--xformers` is recommended. + 1. If you still run out of VRAM, try enabling the "Token Merging" setting for even better memory efficiency. + +### Compatibility Notes +- FABRIC is compatible with SD 1.5, SDXL and WebUI Forge +- The plugin is INCOMPATIBLE with `reference` mode in the ControlNet plugin. Instead of using a reference image, simply add it as a liked image. If you accidentally enable FABRIC and `reference` mode at the same time, you will have to restart the WebUI to fix it. +- Some attention processors are not supported. In particular, `--opt-sub-quad-attention` and `--opt-split-attention-v1` are not supported at the moment. + + + +## How-to and Examples + +### Basic Usage +0. Enable the FABRIC extension +1. Add feedback images: + - select an image from a previous generation and press 👍/👎 in the "Current batch" tab OR + - select the "Upload image" tab, upload an image of your preference and press 👍/👎 +2. Press "Generate" to generate a batch of images incorporating the selected feedback +3. Repeat: Add more feedback, optionally adjust your prompt and regenerate + +_Tips:_ +- You don't have to keep using the same prompt that you used to generate feedback images. In fact, adjusting the prompt in conjunction with providing feedback is most powerful. +- While the number of feedback images is only limited by the size of your GPU, using fewer feedback images tends to give better results that clearly reflect both the prompt and feedback. Increasing the number of feedback images can sometimes lead to the model getting confused, giving too much weight to certain feedback images or completely ignoring others. + +#### Feedback Strength + +The feedback strength controls how much the model pays attention to your feedback images. The higher, the more it will try to stay close to the feedback, potentially ignoring certain aspects of the prompt. Lowering this value is recommended if you're using large numbers of feedback images or if you feel like the model sticks too close to the feedback. + +#### Feedback Schedule + +Using `feedback start` and `feedback end` it's possible to control at which denoising steps the model tries to incorporate the feedback. As a rule of thumb, early steps will influence high-level, coarse features (overall composition, large objects in the scene, ...) and later steps will influence fine-grained, low-level features (details, texture, small objects, ...). Adjusting these values makes it possible to only use feedback on certain features in the generation. A value of `0.0` corresponds to the first and `1.0` to the last denoising step (linear interpolation in between). + +Generally, it's recommended to have feedback active from the start but not until the end, but violating these principles can give interesting results in their own right, especially when simultaneously adjusting feedback strength. + +#### Token Merging + +Token merging (ToMe) is an optimization technique that improves speed and memory usage at the cost of accuracy. Enabling this _will_ change your results, but it can make generation times significantly faster (I observed up to 50%), especially for large resolutions and large numbers of feedback images. + + +### Examples + +#### Style Control using Feedback +Generating images in a certain style, adding them as feedback and dropping the style from the prompt allows retaining certain aspects from the style while retaining flexibility in the prompt: + +| Feedback image | Without feedback | With feedback | +| --- | --- | --- | +| ![picture of a horse riding on top of an astronaut, ukiyo-e](static/example_1_feedback.png) | ![picture of a horse riding on top of an astronaut on the beach](static/example_1_before.png) | ![picture of a horse riding on top of an astronaut on the beach](static/example_1_after.png) | +| picture of a horse riding on top of an astronaut, ukiyo-e | picture of a horse riding on top of an astronaut on the beach | picture of a horse riding on top of an astronaut on the beach | + +Negative prompt: `lowres, bad anatomy, bad hands, cropped, worst quality`; Seed: `1531668169` + +#### Feedback Strength +Feedback strength controls how much the model pays attention to the feedback. This example demonstrates the effect of varying it: + +| Feedback image | | | | +| --- | --- | --- | --- | +| ![pineapple](static/example_3_feedback.png) | | | | +| weight=0.0 | weight=0.2 | weight=0.4 | weight=0.8 | +| ![a new york pineapple](static/example_3_00.png) | ![a new york pineapple](static/example_3_02.png) | ![a new york pineapple](static/example_3_04.png) | ![a new york pineapple](static/example_3_08.png) | + +Prompt: `[macro picture of a pineapple, zoomcore:photo of new york at sunrise:0.3], masterpiece, trending on artstation[:, extremely detailed, hyperrealistic, 8k:0.5]`; Negative prompt: `lowres, bad anatomy, bad hands, cropped, worst quality, grayscale`; Seed: `2345285976`; + + +#### Feedback Schedule +By adjusting the feedback schedule, it's possible to control which features are influenced by the feedback. In this example, the feedback is only active between 30% and 60% of the generation, which allows to isolate the effect of the feedback: + +| Feedback image | Without feedback | Default schedule (0.0 - 0.8) | Custom schedule (0.3 - 0.6) | +| --- | --- | --- | --- | +| ![flowers](static/example_2_feedback.png) | ![a woman with long flowy hair wearing a dress made of pink flowers sitting on a sunny meadow](static/example_2_baseline.png) | ![a woman with long flowy hair wearing a dress made of pink flowers sitting on a sunny meadow](static/example_2_default.png) | ![a woman with long flowy hair wearing a dress made of pink flowers sitting on a sunny meadow](static/example_2_custom.png) | + +Prompt: `a woman with long flowy hair wearing a (dress made of pink flowers:1.1) sitting on a sunny meadow, vibrant`; Negative prompt: `lowres, bad anatomy, bad hands, cropped, worst quality, grayscale, muted colors, monochrome, sepia`; Seed: `2844331335` + + +_All examples were created using the DreamShaper 7 model: https://huggingface.co/Lykon/DreamShaper/tree/main_ + +
+ Advanced Usage + + #### Min. strength + Adjusting the minimum feedback strength controls how much feedback is incorporated during the passive phase, outside of the feedback schedule (i.e. when FABRIC is "inactive", before `feedback start` and after `feedback end`). This allows emphasizing the feedback during certain phases (feature scales) of the generation but still incorporating at least some of it from beginning to end. By default this is 0, so feedback is only incorporated during the active phase. + + #### Negative weight + The negative weight controls how much negative feedback is incorporated relative the the positive feedback. We have found that it's generally preferrable to have lower feedback strenth for negative images, which is why by default this value is `0.5`. Increasing this increases the influence of negative feedback (without changin the influence of positive feedback). + + #### ToMe settings + These settings are quite technical and understanding them is not strictly necessary for using them. Merge ratio controls the ratio of tokens that get merged: higher merge ratio -> fewer tokens -> more speed and less memory, but lower quality. Max. tokens limits the number of feedback tokens: fewer tokens -> more speed, less memory, but lower quality. The seed controls which tokens have a chance of being merged and is mainly there for reproducibility purposes. Changing the seed can alter the outcome quite significantly depending on how aggressive the other ToMe settings are. + + More information on ToMe: https://github.com/dbolya/tomesd/tree/main +
+ + +## Citation +``` +@misc{vonrutte2023fabric, + title={FABRIC: Personalizing Diffusion Models with Iterative Feedback}, + author={Dimitri von Rütte and Elisabetta Fedele and Jonathan Thomm and Lukas Wolf}, + year={2023}, + eprint={2307.10159}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} +``` diff --git a/extensions/CHECK/sd-webui-fabric/images/example1.png b/extensions/CHECK/sd-webui-fabric/images/example1.png new file mode 100644 index 0000000000000000000000000000000000000000..596bb038cf846bd0a3dbb0a91f25d0c52df373ac Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/images/example1.png differ diff --git a/extensions/CHECK/sd-webui-fabric/javascript/fabric.js b/extensions/CHECK/sd-webui-fabric/javascript/fabric.js new file mode 100644 index 0000000000000000000000000000000000000000..d6bf58d86c42cef06b57c074c617ff31abc89d5e --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/javascript/fabric.js @@ -0,0 +1,19 @@ + +function fabric_selected_gallery_index(elem_id) { + const el = document.getElementById(elem_id); + const buttons = el.querySelectorAll('.thumbnail-item.thumbnail-small'); + const button = el.querySelector('.thumbnail-item.thumbnail-small.selected'); + + let result = -1; + buttons.forEach((v, i) => { + if (v == button) { + result = i; + } + }); + + console.log(result); + console.log(button); + console.log(buttons); + + return result; +} diff --git a/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/fabric.cpython-310.pyc b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/fabric.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca46a1ca574db8b3e6a4cab44d9117e540e69b71 Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/fabric.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/fabric_utils.cpython-310.pyc b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/fabric_utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3149a8bc04c8b1319ba2fef910084ee5c06116ac Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/fabric_utils.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/marking.cpython-310.pyc b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/marking.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21be72cff1f0b111f8d557977025061f9cdce34d Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/marking.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/merging.cpython-310.pyc b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/merging.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b438bbe4e9398ccfd8d1fa59051d04062240999 Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/merging.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/patching.cpython-310.pyc b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/patching.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f367efb31530cbee0c5e66566bf1ca0b686dff6 Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/patching.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/weighted_attention.cpython-310.pyc b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/weighted_attention.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce97c672b632edb0560ff0c9b401b2135292e9f3 Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/scripts/__pycache__/weighted_attention.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-fabric/scripts/fabric.py b/extensions/CHECK/sd-webui-fabric/scripts/fabric.py new file mode 100644 index 0000000000000000000000000000000000000000..592ca485f43297de9af28ddd2a129f5f6b7f4943 --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/scripts/fabric.py @@ -0,0 +1,500 @@ +import os +import dataclasses +import functools +import json +import traceback +from pathlib import Path +from dataclasses import dataclass, asdict +from typing import Optional + +import gradio as gr +from PIL import Image + +import modules.scripts +from modules import script_callbacks +from modules.ui_components import FormGroup, FormRow +from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, get_fixed_seed + +from scripts.fabric_utils import WebUiComponents, image_hash +from scripts.patching import patch_unet_forward_pass, unpatch_unet_forward_pass + +# Compatibility with WebUI v1.3.0 and earlier versions +try: + # WebUI v1.4.0+ + from modules.ui_common import create_refresh_button +except ImportError: + # Earlier versions + from modules.ui import create_refresh_button + + +__version__ = "0.6.6" + +DEBUG = os.getenv("DEBUG", "false").lower() in ("true", "1") + +OUTPUT_PATH = "log/fabric/images" +PRESET_PATH = "log/fabric/presets" + +if DEBUG: + print(f"WARNING: Loading FABRIC v{__version__} in DEBUG mode") +else: + print(f"Loading FABRIC v{__version__}") + +""" +# Gradio 3.32 bug fix +Fixes FileNotFoundError when displaying PIL images in Gradio Gallery. +""" +import tempfile +gradio_tempfile_path = os.path.join(tempfile.gettempdir(), 'gradio') +os.makedirs(gradio_tempfile_path, exist_ok=True) + + +def use_feedback(params): + if not params.enabled: + return False + if params.start >= params.end and params.min_weight <= 0: + return False + if params.max_weight <= 0: + return False + if params.neg_scale <= 0 and len(params.pos_images) == 0: + return False + if len(params.pos_images) == 0 and len(params.neg_images) == 0: + return False + return True + + +def save_feedback_image(img, filename=None, base_path=OUTPUT_PATH): + if filename is None: + filename = image_hash(img) + ".png" + img_path = Path(modules.scripts.basedir(), base_path, filename) + img_path.parent.mkdir(parents=True, exist_ok=True) + img.save(img_path) + return filename + + +@functools.lru_cache(maxsize=128) +def load_feedback_image(filename, base_path=OUTPUT_PATH): + img_path = Path(modules.scripts.basedir(), base_path, filename) + return Image.open(img_path) + + +def full_image_path(filename, base_path=OUTPUT_PATH): + img_path = Path(modules.scripts.basedir(), base_path, filename) + return str(img_path) + + +# helper functions for loading saved params +def _load_feedback_paths(d, key): + try: + paths = json.loads(d.get(key, "[]").replace("'", '"')) + except Exception as e: + traceback.print_exc() + print(d) + print(f"Failed to load feedback images: {d.get(key, '[]')}") + paths = [] + + paths = [path for path in paths if os.path.exists(full_image_path(path))] + return paths + +def _load_gallery(d, key): + paths = _load_feedback_paths(d, key) + return [full_image_path(path) for path in paths] + + +def _save_preset(preset_name, liked_paths, disliked_paths, base_path=PRESET_PATH): + preset_path = Path(modules.scripts.basedir(), base_path, f"{preset_name}.json") + preset_path.parent.mkdir(parents=True, exist_ok=True) + + preset = { + "liked_paths": liked_paths, + "disliked_paths": disliked_paths, + } + + with open(preset_path, "w") as f: + json.dump(preset, f, indent=4) + +def _load_presets(base_path=PRESET_PATH): + presets_path = Path(modules.scripts.basedir(), base_path) + presets_path.mkdir(parents=True, exist_ok=True) + presets = [preset.stem for preset in presets_path.iterdir() if preset.is_file() and preset.suffix == ".json"] + return presets + + +@dataclass +class FabricParams: + enabled: bool = True + start: float = 0.0 + end: float = 0.8 + min_weight: float = 0.0 + max_weight: float = 0.8 + neg_scale: float = 0.5 + pos_images: list = dataclasses.field(default_factory=list) + neg_images: list = dataclasses.field(default_factory=list) + pos_latents: Optional[list] = None + neg_latents: Optional[list] = None + pos_latent_cache: Optional[dict] = None + neg_latent_cache: Optional[dict] = None + + feedback_during_high_res_fix: bool = False + tome_enabled: bool = False + tome_ratio: float = 0.5 + tome_max_tokens: int = 4*4096 + tome_seed: int = -1 + burnout_protection: bool = False + + +# TODO: replace global state with Gradio state +class FabricState: + txt2img_images = [] + img2img_images = [] + + +class FabricScript(modules.scripts.Script): + def __init__(self) -> None: + super().__init__() + + def title(self): + return "FABRIC" + + def show(self, is_img2img): + return modules.scripts.AlwaysVisible + + def ui(self, is_img2img): + self.txt2img_selected_image = gr.State(None) + self.img2img_selected_image = gr.State(None) + selected_like = gr.State(None) + selected_dislike = gr.State(None) + # need to use JSON over State to make it compatible with gr.update + liked_paths = gr.JSON(value=[], visible=False) + disliked_paths = gr.JSON(value=[], visible=False) + + with gr.Accordion(f"{self.title()} v{__version__}", open=DEBUG, elem_id="fabric"): + with FormGroup(): + with FormRow(): + feedback_enabled = gr.Checkbox(label="Enable", value=False) + feedback_during_high_res_fix = gr.Checkbox(label="Enable during hires. fix", value=False) + + with gr.Row(): + presets_list = gr.Dropdown(label="Presets", choices=_load_presets(), default=None, live=False) + create_refresh_button(presets_list, lambda: None, lambda: {"choices": _load_presets()}, "fabric_reload_presets_btn") + + with gr.Tabs(): + with gr.Tab("Current batch"): + # TODO: figure out why the display is shared between tabs + self.img2img_selected_display = gr.Image(value=None, type="pil", label="Selected image", visible=is_img2img, height=256) + self.txt2img_selected_display = gr.Image(value=None, type="pil", label="Selected image", visible=not is_img2img, height=256) + + with gr.Row(): + like_btn_selected = gr.Button("👍 Like") + dislike_btn_selected = gr.Button("👎 Dislike") + + with gr.Tab("Upload image"): + upload_img_input = gr.Image(type="pil", label="Upload image", height=256) + + with gr.Row(): + like_btn_uploaded = gr.Button("👍 Like") + dislike_btn_uploaded = gr.Button("👎 Dislike") + + with gr.Tabs(initial_tab="👍 Likes"): + with gr.Tab("👍 Likes"): + with gr.Row(): + remove_selected_like_btn = gr.Button("Remove selected", interactive=False) + clear_liked_btn = gr.Button("Clear") + like_gallery = gr.Gallery(label="Liked images", elem_id="fabric_like_gallery", columns=4, height=192) + + with gr.Tab("👎 Dislikes"): + with gr.Row(): + remove_selected_dislike_btn = gr.Button("Remove selected", interactive=False) + clear_disliked_btn = gr.Button("Clear") + dislike_gallery = gr.Gallery(label="Disliked images", elem_id="fabric_dislike_gallery", columns=4, height=192) + + save_preset_btn = gr.Button("Save as preset") + + + gr.HTML("
") + + with FormGroup(): + with FormRow(): + feedback_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.0, label="Feedback start") + feedback_end = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.8, label="Feedback end") + + feedback_max_weight = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.8, label="Feedback Strength", elem_id="fabric_max_weight") + tome_enabled = gr.Checkbox(label="Enable Token Merging (faster, less VRAM, less accurate)", value=False) + burnout_protection = gr.Checkbox(label="Burnout protection (enable if results contain artifacts or are especially dark)", value=False) + + with gr.Accordion("Advanced options", open=DEBUG): + with FormGroup(): + feedback_min_weight = gr.Slider(minimum=0, maximum=1, step=0.05, value=0.0, label="Min. strength", info="Minimum feedback strength at every diffusion step.", elem_id="fabric_min_weight") + feedback_neg_scale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.5, label="Negative weight", info="Strength of negative feedback relative to positive feedback.", elem_id="fabric_neg_scale") + + tome_ratio = gr.Slider(minimum=0.0, maximum=0.75, step=0.125, value=0.5, label="ToMe merge ratio", info="Percentage of tokens to be merged (higher improves speed)", elem_id="fabric_tome_ratio") + tome_max_tokens = gr.Slider(minimum=4096, maximum=16*4096, step=4096, value=2*4096, label="ToMe max. tokens", info="Maximum number of tokens after merging (lower improves VRAM usage)", elem_id="fabric_tome_max_tokens") + tome_seed = gr.Number(label="ToMe seed", value=-1, step=1, info="Random seed for ToMe partition", elem_id="fabric_tome_seed") + + + + WebUiComponents.on_txt2img_gallery(self.register_txt2img_gallery_select) + WebUiComponents.on_img2img_gallery(self.register_img2img_gallery_select) + + if is_img2img: + like_btn_selected.click(self.add_image_to_state, inputs=[self.img2img_selected_image, liked_paths], outputs=[like_gallery, liked_paths]) + dislike_btn_selected.click(self.add_image_to_state, inputs=[self.img2img_selected_image, disliked_paths], outputs=[dislike_gallery, disliked_paths]) + else: + like_btn_selected.click(self.add_image_to_state, inputs=[self.txt2img_selected_image, liked_paths], outputs=[like_gallery, liked_paths]) + dislike_btn_selected.click(self.add_image_to_state, inputs=[self.txt2img_selected_image, disliked_paths], outputs=[dislike_gallery, disliked_paths]) + + like_btn_uploaded.click(self.add_image_to_state, inputs=[upload_img_input, liked_paths], outputs=[like_gallery, liked_paths]) + dislike_btn_uploaded.click(self.add_image_to_state, inputs=[upload_img_input, disliked_paths], outputs=[dislike_gallery, disliked_paths]) + + clear_liked_btn.click(lambda _: ([], [], []), inputs=[], outputs=[like_gallery, liked_paths]) + clear_disliked_btn.click(lambda _: ([], [], []), inputs=[], outputs=[dislike_gallery, disliked_paths]) + + like_gallery.select( + self.select_for_removal, + _js="(a, b) => [a, fabric_selected_gallery_index('fabric_like_gallery')]", + inputs=[like_gallery, like_gallery], + outputs=[selected_like, remove_selected_like_btn], + ) + + dislike_gallery.select( + self.select_for_removal, + _js="(a, b) => [a, fabric_selected_gallery_index('fabric_dislike_gallery')]", + inputs=[dislike_gallery, dislike_gallery], + outputs=[selected_dislike, remove_selected_dislike_btn], + ) + + remove_selected_like_btn.click( + self.remove_selected, + inputs=[liked_paths, selected_like], + outputs=[like_gallery, liked_paths, selected_like, remove_selected_like_btn], + ) + + remove_selected_dislike_btn.click( + self.remove_selected, + inputs=[disliked_paths, selected_dislike], + outputs=[dislike_gallery, disliked_paths, selected_dislike, remove_selected_dislike_btn], + ) + + save_preset_btn.click( + self.save_preset, + _js="(a, b, c, d) => [a, b, c, prompt('Enter a name for your preset:')]", + inputs=[presets_list, liked_paths, disliked_paths, disliked_paths], # last input is a dummy + outputs=[presets_list], + ) + + presets_list.input( + self.on_preset_selected, + inputs=[presets_list, liked_paths, disliked_paths], + outputs=[ + liked_paths, + disliked_paths, + like_gallery, + dislike_gallery, + ], + ) + + # sets FABRIC params when "send to txt2img/img2img" is clicked + self.infotext_fields = [ + (feedback_enabled, lambda d: gr.Checkbox.update(value="fabric_start" in d)), + (feedback_start, "fabric_start"), + (feedback_end, "fabric_end"), + (feedback_min_weight, "fabric_min_weight"), + (feedback_max_weight, "fabric_max_weight"), + (feedback_neg_scale, "fabric_neg_scale"), + (tome_enabled, "fabric_tome_enabled"), + (tome_ratio, "fabric_tome_ratio"), + (tome_max_tokens, "fabric_tome_max_tokens"), + (tome_seed, "fabric_tome_seed"), + (burnout_protection, "fabric_burnout_protection"), + (feedback_during_high_res_fix, "fabric_feedback_during_high_res_fix"), + (liked_paths, lambda d: gr.update(value=_load_feedback_paths(d, "fabric_pos_images")) if "fabric_pos_images" in d else None), + (disliked_paths, lambda d: gr.update(value=_load_feedback_paths(d, "fabric_neg_images")) if "fabric_neg_images" in d else None), + (like_gallery, lambda d: gr.Gallery.update(value=_load_gallery(d, "fabric_pos_images")) if "fabric_pos_images" in d else None), + (dislike_gallery, lambda d: gr.Gallery.update(value=_load_gallery(d, "fabric_neg_images")) if "fabric_neg_images" in d else None), + ] + + return [ + liked_paths, + disliked_paths, + feedback_enabled, + feedback_start, + feedback_end, + feedback_min_weight, + feedback_max_weight, + feedback_neg_scale, + feedback_during_high_res_fix, + tome_enabled, + tome_ratio, + tome_max_tokens, + tome_seed, + burnout_protection, + ] + + + def select_for_removal(self, gallery, selected_idx): + return [ + selected_idx, + gr.update(interactive=True), + ] + + def remove_selected(self, paths, idx): + if idx >= 0 and idx < len(paths): + paths.pop(idx) + gallery = [full_image_path(path) for path in paths] + + return [ + gallery, + paths, + gr.update(value=None), + gr.update(interactive=False), + ] + + def add_image_to_state(self, img, paths): + if img is not None: + path = save_feedback_image(img) + paths.append(path) + gallery = [full_image_path(path) for path in paths] + return gallery, paths + + def save_preset(self, presets, liked_paths, disliked_paths, preset_name): + if preset_name is not None and preset_name != "": + _save_preset(preset_name, liked_paths, disliked_paths) + return gr.update(choices=_load_presets()) + + def on_preset_selected(self, preset_name, liked_paths, disliked_paths): + preset_path = Path(modules.scripts.basedir(), PRESET_PATH, f"{preset_name}.json") + if preset_path.exists(): + try: + with open(preset_path, "r") as f: + preset = json.load(f) + assert "liked_paths" in preset, "Missing 'liked_paths' in preset" + assert "disliked_paths" in preset, "Missing 'disliked_paths' in preset" + liked_paths = preset["liked_paths"] + disliked_paths = preset["disliked_paths"] + except Exception as e: + traceback.print_exc() + print(f"Failed to load preset: {preset_path}") + like_gallery = [full_image_path(path) for path in liked_paths] + dislike_gallery = [full_image_path(path) for path in disliked_paths] + return liked_paths, disliked_paths, like_gallery, dislike_gallery + + def register_txt2img_gallery_select(self, gallery): + self.register_gallery_select( + gallery, + listener=self.on_txt2img_gallery_select, + selected=self.txt2img_selected_image, + display=self.txt2img_selected_display, + ) + + def register_img2img_gallery_select(self, gallery): + self.register_gallery_select( + gallery, + listener=self.on_img2img_gallery_select, + selected=self.img2img_selected_image, + display=self.img2img_selected_display, + ) + + def register_gallery_select(self, gallery, listener=None, selected=None, display=None): + gallery.select( + listener, + _js="(a, b) => [a, selected_gallery_index()]", + inputs=[ + gallery, + gallery, # can be any Gradio component (but not None), will be overwritten with selected gallery index + ], + outputs=[selected, display], + ) + + def on_txt2img_gallery_select(self, gallery, selected_idx): + return self.on_gallery_select(gallery, selected_idx, FabricState.txt2img_images) + + def on_img2img_gallery_select(self, gallery, selected_idx): + return self.on_gallery_select(gallery, selected_idx, FabricState.img2img_images) + + def on_gallery_select(self, gallery, selected_idx, images): + idx = selected_idx - (len(gallery) - len(images)) + + if idx >= 0 and idx < len(images): + return images[idx], gr.update(value=images[idx]) + else: + return None, None + + def process(self, p, *args): + ( + liked_paths, + disliked_paths, + feedback_enabled, + feedback_start, + feedback_end, + feedback_min_weight, + feedback_max_weight, + feedback_neg_scale, + feedback_during_high_res_fix, + tome_enabled, + tome_ratio, + tome_max_tokens, + tome_seed, + burnout_protection, + ) = args + + # restore original U-Net forward pass in case previous batch errored out + unpatch_unet_forward_pass(p.sd_model.model.diffusion_model) + + if not feedback_enabled: + return + + likes = [load_feedback_image(path) for path in liked_paths] + dislikes = [load_feedback_image(path) for path in disliked_paths] + + params = FabricParams( + enabled=feedback_enabled, + start=feedback_start, + end=feedback_end, + min_weight=feedback_min_weight, + max_weight=feedback_max_weight, + neg_scale=feedback_neg_scale, + pos_images=likes, + neg_images=dislikes, + feedback_during_high_res_fix=feedback_during_high_res_fix, + tome_enabled=tome_enabled, + tome_ratio=(round(tome_ratio * 16) / 16), + tome_max_tokens=tome_max_tokens, + tome_seed=get_fixed_seed(int(tome_seed)), + burnout_protection=burnout_protection, + ) + + + if use_feedback(params) or (DEBUG and feedback_enabled): + print(f"[FABRIC] Patching U-Net forward pass... ({len(likes)} likes, {len(dislikes)} dislikes)") + + # log the generation params to be displayed/stored as metadata + log_params = asdict(params) + log_params["pos_images"] = json.dumps(liked_paths) + log_params["neg_images"] = json.dumps(disliked_paths) + del log_params["enabled"] + + if not params.tome_enabled: + del log_params["tome_ratio"] + del log_params["tome_max_tokens"] + del log_params["tome_seed"] + + log_params = {f"fabric_{k}": v for k, v in log_params.items()} + p.extra_generation_params.update(log_params) + + unet = p.sd_model.model.diffusion_model + patch_unet_forward_pass(p, unet, params) + else: + print("[FABRIC] Skipping U-Net forward pass patching") + + def postprocess(self, p, processed, *args): + unpatch_unet_forward_pass(p.sd_model.model.diffusion_model) + + images = processed.images[processed.index_of_first_image:] + if isinstance(p, StableDiffusionProcessingTxt2Img): + FabricState.txt2img_images = images + elif isinstance(p, StableDiffusionProcessingImg2Img): + FabricState.img2img_images = images + else: + raise RuntimeError(f"Unsupported processing type: {type(p)}") + + +script_callbacks.on_after_component(WebUiComponents.register_component) diff --git a/extensions/CHECK/sd-webui-fabric/scripts/fabric_utils.py b/extensions/CHECK/sd-webui-fabric/scripts/fabric_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..5648bf6e48045e58dee81191d7a4137caac8e6a3 --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/scripts/fabric_utils.py @@ -0,0 +1,46 @@ +import hashlib + +from PIL import Image + + +def image_hash(img: Image.Image, length: int = 16): + hash_sha256 = hashlib.sha256() + hash_sha256.update(img.tobytes()) + img_hash = hash_sha256.hexdigest() + if length and length > 0: + img_hash = img_hash[:length] + return img_hash + + +class WebUiComponents: + txt2img_gallery = None + img2img_gallery = None + txt2img_callbacks = [] + img2img_callbacks = [] + + @staticmethod + def on_txt2img_gallery(callback): + if WebUiComponents.txt2img_gallery is not None: + callback(WebUiComponents.txt2img_gallery) + else: + WebUiComponents.txt2img_callbacks.append(callback) + + def on_img2img_gallery(callback): + if WebUiComponents.img2img_gallery is not None: + callback(WebUiComponents.img2img_gallery) + else: + WebUiComponents.img2img_callbacks.append(callback) + + @staticmethod + def register_component(component, **kwargs): + elem_id = getattr(component, "elem_id", None) + if elem_id == "txt2img_gallery": + WebUiComponents.txt2img_gallery = component + for callback in WebUiComponents.txt2img_callbacks: + callback(component) + WebUiComponents.txt2img_callbacks = [] + elif elem_id == "img2img_gallery": + WebUiComponents.img2img_gallery = component + for callback in WebUiComponents.img2img_callbacks: + callback(component) + WebUiComponents.img2img_callbacks = [] diff --git a/extensions/CHECK/sd-webui-fabric/scripts/marking.py b/extensions/CHECK/sd-webui-fabric/scripts/marking.py new file mode 100644 index 0000000000000000000000000000000000000000..b799329fdf1c0317ca0400a4d66e55e9d086cc2c --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/scripts/marking.py @@ -0,0 +1,99 @@ +import torch + +from modules.prompt_parser import MulticondLearnedConditioning, ComposableScheduledPromptConditioning, ScheduledPromptConditioning +from modules.processing import StableDiffusionProcessing + + +""" +We adopt the same marking strategy as ControlNet for determining whether a prompt is conditional or unconditional. +For the original implementation see: https://github.com/Mikubill/sd-webui-controlnet/blob/main/scripts/hook.py +""" + +POSITIVE_MARK_TOKEN = 1024 +NEGATIVE_MARK_TOKEN = - POSITIVE_MARK_TOKEN +MARK_EPS = 1e-3 + + +def process_sample(process, *args, **kwargs): + # ControlNet must know whether a prompt is conditional prompt (positive prompt) or unconditional conditioning prompt (negative prompt). + # You can use the hook.py's `mark_prompt_context` to mark the prompts that will be seen by ControlNet. + # Let us say XXX is a MulticondLearnedConditioning or a ComposableScheduledPromptConditioning or a ScheduledPromptConditioning or a list of these components, + # if XXX is a positive prompt, you should call mark_prompt_context(XXX, positive=True) + # if XXX is a negative prompt, you should call mark_prompt_context(XXX, positive=False) + # After you mark the prompts, the ControlNet will know which prompt is cond/uncond and works as expected. + # After you mark the prompts, the mismatch errors will disappear. + mark_prompt_context(kwargs.get('conditioning', []), positive=True) + mark_prompt_context(kwargs.get('unconditional_conditioning', []), positive=False) + mark_prompt_context(getattr(process, 'hr_c', []), positive=True) + mark_prompt_context(getattr(process, 'hr_uc', []), positive=False) + return process.sample_before_CN_hack(*args, **kwargs) + + +def prompt_context_is_marked(x): + t = x[..., 0, :] + m = torch.abs(t) - POSITIVE_MARK_TOKEN + m = torch.mean(torch.abs(m)).detach().cpu().float().numpy() + return float(m) < MARK_EPS + + +def mark_prompt_context(x, positive): + if isinstance(x, list): + for i in range(len(x)): + x[i] = mark_prompt_context(x[i], positive) + return x + if isinstance(x, MulticondLearnedConditioning): + x.batch = mark_prompt_context(x.batch, positive) + return x + if isinstance(x, ComposableScheduledPromptConditioning): + x.schedules = mark_prompt_context(x.schedules, positive) + return x + if isinstance(x, ScheduledPromptConditioning): + if isinstance(x.cond, dict): + cond = x.cond['crossattn'] + if prompt_context_is_marked(cond): + return x + mark = POSITIVE_MARK_TOKEN if positive else NEGATIVE_MARK_TOKEN + cond = torch.cat([torch.zeros_like(cond)[:1] + mark, cond], dim=0) + return ScheduledPromptConditioning(end_at_step=x.end_at_step, cond=dict(crossattn=cond, vector=x.cond['vector'])) + else: + cond = x.cond + if prompt_context_is_marked(cond): + return x + mark = POSITIVE_MARK_TOKEN if positive else NEGATIVE_MARK_TOKEN + cond = torch.cat([torch.zeros_like(cond)[:1] + mark, cond], dim=0) + return ScheduledPromptConditioning(end_at_step=x.end_at_step, cond=cond) + return x + + +def unmark_prompt_context(x): + if not prompt_context_is_marked(x): + # ControlNet must know whether a prompt is conditional prompt (positive prompt) or unconditional conditioning prompt (negative prompt). + # You can use the hook.py's `mark_prompt_context` to mark the prompts that will be seen by ControlNet. + # Let us say XXX is a MulticondLearnedConditioning or a ComposableScheduledPromptConditioning or a ScheduledPromptConditioning or a list of these components, + # if XXX is a positive prompt, you should call mark_prompt_context(XXX, positive=True) + # if XXX is a negative prompt, you should call mark_prompt_context(XXX, positive=False) + # After you mark the prompts, the ControlNet will know which prompt is cond/uncond and works as expected. + # After you mark the prompts, the mismatch errors will disappear. + mark_batch = torch.ones(size=(x.shape[0], 1, 1, 1), dtype=x.dtype, device=x.device) + context = x + return mark_batch, [], [], context + mark = x[:, 0, :] + context = x[:, 1:, :] + mark = torch.mean(torch.abs(mark - NEGATIVE_MARK_TOKEN), dim=1) + mark = (mark > MARK_EPS).float() + mark_batch = mark[:, None, None, None].to(x.dtype).to(x.device) + + mark = mark.detach().cpu().numpy().tolist() + uc_indices = [i for i, item in enumerate(mark) if item < 0.5] + c_indices = [i for i, item in enumerate(mark) if not item < 0.5] + + StableDiffusionProcessing.cached_c = [None, None] + StableDiffusionProcessing.cached_uc = [None, None] + + return mark_batch, uc_indices, c_indices, context + + +def apply_marking_patch(process): + if getattr(process, 'sample_before_CN_hack', None) is None: + process.sample_before_CN_hack = process.sample + process.sample = process_sample.__get__(process) diff --git a/extensions/CHECK/sd-webui-fabric/scripts/merging.py b/extensions/CHECK/sd-webui-fabric/scripts/merging.py new file mode 100644 index 0000000000000000000000000000000000000000..70b8e7e06df88b8afae53356d0d27c36ca140348 --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/scripts/merging.py @@ -0,0 +1,191 @@ +import torch +import math +from typing import Dict, Any, Tuple, Callable + + +""" +Copied and adapted from https://github.com/dbolya/tomesd/tree/main +Relevant files: +- https://github.com/dbolya/tomesd/blob/main/tomesd/merge.py +- https://github.com/dbolya/tomesd/blob/main/tomesd/patching.py +""" + +def init_generator(device: torch.device, fallback: torch.Generator=None, seed: int = 42): + """ + Forks the current default random generator given device. + """ + if device.type == "cpu": + return torch.Generator(device="cpu").manual_seed(seed) + elif device.type == "cuda": + return torch.Generator(device=device).manual_seed(seed) + else: + if fallback is None: + return init_generator(torch.device("cpu")) + else: + return fallback + +def do_nothing(x: torch.Tensor, mode: str = None): + return x + + +def mps_gather_workaround(input, dim, index): + if input.shape[-1] == 1: + return torch.gather( + input.unsqueeze(-1), + dim - 1 if dim < 0 else dim, + index.unsqueeze(-1) + ).squeeze(-1) + else: + return torch.gather(input, dim, index) + + +def bipartite_soft_matching_random2d(metric: torch.Tensor, + w: int, h: int, sx: int, sy: int, r: int, + no_rand: bool = False, + generator: torch.Generator = None) -> Tuple[Callable, Callable]: + """ + Partitions the tokens into src and dst and merges r tokens from src to dst. + Dst tokens are partitioned by choosing one randomy in each (sx, sy) region. + + Args: + - metric [B, N, C]: metric to use for similarity + - w: image width in tokens + - h: image height in tokens + - sx: stride in the x dimension for dst, must divide w + - sy: stride in the y dimension for dst, must divide h + - r: number of tokens to remove (by merging) + - no_rand: if true, disable randomness (use top left corner only) + - rand_seed: if no_rand is false, and if not None, sets random seed. + """ + B, N, _ = metric.shape + + if r <= 0: + return do_nothing, do_nothing + + gather = mps_gather_workaround if metric.device.type == "mps" else torch.gather + + with torch.no_grad(): + hsy, wsx = h // sy, w // sx + + # For each sy by sx kernel, randomly assign one token to be dst and the rest src + if no_rand: + rand_idx = torch.zeros(hsy, wsx, 1, device=metric.device, dtype=torch.int64) + else: + rand_idx = torch.randint(sy*sx, size=(hsy, wsx, 1), device=generator.device, generator=generator).to(metric.device) + + # The image might not divide sx and sy, so we need to work on a view of the top left if the idx buffer instead + idx_buffer_view = torch.zeros(hsy, wsx, sy*sx, device=metric.device, dtype=torch.int64) + idx_buffer_view.scatter_(dim=2, index=rand_idx, src=-torch.ones_like(rand_idx, dtype=rand_idx.dtype)) + idx_buffer_view = idx_buffer_view.view(hsy, wsx, sy, sx).transpose(1, 2).reshape(hsy * sy, wsx * sx) + + # Image is not divisible by sx or sy so we need to move it into a new buffer + if (hsy * sy) < h or (wsx * sx) < w: + idx_buffer = torch.zeros(h, w, device=metric.device, dtype=torch.int64) + idx_buffer[:(hsy * sy), :(wsx * sx)] = idx_buffer_view + else: + idx_buffer = idx_buffer_view + + # We set dst tokens to be -1 and src to be 0, so an argsort gives us dst|src indices + rand_idx = idx_buffer.reshape(1, -1, 1).argsort(dim=1) + + # We're finished with these + del idx_buffer, idx_buffer_view + + # rand_idx is currently dst|src, so split them + num_dst = hsy * wsx + a_idx = rand_idx[:, num_dst:, :] # src + b_idx = rand_idx[:, :num_dst, :] # dst + + def split(x): + C = x.shape[-1] + src = gather(x, dim=1, index=a_idx.expand(B, N - num_dst, C)) + dst = gather(x, dim=1, index=b_idx.expand(B, num_dst, C)) + return src, dst + + # Cosine similarity between A and B + metric = metric / metric.norm(dim=-1, keepdim=True) + a, b = split(metric) + scores = a @ b.transpose(-1, -2) + + # Can't reduce more than the # tokens in src + r = min(a.shape[1], r) + + # Find the most similar greedily + node_max, node_idx = scores.max(dim=-1) + edge_idx = node_max.argsort(dim=-1, descending=True)[..., None] + + unm_idx = edge_idx[..., r:, :] # Unmerged Tokens + src_idx = edge_idx[..., :r, :] # Merged Tokens + dst_idx = gather(node_idx[..., None], dim=-2, index=src_idx) + + def merge(x: torch.Tensor, mode="mean") -> torch.Tensor: + src, dst = split(x) + n, t1, c = src.shape + + unm = gather(src, dim=-2, index=unm_idx.expand(n, t1 - r, c)) + src = gather(src, dim=-2, index=src_idx.expand(n, r, c)) + dst = dst.scatter_reduce(-2, dst_idx.expand(n, r, c), src, reduce=mode) + + return torch.cat([unm, dst], dim=1) + + def unmerge(x: torch.Tensor) -> torch.Tensor: + unm_len = unm_idx.shape[1] + unm, dst = x[..., :unm_len, :], x[..., unm_len:, :] + _, _, c = unm.shape + + src = gather(dst, dim=-2, index=dst_idx.expand(B, r, c)) + + # Combine back to the original shape + out = torch.zeros(B, N, c, device=x.device, dtype=x.dtype) + out.scatter_(dim=-2, index=b_idx.expand(B, num_dst, c), src=dst) + out.scatter_(dim=-2, index=gather(a_idx.expand(B, a_idx.shape[1], 1), dim=1, index=unm_idx).expand(B, unm_len, c), src=unm) + out.scatter_(dim=-2, index=gather(a_idx.expand(B, a_idx.shape[1], 1), dim=1, index=src_idx).expand(B, r, c), src=src) + + return out + + return merge, unmerge + + +def compute_merge( + x: torch.Tensor, + args: Dict[str, Any], + size: Tuple[int, int], + max_tokens: int = None, + ratio: float = None, +) -> Tuple[Callable, ...]: + if not args["enabled"]: + return do_nothing, do_nothing + + if max_tokens is None and ratio is None: + raise ValueError("Must specify either max_tokens or ratio") + + original_h, original_w = size + original_tokens = original_h * original_w + downsample = int(math.ceil(math.sqrt(original_tokens // x.shape[1]))) + + if ratio is not None: + target_tokens = int(x.shape[1] * (1 - ratio)) + else: + target_tokens = x.shape[1] + + if max_tokens is not None and max_tokens > 0: + target_tokens = min(target_tokens, max_tokens) # remove all but max_tokens tokens + r = x.shape[1] - target_tokens + + if r > 0: + w = int(math.ceil(original_w / downsample)) + h = int(math.ceil(original_h / downsample)) + + # Re-init the generator if it hasn't already been initialized or device has changed. + if args["generator"] is None: + args["generator"] = init_generator(x.device, seed=args["seed"]) + elif args["generator"].device != x.device: + args["generator"] = init_generator(x.device, fallback=args["generator"], seed=args["seed"]) + + # If the batch size is odd, then it's not possible for prompted and unprompted images to be in the same + # batch, which causes artifacts with use_rand, so force it to be off. + use_rand = False if x.shape[0] % 2 == 1 else args["use_rand"] + return bipartite_soft_matching_random2d(x, w, h, args["sx"], args["sy"], r, + no_rand=not use_rand, generator=args["generator"]) + else: + return do_nothing, do_nothing diff --git a/extensions/CHECK/sd-webui-fabric/scripts/patching.py b/extensions/CHECK/sd-webui-fabric/scripts/patching.py new file mode 100644 index 0000000000000000000000000000000000000000..fea7185ab1c842d2f63e5e541d31d056ecdece49 --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/scripts/patching.py @@ -0,0 +1,338 @@ +import functools + +import torch +import torchvision.transforms.functional as functional + +from modules import devices, images, shared +from modules.processing import StableDiffusionProcessingTxt2Img + +import ldm.modules.attention +import sgm.modules.attention +from ldm.models.diffusion.ddpm import LatentDiffusion +from ldm.models.diffusion.ddpm import extract_into_tensor +from sgm.models.diffusion import DiffusionEngine + +from scripts.marking import apply_marking_patch, unmark_prompt_context +from scripts.fabric_utils import image_hash +from scripts.weighted_attention import weighted_attention +from scripts.merging import compute_merge + +try: + import ldm_patched.ldm.modules.attention + has_webui_forge = True + print("[FABRIC] Detected WebUI Forge, running in compatibility mode.") +except ImportError: + has_webui_forge = False + + +SD15 = "sd15" +SDXL = "sdxl" + + +def encode_to_latent(p, image, w, h): + image = images.resize_image(1, image, w, h) + x = functional.pil_to_tensor(image) + x = functional.center_crop(x, (w, h)) # just to be safe + x = x.to(devices.device, dtype=devices.dtype_vae) + x = ((x / 255.0) * 2.0 - 1.0).unsqueeze(0) + + # TODO: use caching to make this faster + with devices.autocast(): + vae_output = p.sd_model.encode_first_stage(x) + z = p.sd_model.get_first_stage_encoding(vae_output) + if torch.isnan(z).any(): + print(f"[FABRIC] NaNs in VAE output found, retrying with 32-bit precision. To always start with 32-bit VAE, use --no-half-vae commandline flag.") + devices.dtype_vae = torch.float32 + x = x.to(devices.dtype_vae) + p.sd_model.first_stage_model.to(devices.dtype_vae) + vae_output = p.sd_model.encode_first_stage(x) + z = p.sd_model.get_first_stage_encoding(vae_output) + z = z.to(devices.dtype_unet) + return z.squeeze(0) + +def forward_noise(p, x_0, t, noise=None): + device = x_0.device + if noise is None: + noise = torch.randn_like(x_0) + alpha_bar = p.sd_model.alphas_cumprod.to(device) + sqrt_alpha_bar_t = extract_into_tensor(alpha_bar.sqrt(), t, x_0.shape) + sqrt_one_minus_alpha_bar_t = extract_into_tensor((1.0 - alpha_bar).sqrt(), t, x_0.shape) + x_t = sqrt_alpha_bar_t * x_0 + sqrt_one_minus_alpha_bar_t * noise + return x_t + + +def get_latents_from_params(p, params, width, height): + w, h = (width // 8) * 8, (height // 8) * 8 + w_latent, h_latent = width // 8, height // 8 + + def get_latents(images, cached_latents=None): + # check if latents need to be computed or recomputed (if image size changed e.g. due to high-res fix) + if cached_latents is None: + cached_latents = {} + + latents = [] + for img in images: + img_hash = image_hash(img) + if img_hash not in cached_latents: + cached_latents[img_hash] = encode_to_latent(p, img, w, h) + elif cached_latents[img_hash].shape[-2:] != (w_latent, h_latent): + print(f"[FABRIC] Recomputing latent for image of size {img.size}") + cached_latents[img_hash] = encode_to_latent(p, img, w, h) + latents.append(cached_latents[img_hash]) + return latents, cached_latents + + params.pos_latents, params.pos_latent_cache = get_latents(params.pos_images, params.pos_latent_cache) + params.neg_latents, params.neg_latent_cache = get_latents(params.neg_images, params.neg_latent_cache) + return params.pos_latents, params.neg_latents + + +def get_curr_feedback_weight(p, params, timestep, num_timesteps=1000): + progress = 1 - (timestep / (num_timesteps - 1)) + if progress >= params.start and progress <= params.end: + w = params.max_weight + else: + w = params.min_weight + return max(0, w), max(0, w * params.neg_scale) + + +def patch_unet_forward_pass(p, unet, params): + if not params.pos_images and not params.neg_images: + print("[FABRIC] No feedback images found, aborting patching") + return + + if not hasattr(unet, "_fabric_old_forward"): + unet._fabric_old_forward = unet.forward + + if isinstance(p.sd_model, LatentDiffusion): + sd_version = SD15 + num_timesteps = p.sd_model.num_timesteps + elif isinstance(p.sd_model, DiffusionEngine): + sd_version = SDXL + num_timesteps = len(p.sd_model.alphas_cumprod) + else: + raise ValueError(f"[FABRIC] Unsupported SD model: {type(p.sd_model)}") + + transformer_block_type = tuple( + [ + ldm.modules.attention.BasicTransformerBlock, # SD 1.5 + sgm.modules.attention.BasicTransformerBlock, # SDXL + ] + + ([ldm_patched.ldm.modules.attention.BasicTransformerBlock] if has_webui_forge else []) + ) + + batch_size = p.batch_size + + null_ctx = p.sd_model.get_learned_conditioning([""]) + if isinstance(null_ctx, torch.Tensor): # SD1.5 + null_ctx = null_ctx.to(devices.device, dtype=devices.dtype_unet) + elif isinstance(null_ctx, dict): # SDXL + for key in null_ctx: + if isinstance(null_ctx[key], torch.Tensor): + null_ctx[key] = null_ctx[key].to(devices.device, dtype=devices.dtype_unet) + else: + raise ValueError(f"[FABRIC] Unsupported context type: {type(null_ctx)}") + + width = (p.width // 8) * 8 + height = (p.height // 8) * 8 + + has_hires_fix = isinstance(p, StableDiffusionProcessingTxt2Img) and getattr(p, 'enable_hr', False) + if has_hires_fix: + if p.hr_resize_x == 0 and p.hr_resize_y == 0: + hr_w = int(p.width * p.hr_scale) + hr_h = int(p.height * p.hr_scale) + else: + hr_w, hr_h = p.hr_resize_x, p.hr_resize_y + hr_w = (hr_w // 8) * 8 + hr_h = (hr_h // 8) * 8 + else: + hr_w = width + hr_h = height + + tome_args = { + "enabled": params.tome_enabled, + "sx": 2, "sy": 2, + "use_rand": True, + "generator": None, + "seed": params.tome_seed, + } + + prev_vals = { + "weight_modifier": 1.0, + } + + def new_forward(self, x, timesteps=None, context=None, **kwargs): + _, uncond_ids, cond_ids, context = unmark_prompt_context(context) + has_cond = len(cond_ids) > 0 + has_uncond = len(uncond_ids) > 0 + + h_latent, w_latent = x.shape[-2:] + w, h = 8 * w_latent, 8 * h_latent + if has_hires_fix and w == hr_w and h == hr_h: + if not params.feedback_during_high_res_fix: + print("[FABRIC] Skipping feedback during high-res fix") + return self._fabric_old_forward(x, timesteps, context, **kwargs) + + pos_weight, neg_weight = get_curr_feedback_weight(p, params, timesteps[0].item(), num_timesteps=num_timesteps) + if pos_weight <= 0 and neg_weight <= 0: + return self._fabric_old_forward(x, timesteps, context, **kwargs) + + if params.burnout_protection and "cond" in prev_vals and "uncond" in prev_vals: + # burnout protection: if the difference betwen cond/uncond was too high in the previous step (sign of instability), slash the weight modifier + diff_std = (prev_vals["cond"] - prev_vals["uncond"]).std(dim=(2, 3)).max().item() + diff_abs_mean = (prev_vals["cond"] - prev_vals["uncond"]).mean(dim=(2, 3)).abs().max().item() + if diff_std > 0.06 or diff_abs_mean > 0.02: + prev_vals["weight_modifier"] *= 0.5 + else: + prev_vals["weight_modifier"] = min(1.0, 1.5 * prev_vals["weight_modifier"]) + + pos_weight, neg_weight = pos_weight * prev_vals["weight_modifier"], neg_weight * prev_vals["weight_modifier"] + + pos_latents, neg_latents = get_latents_from_params(p, params, w, h) + pos_latents = pos_latents if has_cond else [] + neg_latents = neg_latents if has_uncond else [] + all_latents = pos_latents + neg_latents + + # Note: calls to the VAE with `--medvram` will move the U-Net to CPU, so we need to move it back to GPU + if shared.cmd_opts.medvram: + try: + # Trigger register_forward_pre_hook to move the model to correct device + p.sd_model.model() + except: + pass + + if len(all_latents) == 0: + return self._fabric_old_forward(x, timesteps, context, **kwargs) + + # add noise to reference latents + xs_0 = torch.stack(all_latents, dim=0) + ts = timesteps[0, None].expand(xs_0.size(0)) # (bs,) + all_zs = forward_noise(p, xs_0, torch.round(ts.float()).long()) + + # save original forward pass + for module in self.modules(): + if isinstance(module, transformer_block_type) and not hasattr(module.attn1, "_fabric_old_forward"): + module.attn1._fabric_old_forward = module.attn1.forward + module.attn2._fabric_old_forward = module.attn2.forward + + try: + ## cache hidden states + cached_hiddens = {} + def patched_attn1_forward(attn1, layer_idx, x, **kwargs): + merge, unmerge = compute_merge(x, args=tome_args, size=(h_latent, w_latent), ratio=params.tome_ratio) + x = merge(x) + if layer_idx not in cached_hiddens: + cached_hiddens[layer_idx] = x.detach().clone().cpu() + else: + cached_hiddens[layer_idx] = torch.cat([cached_hiddens[layer_idx], x.detach().clone().cpu()], dim=0) + out = attn1._fabric_old_forward(x, **kwargs) + out = unmerge(out) + return out + + def patched_attn2_forward(attn2, x, **kwargs): + merge, unmerge = compute_merge(x, args=tome_args, size=(h_latent, w_latent), ratio=params.tome_ratio) + x = merge(x) + out = attn2._fabric_old_forward(x, **kwargs) + out = unmerge(out) + return out + + # patch forward pass to cache hidden states + layer_idx = 0 + for module in self.modules(): + if isinstance(module, transformer_block_type): + module.attn1.forward = functools.partial(patched_attn1_forward, module.attn1, layer_idx) + module.attn2.forward = functools.partial(patched_attn2_forward, module.attn2) + layer_idx += 1 + + # run forward pass just to cache hidden states, output is discarded + for i in range(0, len(all_zs), batch_size): + zs = all_zs[i : i + batch_size].to(x.device, dtype=self.dtype) + ts = timesteps[:1].expand(zs.size(0)) # (bs,) + # use the null prompt for pre-computing hidden states on feedback images + ctx_args = {} + if sd_version == SD15: + ctx_args["context"] = null_ctx.expand(zs.size(0), -1, -1) # (bs, seq_len, d_model) + else: # SDXL + ctx_args["context"] = null_ctx["crossattn"].expand(zs.size(0), -1, -1) # (bs, seq_len, d_model) + ctx_args["y"] = null_ctx["vector"].expand(zs.size(0), -1) # (bs, d_vector) + _ = self._fabric_old_forward(zs, ts, **ctx_args) + + num_pos = len(pos_latents) + num_neg = len(neg_latents) + num_cond = len(cond_ids) + num_uncond = len(uncond_ids) + tome_h_latent = h_latent * (1 - params.tome_ratio) + + def patched_attn1_forward(attn1, idx, x, context=None, **kwargs): + if context is None: + context = x + + cached_hs = cached_hiddens[idx].to(x.device) + + d_model = x.shape[-1] + + def attention_with_feedback(_x, context, feedback_hs, w): + num_xs, num_fb = _x.shape[0], feedback_hs.shape[0] + if num_fb > 0: + feedback_ctx = feedback_hs.view(1, -1, d_model).expand(num_xs, -1, -1) # (n_cond, seq * n_pos, dim) + merge, _ = compute_merge(feedback_ctx, args=tome_args, size=(tome_h_latent * num_fb, w_latent), max_tokens=params.tome_max_tokens) + feedback_ctx = merge(feedback_ctx) + ctx = torch.cat([context, feedback_ctx], dim=1) # (n_cond, seq + seq*n_pos, dim) + weights = torch.ones(ctx.shape[1], device=ctx.device, dtype=ctx.dtype) # (seq + seq*n_pos,) + weights[_x.shape[1]:] = w + else: + ctx = context + weights = None + return weighted_attention(attn1, attn1._fabric_old_forward, _x, ctx, weights, **kwargs) # (n_cond, seq, dim) + + out = torch.zeros_like(x, dtype=devices.dtype_unet) + if num_cond > 0: + out_cond = attention_with_feedback(x[cond_ids], context[cond_ids], cached_hs[:num_pos], pos_weight) # (n_cond, seq, dim) + out[cond_ids] = out_cond + if num_uncond > 0: + out_uncond = attention_with_feedback(x[uncond_ids], context[uncond_ids], cached_hs[num_pos:], neg_weight) # (n_cond, seq, dim) + out[uncond_ids] = out_uncond + return out + + # patch forward pass to inject cached hidden states + layer_idx = 0 + for module in self.modules(): + if isinstance(module, transformer_block_type): + module.attn1.forward = functools.partial(patched_attn1_forward, module.attn1, layer_idx) + layer_idx += 1 + + # run forward pass with cached hidden states + out = self._fabric_old_forward(x, timesteps, context, **kwargs) + + cond_outs = out[cond_ids] + uncond_outs = out[uncond_ids] + + if has_cond: + prev_vals["cond"] = cond_outs.detach().clone() + if has_uncond: + prev_vals["uncond"] = uncond_outs.detach().clone() + + if params.burnout_protection: + # burnout protection: recenter the output to prevent instabilities caused by mean drift + mean = out.mean(dim=(2, 3), keepdim=True) + out = out - 0.7 * mean + finally: + # restore original pass + for module in self.modules(): + if isinstance(module, transformer_block_type) and hasattr(module.attn1, "_fabric_old_forward"): + module.attn1.forward = module.attn1._fabric_old_forward + del module.attn1._fabric_old_forward + if isinstance(module, transformer_block_type) and hasattr(module.attn2, "_fabric_old_forward"): + module.attn2.forward = module.attn2._fabric_old_forward + del module.attn2._fabric_old_forward + + return out + + unet.forward = new_forward.__get__(unet) + + apply_marking_patch(p) + +def unpatch_unet_forward_pass(unet): + if hasattr(unet, "_fabric_old_forward"): + print("[FABRIC] Restoring original U-Net forward pass") + unet.forward = unet._fabric_old_forward + del unet._fabric_old_forward diff --git a/extensions/CHECK/sd-webui-fabric/scripts/weighted_attention.py b/extensions/CHECK/sd-webui-fabric/scripts/weighted_attention.py new file mode 100644 index 0000000000000000000000000000000000000000..08fd21545c3a2829c4152503f86b26131b1b5a11 --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/scripts/weighted_attention.py @@ -0,0 +1,359 @@ +import math +import psutil + +import torch +import torch.nn.functional +from torch import einsum +from einops import rearrange + +from ldm.util import default + +from modules import shared, devices, sd_hijack +from modules.hypernetworks import hypernetwork +from modules.sd_hijack_optimizations import ( + get_xformers_flash_attention_op, + get_available_vram, +) + +try: + import xformers + import xformers.ops +except ImportError: + pass + +try: + from ldm_patched.modules import model_management + has_webui_forge = True + print("[FABRIC] Detected WebUI Forge, running in compatibility mode.") +except ImportError: + has_webui_forge = False + + +def get_weighted_attn_fn(): + if has_webui_forge: + if model_management.xformers_enabled(): + return weighted_xformers_attention_forward + elif model_management.pytorch_attention_enabled(): + return weighted_scaled_dot_product_attention_forward + else: + print(f"[FABRIC] Warning: No attention method enabled. Falling back to split attention.") + return weighted_split_cross_attention_forward + + method = sd_hijack.model_hijack.optimization_method + if method is None: + return weighted_split_cross_attention_forward + method = method.lower() + + if method not in ['none', 'sdp-no-mem', 'sdp', 'xformers', 'sub-quadratic', 'v1', 'invokeai', 'doggettx']: + print(f"[FABRIC] Warning: Unknown attention optimization method {method}.") + return weighted_split_cross_attention_forward + + if method == 'none': + return weighted_split_cross_attention_forward + elif method == 'xformers': + return weighted_xformers_attention_forward + elif method == 'sdp-no-mem': + return weighted_scaled_dot_product_no_mem_attention_forward + elif method == 'sdp': + return weighted_scaled_dot_product_attention_forward + elif method == 'doggettx': + return weighted_split_cross_attention_forward + elif method == 'invokeai': + return weighted_split_cross_attention_forward_invokeAI + elif method == 'sub-quadratic': + print(f"[FABRIC] Warning: Sub-quadratic attention is not supported yet. Please open an issue if you need this for your workflow. Falling back to split attention.") + return weighted_split_cross_attention_forward + elif method == 'v1': + print(f"[FABRIC] Warning: V1 attention is not supported yet. Please open an issue if you need this for your workflow. Falling back to split attention.") + return weighted_split_cross_attention_forward + else: + return weighted_split_cross_attention_forward + + +def weighted_attention(self, attn_fn, x, context=None, weights=None, **kwargs): + if weights is None: + return attn_fn(x, context=context, **kwargs) + + weighted_attn_fn = get_weighted_attn_fn() + return weighted_attn_fn(self, x, context=context, weights=weights, mask=kwargs.get('mask', None)) + + +def _get_attn_bias(weights, shape=None, dtype=torch.float32): + # shape of weights needs to be divisible by 8 in order for xformers attn bias to work + last_dim = ((weights.shape[-1] - 1) // 8 + 1) * 8 + w_bias = torch.zeros(weights.shape[:-1] + (last_dim,), device=weights.device, dtype=dtype) + + min_val = torch.finfo(dtype).min + w_bias[..., :weights.shape[-1]] = weights.log().to(dtype=dtype).clamp(min=min_val) + + if shape is not None: + assert shape[-1] == weights.shape[-1], "Last dimension of shape must match last dimension of weights (number of keys)" + w_bias = w_bias.view([1] * (len(shape) - 1) + [-1]).expand(shape[:-1] + (last_dim,)) + # make sure not to consolidate the tensor after expanding, + # as it will lead to a stride overflow for large numbers of feedback images + + # slice in order to preserve multiple-of-8 stride + w_bias = w_bias[..., :weights.shape[-1]] + return w_bias + +### The following attn functions are copied and adapted from modules.sd_hijack_optimizations + +# --- InvokeAI --- +mem_total_gb = psutil.virtual_memory().total // (1 << 30) + +def einsum_op_compvis(q, k, v, weights=None): + s = einsum('b i d, b j d -> b i j', q, k) + if weights is not None: + s += _get_attn_bias(weights, s.shape, s.dtype) + s = s.softmax(dim=-1, dtype=s.dtype) + return einsum('b i j, b j d -> b i d', s, v) + +def einsum_op_slice_0(q, k, v, slice_size, weights=None): + r = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype) + for i in range(0, q.shape[0], slice_size): + end = i + slice_size + r[i:end] = einsum_op_compvis(q[i:end], k[i:end], v[i:end], weights) + return r + +def einsum_op_slice_1(q, k, v, slice_size, weights=None): + r = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype) + for i in range(0, q.shape[1], slice_size): + end = i + slice_size + r[:, i:end] = einsum_op_compvis(q[:, i:end], k, v, weights) + return r + +def einsum_op_mps_v1(q, k, v, weights=None): + if q.shape[0] * q.shape[1] <= 2**16: # (512x512) max q.shape[1]: 4096 + return einsum_op_compvis(q, k, v, weights) + else: + slice_size = math.floor(2**30 / (q.shape[0] * q.shape[1])) + if slice_size % 4096 == 0: + slice_size -= 1 + return einsum_op_slice_1(q, k, v, slice_size, weights) + +def einsum_op_mps_v2(q, k, v, weights=None): + if mem_total_gb > 8 and q.shape[0] * q.shape[1] <= 2**16: + return einsum_op_compvis(q, k, v, weights) + else: + return einsum_op_slice_0(q, k, v, 1, weights) + +def einsum_op_tensor_mem(q, k, v, max_tensor_mb, weights=None): + size_mb = q.shape[0] * q.shape[1] * k.shape[1] * q.element_size() // (1 << 20) + if size_mb <= max_tensor_mb: + return einsum_op_compvis(q, k, v, weights) + div = 1 << int((size_mb - 1) / max_tensor_mb).bit_length() + if div <= q.shape[0]: + return einsum_op_slice_0(q, k, v, q.shape[0] // div, weights) + return einsum_op_slice_1(q, k, v, max(q.shape[1] // div, 1), weights) + +def einsum_op_cuda(q, k, v, weights=None): + stats = torch.cuda.memory_stats(q.device) + mem_active = stats['active_bytes.all.current'] + mem_reserved = stats['reserved_bytes.all.current'] + mem_free_cuda, _ = torch.cuda.mem_get_info(q.device) + mem_free_torch = mem_reserved - mem_active + mem_free_total = mem_free_cuda + mem_free_torch + # Divide factor of safety as there's copying and fragmentation + return einsum_op_tensor_mem(q, k, v, mem_free_total / 3.3 / (1 << 20), weights) + +def einsum_op(q, k, v, weights=None): + if q.device.type == 'cuda': + return einsum_op_cuda(q, k, v, weights) + + if q.device.type == 'mps': + if mem_total_gb >= 32 and q.shape[0] % 32 != 0 and q.shape[0] * q.shape[1] < 2**18: + return einsum_op_mps_v1(q, k, v, weights) + return einsum_op_mps_v2(q, k, v, weights) + + # Smaller slices are faster due to L2/L3/SLC caches. + # Tested on i7 with 8MB L3 cache. + return einsum_op_tensor_mem(q, k, v, 32, weights) + +def weighted_split_cross_attention_forward_invokeAI(self, x, context=None, mask=None, weights=None): + h = self.heads + + q = self.to_q(x) + context = default(context, x) + + context_k, context_v = hypernetwork.apply_hypernetworks(shared.loaded_hypernetworks, context) + k = self.to_k(context_k) + v = self.to_v(context_v) + del context, context_k, context_v, x + + dtype = q.dtype + if shared.opts.upcast_attn: + q, k, v = q.float(), k.float(), v if v.device.type == 'mps' else v.float() + + with devices.without_autocast(disable=not shared.opts.upcast_attn): + k = k * self.scale + + q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q, k, v)) + r = einsum_op(q, k, v, weights) + r = r.to(dtype) + return self.to_out(rearrange(r, '(b h) n d -> b n (h d)', h=h)) +# --- end InvokeAI --- + + +def weighted_xformers_attention_forward(self, x, context=None, mask=None, weights=None): + + h = self.heads + q_in = self.to_q(x) + context = default(context, x) + + context_k, context_v = hypernetwork.apply_hypernetworks(shared.loaded_hypernetworks, context) + k_in = self.to_k(context_k) + v_in = self.to_v(context_v) + + q, k, v = (rearrange(t, 'b n (h d) -> b n h d', h=h) for t in (q_in, k_in, v_in)) + del q_in, k_in, v_in + + dtype = q.dtype + if shared.opts.upcast_attn: + q, k, v = q.float(), k.float(), v.float() + + ### FABRIC ### + bias_shape = (q.size(0), q.size(2), q.size(1), k.size(1)) # (bs, h, nq, nk) + if weights is not None: + attn_bias = _get_attn_bias(weights, bias_shape, dtype=q.dtype) + else: + attn_bias = None + ### END FABRIC ### + + out = xformers.ops.memory_efficient_attention(q, k, v, attn_bias=attn_bias, op=get_xformers_flash_attention_op(q, k, v)) + + out = out.to(dtype) + + out = rearrange(out, 'b n h d -> b n (h d)', h=h) + return self.to_out(out) + + +def weighted_scaled_dot_product_attention_forward(self, x, context=None, mask=None, weights=None): + batch_size, sequence_length, inner_dim = x.shape + + if mask is not None: + mask = self.prepare_attention_mask(mask, sequence_length, batch_size) + mask = mask.view(batch_size, self.heads, -1, mask.shape[-1]) + + h = self.heads + q_in = self.to_q(x) + context = default(context, x) + + context_k, context_v = hypernetwork.apply_hypernetworks(shared.loaded_hypernetworks, context) + k_in = self.to_k(context_k) + v_in = self.to_v(context_v) + + head_dim = inner_dim // h + q = q_in.view(batch_size, -1, h, head_dim).transpose(1, 2) + k = k_in.view(batch_size, -1, h, head_dim).transpose(1, 2) + v = v_in.view(batch_size, -1, h, head_dim).transpose(1, 2) + + del q_in, k_in, v_in + + dtype = q.dtype + if shared.opts.upcast_attn: + q, k, v = q.float(), k.float(), v.float() + + ### FABRIC ### + mask_shape = q.shape[:3] + (k.shape[2],) # (bs, h, nq, nk) + if mask is None: + mask = 0 + else: + mask.masked_fill(not mask, -float('inf')) if mask.dtype==torch.bool else mask + mask = mask.to(dtype=q.dtype) + if weights is not None: + w_bias = _get_attn_bias(weights, mask_shape, dtype=q.dtype) + mask += w_bias + ### END FABRIC ### + + # the output of sdp = (batch, num_heads, seq_len, head_dim) + hidden_states = torch.nn.functional.scaled_dot_product_attention( + q, k, v, attn_mask=mask, dropout_p=0.0, is_causal=False + ) + + hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, h * head_dim) + hidden_states = hidden_states.to(dtype) + + # linear proj + hidden_states = self.to_out[0](hidden_states) + # dropout + hidden_states = self.to_out[1](hidden_states) + return hidden_states + +def weighted_scaled_dot_product_no_mem_attention_forward(self, x, context=None, mask=None, weights=None): + with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=True, enable_mem_efficient=False): + return weighted_scaled_dot_product_attention_forward(self, x, context, mask, weights) + + +def weighted_split_cross_attention_forward(self, x, context=None, mask=None, weights=None): + h = self.heads + + q_in = self.to_q(x) + context = default(context, x) + + context_k, context_v = hypernetwork.apply_hypernetworks(shared.loaded_hypernetworks, context) + k_in = self.to_k(context_k) + v_in = self.to_v(context_v) + + dtype = q_in.dtype + if shared.opts.upcast_attn: + q_in, k_in, v_in = q_in.float(), k_in.float(), v_in if v_in.device.type == 'mps' else v_in.float() + + with devices.without_autocast(disable=not shared.opts.upcast_attn): + default_scale = (q_in.shape[-1] / h) ** -0.5 + k_in = k_in * getattr(self, "scale", default_scale) + + del context, x + + q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q_in, k_in, v_in)) + del q_in, k_in, v_in + + r1 = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype) + + mem_free_total = get_available_vram() + + gb = 1024 ** 3 + tensor_size = q.shape[0] * q.shape[1] * k.shape[1] * q.element_size() + modifier = 3 if q.element_size() == 2 else 2.5 + mem_required = tensor_size * modifier + + # FABRIC incurs some batch-size-dependend overhead. Found empirically on RTX 3090. + bs = q.shape[0] / 8 # batch size + mem_required *= 1/(bs + 1) + 1.25 + mem_required *= 1.05 # safety margin + steps = 1 + + if mem_required > mem_free_total: + steps = 2 ** (math.ceil(math.log(mem_required / mem_free_total, 2))) + # print(f"Expected tensor size:{tensor_size/gb:0.1f}GB, cuda free:{mem_free_cuda/gb:0.1f}GB " + # f"torch free:{mem_free_torch/gb:0.1f} total:{mem_free_total/gb:0.1f} steps:{steps}") + + if steps > 64: + max_res = math.floor(math.sqrt(math.sqrt(mem_free_total / 2.5)) / 8) * 64 + raise RuntimeError(f'Not enough memory, use lower resolution (max approx. {max_res}x{max_res}). ' + f'Need: {mem_required / 64 / gb:0.1f}GB free, Have:{mem_free_total / gb:0.1f}GB free') + + slice_size = q.shape[1] // steps if (q.shape[1] % steps) == 0 else q.shape[1] + for i in range(0, q.shape[1], slice_size): + end = i + slice_size + s1 = einsum('b i d, b j d -> b i j', q[:, i:end], k) + + # OURS: apply weights to attention + if weights is not None: + bias = weights.to(s1.dtype).log().clamp(min=torch.finfo(s1.dtype).min) + s1 = s1 + bias + del bias + + s2 = s1.softmax(dim=-1, dtype=q.dtype) + del s1 + + r1[:, i:end] = einsum('b i j, b j d -> b i d', s2, v) + del s2 + + del q, k, v + + r1 = r1.to(dtype) + + r2 = rearrange(r1, '(b h) n d -> b n (h d)', h=h) + del r1 + + return self.to_out(r2) diff --git a/extensions/CHECK/sd-webui-fabric/setup.py b/extensions/CHECK/sd-webui-fabric/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/extensions/CHECK/sd-webui-fabric/static/example_1_after.png b/extensions/CHECK/sd-webui-fabric/static/example_1_after.png new file mode 100644 index 0000000000000000000000000000000000000000..3504a50b331a6a9f3a3e79ca9681e42499b08a0d Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/static/example_1_after.png differ diff --git a/extensions/CHECK/sd-webui-fabric/static/example_1_before.png b/extensions/CHECK/sd-webui-fabric/static/example_1_before.png new file mode 100644 index 0000000000000000000000000000000000000000..6404accb621b7eb8e1098bd6b6ca467208b6b7c9 Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/static/example_1_before.png differ diff --git a/extensions/CHECK/sd-webui-fabric/static/example_1_feedback.png b/extensions/CHECK/sd-webui-fabric/static/example_1_feedback.png new file mode 100644 index 0000000000000000000000000000000000000000..2f8c83a4365d55e533112546dd57eaffc47472b6 Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/static/example_1_feedback.png differ diff --git a/extensions/CHECK/sd-webui-fabric/static/example_2_baseline.png b/extensions/CHECK/sd-webui-fabric/static/example_2_baseline.png new file mode 100644 index 0000000000000000000000000000000000000000..5ebede71458439542ae1ce69471138ab0479aeaa Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/static/example_2_baseline.png differ diff --git a/extensions/CHECK/sd-webui-fabric/static/example_2_custom.png b/extensions/CHECK/sd-webui-fabric/static/example_2_custom.png new file mode 100644 index 0000000000000000000000000000000000000000..44f43c11125ce0a859c92e3f4da3cc11b300b675 Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/static/example_2_custom.png differ diff --git a/extensions/CHECK/sd-webui-fabric/static/example_2_default.png b/extensions/CHECK/sd-webui-fabric/static/example_2_default.png new file mode 100644 index 0000000000000000000000000000000000000000..b67f63a4af3c812772fef8c7bbd1776f6cb3b2cf --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/static/example_2_default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0de1e1bb2cb9d865922875b39a405b2305247cbf4fbe3e7d93250af416352360 +size 1109568 diff --git a/extensions/CHECK/sd-webui-fabric/static/example_2_feedback.png b/extensions/CHECK/sd-webui-fabric/static/example_2_feedback.png new file mode 100644 index 0000000000000000000000000000000000000000..9c10755929eeffdc4e50f9604a78537b7337b8d7 --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/static/example_2_feedback.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7df03500faae3c20ce1d173ba705dc0abaf5b4d0fb99258ff7339d6225478326 +size 1198485 diff --git a/extensions/CHECK/sd-webui-fabric/static/example_3_00.png b/extensions/CHECK/sd-webui-fabric/static/example_3_00.png new file mode 100644 index 0000000000000000000000000000000000000000..e78a7be48d12d5bc0f2c2dce42e0424b5b16e1e0 Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/static/example_3_00.png differ diff --git a/extensions/CHECK/sd-webui-fabric/static/example_3_02.png b/extensions/CHECK/sd-webui-fabric/static/example_3_02.png new file mode 100644 index 0000000000000000000000000000000000000000..41e3fdc6ef788751d90b8f4182133d34086b99be Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/static/example_3_02.png differ diff --git a/extensions/CHECK/sd-webui-fabric/static/example_3_04.png b/extensions/CHECK/sd-webui-fabric/static/example_3_04.png new file mode 100644 index 0000000000000000000000000000000000000000..7f43542c8644f75dde0c277b9b3a7b2cababdc0e Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/static/example_3_04.png differ diff --git a/extensions/CHECK/sd-webui-fabric/static/example_3_06.png b/extensions/CHECK/sd-webui-fabric/static/example_3_06.png new file mode 100644 index 0000000000000000000000000000000000000000..369f9cef9d02fb8688c005555d8b921c948a1664 Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/static/example_3_06.png differ diff --git a/extensions/CHECK/sd-webui-fabric/static/example_3_08.png b/extensions/CHECK/sd-webui-fabric/static/example_3_08.png new file mode 100644 index 0000000000000000000000000000000000000000..72a6d910142a4bb9962cd83797bfbf1ac2fea949 Binary files /dev/null and b/extensions/CHECK/sd-webui-fabric/static/example_3_08.png differ diff --git a/extensions/CHECK/sd-webui-fabric/static/example_3_10.png b/extensions/CHECK/sd-webui-fabric/static/example_3_10.png new file mode 100644 index 0000000000000000000000000000000000000000..58496d4b639e64acc77f27bfc5b614cc30fab7e3 --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/static/example_3_10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bd5b95785d31f04eeb0d47997dcfc18b66ad841ef9d0215916e7feeedb8a2b9 +size 1008360 diff --git a/extensions/CHECK/sd-webui-fabric/static/example_3_feedback.png b/extensions/CHECK/sd-webui-fabric/static/example_3_feedback.png new file mode 100644 index 0000000000000000000000000000000000000000..e393f19bd331be697c06e8969eaabd9f766dae64 --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/static/example_3_feedback.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11dc3ec5619bdb2d052b5985db800c6f67432eee4fcf4f4359f4e41bdd6cf34d +size 1023778 diff --git a/extensions/CHECK/sd-webui-fabric/static/fabric_demo.gif b/extensions/CHECK/sd-webui-fabric/static/fabric_demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..76e01c711b0b92970ab21fb20eba4f0a809e7c71 --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/static/fabric_demo.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17c2be3cc307c0393f85c533773cbd4dc2731fea9eeba12428cf1738627e31be +size 1532019 diff --git a/extensions/CHECK/sd-webui-fabric/style.css b/extensions/CHECK/sd-webui-fabric/style.css new file mode 100644 index 0000000000000000000000000000000000000000..726c975d4e753a81de201649a53985327a19d3cf --- /dev/null +++ b/extensions/CHECK/sd-webui-fabric/style.css @@ -0,0 +1,8 @@ + +#fabric_like_gallery img, #fabric_dislike_gallery img { + object-fit: scale-down; +} + +#fabric { + --layout-gap: 0.75rem; +} \ No newline at end of file diff --git a/extensions/CHECK/sd-webui-llul/.gitignore b/extensions/CHECK/sd-webui-llul/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..9f94e5dd988b2944cb27eeaa23ca1cf68e869d8c --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/.gitignore @@ -0,0 +1,2 @@ +.vscode +__pycache__ diff --git a/extensions/CHECK/sd-webui-llul/LICENSE b/extensions/CHECK/sd-webui-llul/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d1e1072ee5e1d109c15b6fd18756aedc2a401840 --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/LICENSE @@ -0,0 +1 @@ +MIT License diff --git a/extensions/CHECK/sd-webui-llul/README.md b/extensions/CHECK/sd-webui-llul/README.md new file mode 100644 index 0000000000000000000000000000000000000000..165698dcdb2b2ced0ed8ebef3f49e84823f99996 --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/README.md @@ -0,0 +1,43 @@ +# LLuL - Local Latent upscaLer + +![cover](./images/cover.jpg) + +https://user-images.githubusercontent.com/120772120/221390831-9fbccdf8-5898-4515-b988-d6733e8af3f1.mp4 + +## What is this? + +This is an extension for [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) which lets you to upscale latents locally. + +See above image. This is all what this extension does. + +## Usage + +1. Select `Enabled` checkbox. +2. Move gray box where you want to apply upscaling. +3. Generate image. + +## Examples + +![sample 2](./images/sample1.jpg) + +![sample 3](./images/sample2.jpg) + +![sample 4](./images/sample3.jpg) + +![sample 5](./images/sample4.jpg) + +- Weight 0.00 -> 0.20 Animation + +https://user-images.githubusercontent.com/120772120/221390834-7e2c1a1a-d7a6-46b0-8949-83c4d5839c33.mp4 + +## Mask + +The mask is now available. + +Each pixel values of the mask are treated as the scale factors of the interpolation weight. White is 1.0 (enabled), black is 0.0 (disabled) and gray reduces the weights. + +![mask sample](./images/mask_effect.jpg) + +## How it works + +![description of process](./images/desc.png) diff --git a/extensions/CHECK/sd-webui-llul/images/cover.jpg b/extensions/CHECK/sd-webui-llul/images/cover.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3e13d93d6984aa153412ff2e8bbfdd6326893726 Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/images/cover.jpg differ diff --git a/extensions/CHECK/sd-webui-llul/images/cover.mp4 b/extensions/CHECK/sd-webui-llul/images/cover.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..39fec8b725874eb3275935316c9dda7e469b41cc Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/images/cover.mp4 differ diff --git a/extensions/CHECK/sd-webui-llul/images/cover_yuv420p.mp4 b/extensions/CHECK/sd-webui-llul/images/cover_yuv420p.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..43f5f7718f90e4e55f7f5cd245e80610405912bd Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/images/cover_yuv420p.mp4 differ diff --git a/extensions/CHECK/sd-webui-llul/images/desc.png b/extensions/CHECK/sd-webui-llul/images/desc.png new file mode 100644 index 0000000000000000000000000000000000000000..c827387deef8067d76629f380018b4cccc3b2fc3 Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/images/desc.png differ diff --git a/extensions/CHECK/sd-webui-llul/images/llul.mp4 b/extensions/CHECK/sd-webui-llul/images/llul.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..b344c4a94dd832886ec4de44738343059227ad88 Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/images/llul.mp4 differ diff --git a/extensions/CHECK/sd-webui-llul/images/llul_yuv420p.mp4 b/extensions/CHECK/sd-webui-llul/images/llul_yuv420p.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..30893a1dc0676b83572946f4cd711f533908a20f --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/images/llul_yuv420p.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69b7ac823e0f0e5759886137708ab7cc62521baa2d9917f5c9a551fded46ac5e +size 1341189 diff --git a/extensions/CHECK/sd-webui-llul/images/mask_effect.jpg b/extensions/CHECK/sd-webui-llul/images/mask_effect.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9d7232768bebc576d77524be16632d611e830680 --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/images/mask_effect.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e4b6d4218a38fadeb7d2fa3502f247804b3dc2ffb344e6d9506f2107aa62c5c +size 1098988 diff --git a/extensions/CHECK/sd-webui-llul/images/sample1.jpg b/extensions/CHECK/sd-webui-llul/images/sample1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bcfab5b07db02f77c8bb660c6b3bc40a8cd89eba Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/images/sample1.jpg differ diff --git a/extensions/CHECK/sd-webui-llul/images/sample2.jpg b/extensions/CHECK/sd-webui-llul/images/sample2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..34fed788497190604fdb28cd9ce16af5b2af0a41 Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/images/sample2.jpg differ diff --git a/extensions/CHECK/sd-webui-llul/images/sample3.jpg b/extensions/CHECK/sd-webui-llul/images/sample3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3b7b173006ec6336d623adf01e3c2c4472ca4a21 Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/images/sample3.jpg differ diff --git a/extensions/CHECK/sd-webui-llul/images/sample4.jpg b/extensions/CHECK/sd-webui-llul/images/sample4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..60657fc1bfb160c578d8a5ce629940324e31b638 Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/images/sample4.jpg differ diff --git a/extensions/CHECK/sd-webui-llul/javascript/llul.js b/extensions/CHECK/sd-webui-llul/javascript/llul.js new file mode 100644 index 0000000000000000000000000000000000000000..9ba48f7213a690787bc4e1b9e376cfaaccb916bd --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/javascript/llul.js @@ -0,0 +1,269 @@ +(function () { + + if (!globalThis.LLuL) globalThis.LLuL = {}; + const LLuL = globalThis.LLuL; + + function id(type, s) { + return `llul-${type}-${s}`; + } + + function isDark() { + return gradioApp().querySelector('.dark') !== null; + } + + const M = 2; + function setSize(canvas, width, height) { + width = Math.floor(+width / M); + height = Math.floor(+height / M); + if (canvas.width != width) canvas.width = width; + if (canvas.height != height) canvas.height = height; + } + + function updateXY(canvas) { + let x = +canvas.dataset.x, + y = +canvas.dataset.y, + m = +canvas.dataset.m, + mm = Math.pow(2, m), + w = +canvas.width * M, + h = +canvas.height * M; + if (x < 0) x = 0; + if (w < x + w / mm) x = Math.floor(w - w / mm); + if (y < 0) y = 0; + if (h < y + h / mm) y = Math.floor(h - h / mm); + + canvas.dataset.x = x; + canvas.dataset.y = y; + canvas.dataset.m = m; + + canvas.parentNode.querySelector('.llul-pos-x').value = x; + canvas.parentNode.querySelector('.llul-pos-y').value = y; + } + + let last_image = new Image(); + let hide_image = true; + async function draw(canvas) { + const + x = +canvas.dataset.x, + y = +canvas.dataset.y, + m = +canvas.dataset.m, + mm = Math.pow(2, m), + w = +canvas.width, + h = +canvas.height, + bg = canvas.dataset.bg; + + const ctx = canvas.getContext('2d'); + + if (bg) { + if (last_image?.src === bg) { + // do nothing + } else { + await (new Promise(resolve => { + last_image.onload = () => resolve(); + last_image.src = bg; + })); + } + hide_image = false; + } else { + last_image.src = ''; + hide_image = true; + } + + if (last_image.src && !hide_image) { + ctx.drawImage(last_image, 0, 0, +last_image.width, +last_image.height, 0, 0, +canvas.width, +canvas.height); + } else { + const bgcolor = isDark() ? 'black' : 'white'; + ctx.fillStyle = bgcolor; + ctx.fillRect(0, 0, +canvas.width, +canvas.height); + } + + ctx.fillStyle = 'gray'; + ctx.fillRect(x / M, y / M, Math.floor(w / mm), Math.floor(h / mm)); + } + + async function update_gradio(type, canvas) { + await LLuL.js2py(type, 'x', +canvas.dataset.x); + await LLuL.js2py(type, 'y', +canvas.dataset.y); + } + + function init(type) { + const $$ = (x,n) => Array.from(gradioApp().querySelectorAll(x)).at(n); + const $ = x => $$(x, -1); + + if (!$('#' + id(type, 'accordion'))) return false; + + const cont = $('#' + id(type, 'container')); + const x = $('#' + id(type, 'x')); + const y = $('#' + id(type, 'y')); + const m = $(`#${id(type, 'm')} input[type=number]`); + const ms = $(`#${id(type, 'm')} input[type=range]`); + if (!cont || !x || !y || !m || !ms) return false; + + if (cont.querySelector('canvas')) return true; // already called + + const width = $$(`#${type}_width input[type=number]`, 0); + const height = $$(`#${type}_height input[type=number]`, 0); + const width2 = $$(`#${type}_width input[type=range]`, 0); + const height2 = $$(`#${type}_height input[type=range]`, 0); + + const pos_x = Math.floor(+width.value / 4); + const pos_y = Math.floor(+height.value / 4); + + const pos_cont = document.createElement('div'); + pos_cont.innerHTML = ` +
+ + +
+ `; + + const canvas = document.createElement('canvas'); + canvas.style.border = '1px solid gray'; + canvas.dataset.x = pos_x; + canvas.dataset.y = pos_y; + canvas.dataset.m = m.value; + + const bg_cont = document.createElement('div'); + bg_cont.classList.add('llul-bg-setting'); + bg_cont.innerHTML = ` +Load BG +Erase BG + + `; + + for (let ele of [width, height, width2, height2, m, ms]) { + ele.addEventListener('input', e => { + canvas.dataset.m = +m.value; + setSize(canvas, width.value, height.value); + updateXY(canvas); + draw(canvas); + }); + } + + // + // Event Listeners + // + + // canvas + + let dragging = false; + let last_x, last_y; + canvas.addEventListener('pointerdown', e => { + e.preventDefault(); + dragging = true; + last_x = e.offsetX; + last_y = e.offsetY; + }); + canvas.addEventListener('pointerup', async e => { + e.preventDefault(); + dragging = false; + await update_gradio(type, canvas); + }); + canvas.addEventListener('pointermove', e => { + if (!dragging) return; + const dx = e.offsetX - last_x, dy = e.offsetY - last_y; + const x = +canvas.dataset.x, y = +canvas.dataset.y; + canvas.dataset.x = x + dx * M; + canvas.dataset.y = y + dy * M; + last_x = e.offsetX; + last_y = e.offsetY; + updateXY(canvas); + draw(canvas); + }); + + // bg_cont + + function set_bg(url) { + canvas.dataset.bg = url; + draw(canvas); + } + bg_cont.querySelector('input[type=file]').addEventListener('change', e => { + const ele = e.target; + const files = ele.files; + if (files.length != 0) { + const file = files[0]; + const r = new FileReader(); + r.onload = () => set_bg(r.result); + r.readAsDataURL(file); + } + ele.value = ''; + }, false); + bg_cont.addEventListener('click', e => { + const ele = e.target; + if (ele.textContent == 'Load BG') { + bg_cont.querySelector('input[type=file]').click(); + } else if (ele.textContent == 'Erase BG') { + set_bg(''); + } + }); + + // pos_cont + + //pos_cont.addEventListener('input', e => { + // const ele = e.target; + // let x = +canvas.dataset.x; + // let y = +canvas.dataset.y; + // if (ele.classList.contains(`llul-pos-x`)) { + // x = +ele.value; + // } else if (ele.classList.contains(`llul-pos-y`)) { + // y = +ele.value; + // } else { + // return; + // } + // canvas.dataset.x = x; + // canvas.dataset.y = y; + // updateXY(canvas); + // draw(canvas); + // update_gradio(type, canvas); + //}); + + cont.appendChild(pos_cont); + cont.appendChild(canvas); + cont.appendChild(bg_cont); + setSize(canvas, width.value, height.value); + updateXY(canvas); + draw(canvas); + + return true; + } + + function init2(type, init_fn) { + const repeat_until = (fn, resolve) => { + const v = fn(); + if (v) { + resolve(v); + } else { + setTimeout(() => repeat_until(fn, resolve), 500); + } + }; + + return new Promise(resolve => repeat_until(() => init_fn(type), resolve)); + } + + function init_LLuL() { + if (!LLuL.txt2img) { + LLuL.txt2img = init2('txt2img', init); + if (LLuL.txt2img) { + LLuL.txt2img.then(() => console.log('[LLuL] txt2img initialized')); + } + } + + if (!LLuL.img2img) { + LLuL.img2img = init2('img2img', init); + if (LLuL.img2img) { + LLuL.img2img.then(() => console.log('[LLuL] img2img initialized')); + } + } + + return LLuL.txt2img && LLuL.img2img; + } + + function apply() { + const ok = init_LLuL(); + if (!ok) { + setTimeout(apply, 500); + } + } + + apply(); + +})(); diff --git a/extensions/CHECK/sd-webui-llul/javascript/uti.js b/extensions/CHECK/sd-webui-llul/javascript/uti.js new file mode 100644 index 0000000000000000000000000000000000000000..34c9828901a05633ddd12a2a702dbac05393bf0c --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/javascript/uti.js @@ -0,0 +1,70 @@ +(function() { + + if (!globalThis.LLuL) globalThis.LLuL = {}; + + const OBJ = (function (NAME) { + + let _r = 0; + function to_gradio(v) { + // force call `change` event on gradio + return [v.toString(), (_r++).toString()]; + } + + function js2py(type, gradio_field, value) { + // set `value` to gradio's field + // (1) Click gradio's button. + // (2) Gradio will fire js callback to retrieve value to be set. + // (3) Gradio will fire another js callback to notify the process has been completed. + return new Promise(resolve => { + const callback_name = `${NAME}-${type}-${gradio_field}`; + + // (2) + globalThis[callback_name] = () => { + + delete globalThis[callback_name]; + + // (3) + const callback_after = callback_name + '_after'; + globalThis[callback_after] = () => { + delete globalThis[callback_after]; + resolve(); + }; + + return to_gradio(value); + }; + + // (1) + gradioApp().querySelector(`#${callback_name}_set`).click(); + }); + } + + function py2js(type, pyname, ...args) { + // call python's function + // (1) Set args to gradio's field + // (2) Click gradio's button + // (3) JS callback will be kicked with return value from gradio + + // (1) + return (args.length == 0 ? Promise.resolve() : js2py(type, pyname + '_args', JSON.stringify(args))) + .then(() => { + return new Promise(resolve => { + const callback_name = `${NAME}-${type}-${pyname}`; + // (3) + globalThis[callback_name] = value => { + delete globalThis[callback_name]; + resolve(value); + } + // (2) + gradioApp().querySelector(`#${callback_name}_get`).click(); + }); + }); + } + + return { js2py, py2js } + + })('llul'); + + if (!globalThis.LLuL.js2py) globalThis.LLuL.js2py = OBJ.js2py; + if (!globalThis.LLuL.py2js) globalThis.LLuL.py2js = OBJ.py2js; + +})(); \ No newline at end of file diff --git a/extensions/CHECK/sd-webui-llul/scripts/__pycache__/llul.cpython-310.pyc b/extensions/CHECK/sd-webui-llul/scripts/__pycache__/llul.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e75f52712035a4781c9539da7406a3368e60ca81 Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/scripts/__pycache__/llul.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-llul/scripts/__pycache__/llul_hooker.cpython-310.pyc b/extensions/CHECK/sd-webui-llul/scripts/__pycache__/llul_hooker.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5d26188bf0b193844992eb24c497acd735de050 Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/scripts/__pycache__/llul_hooker.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-llul/scripts/__pycache__/llul_xyz.cpython-310.pyc b/extensions/CHECK/sd-webui-llul/scripts/__pycache__/llul_xyz.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..180ea5f48ac16a3e9c5042f64c1b205152e3409d Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/scripts/__pycache__/llul_xyz.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-llul/scripts/__pycache__/sdhook.cpython-310.pyc b/extensions/CHECK/sd-webui-llul/scripts/__pycache__/sdhook.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3c26b5b4fbf1cc3050471af61ea315f602d8d2f Binary files /dev/null and b/extensions/CHECK/sd-webui-llul/scripts/__pycache__/sdhook.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-llul/scripts/llul.py b/extensions/CHECK/sd-webui-llul/scripts/llul.py new file mode 100644 index 0000000000000000000000000000000000000000..1df46b9b9b28b7cd82bfa3fceaf7ae21944fda68 --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/scripts/llul.py @@ -0,0 +1,289 @@ +import tempfile +from typing import Union, List, Callable + +import torch +import torchvision.transforms.functional +from PIL import Image +import gradio as gr + +from modules.processing import StableDiffusionProcessing, Processed +from modules import scripts + +from scripts.llul_hooker import Hooker, Upscaler, Downscaler +from scripts.llul_xyz import init_xyz + +NAME = 'LLuL' + +class Script(scripts.Script): + + def __init__(self): + super().__init__() + self.last_hooker: Union[Hooker,None] = None + + def title(self): + return NAME + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + mode = 'img2img' if is_img2img else 'txt2img' + id = lambda x: f'{NAME.lower()}-{mode}-{x}' + js = lambda s: f'globalThis["{id(s)}"]' + + with gr.Group(): + with gr.Accordion(NAME, open=False, elem_id=id('accordion')): + enabled = gr.Checkbox(label='Enabled', value=False) + with gr.Row(): + weight = gr.Slider(minimum=-1, maximum=2, value=0.15, step=0.01, label='Weight') + multiply = gr.Slider(value=1, minimum=1, maximum=5, step=1, label='Multiplication (2^N)', elem_id=id('m')) + gr.HTML(elem_id=id('container')) + add_area_image = gr.Checkbox(value=True, label='Add the effective area to output images.') + + with gr.Row(): + use_mask = gr.Checkbox(value=False, label='Enable mask which scales the weight (black = 0.0, white = 1.0)') + mask = gr.File(interactive=True, label='Upload mask image', elem_id=id('mask')) + + force_float = gr.Checkbox(label='Force convert half to float on interpolation (for some platforms)', value=False) + understand = gr.Checkbox(label='I know what I am doing.', value=False) + with gr.Column(visible=False) as g: + layers = gr.Textbox(label='Layers', value='OUT') + apply_to = gr.CheckboxGroup(choices=['Resblock', 'Transformer', 'S. Attn.', 'X. Attn.', 'OUT'], value=['OUT'], label='Apply to') + start_steps = gr.Slider(minimum=1, maximum=300, value=5, step=1, label='Start steps') + max_steps = gr.Slider(minimum=0, maximum=300, value=0, step=1, label='Max steps') + with gr.Row(): + up = gr.Radio(choices=['Nearest', 'Bilinear', 'Bicubic'], value='Bilinear', label='Upscaling') + up_aa = gr.Checkbox(value=False, label='Enable AA for Upscaling.') + with gr.Row(): + down = gr.Radio(choices=['Nearest', 'Bilinear', 'Bicubic', 'Area', 'Pooling Max', 'Pooling Avg'], value='Bilinear', label='Downscaling') + down_aa = gr.Checkbox(value=False, label='Enable AA for Downscaling.') + intp = gr.Radio(choices=['Lerp', 'SLerp'], value='Lerp', label='interpolation method') + + understand.change( + lambda b: { g: gr.update(visible=b) }, + inputs=[understand], + outputs=[ + g # type: ignore + ] + ) + + with gr.Row(visible=False): + sink = gr.HTML(value='') # to suppress error in javascript + x = js2py('x', id, js, sink) + y = js2py('y', id, js, sink) + + return [ + enabled, + multiply, + weight, + understand, + layers, + apply_to, + start_steps, + max_steps, + up, + up_aa, + down, + down_aa, + intp, + x, + y, + force_float, + use_mask, + mask, + add_area_image, + ] + + def process( + self, + p: StableDiffusionProcessing, + enabled: bool, + multiply: Union[int,float], + weight: float, + understand: bool, + layers: str, + apply_to: Union[List[str],str], + start_steps: Union[int,float], + max_steps: Union[int,float], + up: str, + up_aa: bool, + down: str, + down_aa: bool, + intp: str, + x: Union[str,None] = None, + y: Union[str,None] = None, + force_float = False, + use_mask: bool = False, + mask: Union[tempfile._TemporaryFileWrapper,None] = None, + add_area_image: bool = True, # for postprocess + ): + if self.last_hooker is not None: + self.last_hooker.__exit__(None, None, None) + self.last_hooker = None + + if not enabled: + return + + if p.width < 128 or p.height < 128: + raise ValueError(f'Image size is too small to LLuL: {p.width}x{p.height}; expected >=128x128.') + + multiply = 2 ** int(max(multiply, 0)) + weight = float(weight) + if x is None or len(x) == 0: + x = str((p.width - p.width // multiply) // 2) + if y is None or len(y) == 0: + y = str((p.height - p.height // multiply) // 2) + + if understand: + lays = ( + None if len(layers) == 0 else + [x.strip() for x in layers.split(',')] + ) + if isinstance(apply_to, str): + apply_to = [x.strip() for x in apply_to.split(',')] + apply_to = [x.lower() for x in apply_to] + start_steps = max(1, int(start_steps)) + max_steps = max(1, [p.steps, int(max_steps)][1 <= max_steps]) + up_fn = Upscaler(up, up_aa) + down_fn = Downscaler(down, down_aa) + intp = intp.lower() + else: + lays = ['OUT'] + apply_to = ['out'] + start_steps = 5 + max_steps = int(p.steps) + up_fn = Upscaler('bilinear', aa=False) + down_fn = Downscaler('bilinear', aa=False) + intp = 'lerp' + + xf = float(x) + yf = float(y) + + mask_image = None + if use_mask and mask is not None: + # Can I read from passed tempfile._TemporaryFileWrapper??? + mask_image = Image.open(mask.name).convert('L') + intp = 'lerp' + + self.last_hooker = Hooker( + enabled=True, + multiply=int(multiply), + weight=weight, + layers=lays, + apply_to=apply_to, + start_steps=start_steps, + max_steps=max_steps, + up_fn=up_fn, + down_fn=down_fn, + intp=intp, + x=xf/p.width, + y=yf/p.height, + force_float=force_float, + mask_image=mask_image, + ) + + self.last_hooker.setup(p) + self.last_hooker.__enter__() + + p.extra_generation_params.update({ + f'{NAME} Enabled': enabled, + f'{NAME} Multiply': multiply, + f'{NAME} Weight': weight, + f'{NAME} Layers': lays, + f'{NAME} Apply to': apply_to, + f'{NAME} Start steps': start_steps, + f'{NAME} Max steps': max_steps, + f'{NAME} Upscaler': up_fn.name, + f'{NAME} Downscaler': down_fn.name, + f'{NAME} Interpolation': intp, + f'{NAME} x': x, + f'{NAME} y': y, + }) + + def postprocess( + self, + p: StableDiffusionProcessing, + proc: Processed, + enabled: bool, + multiply: Union[int,float], + weight: float, + understand: bool, + layers: str, + apply_to: Union[List[str],str], + start_steps: Union[int,float], + max_steps: Union[int,float], + up: str, + up_aa: bool, + down: str, + down_aa: bool, + intp: str, + x: Union[str,None] = None, + y: Union[str,None] = None, + force_float = False, + use_mask: bool = False, + mask: Union[tempfile._TemporaryFileWrapper,None] = None, + add_area_image: bool = True, + ): + if not enabled: + return + + multiply = int(2 ** int(max(multiply, 0))) + if x is None or len(x) == 0: + x = str((p.width - p.width // multiply) // 2) + if y is None or len(y) == 0: + y = str((p.height - p.height // multiply) // 2) + + xi0 = int(float(x)) # for '133.1999969482422' or etc. + yi0 = int(float(y)) + xi1 = xi0 + p.width // multiply + yi1 = yi0 + p.height // multiply + + area = torch.zeros((1, p.height, p.width), dtype=torch.float) + area[:, yi0:yi1, xi0:xi1] = 1.0 + + pil_to_tensor = torchvision.transforms.functional.to_tensor + tensor_to_pil = torchvision.transforms.functional.to_pil_image + + if use_mask and mask is not None: + # Can I read from passed tempfile._TemporaryFileWrapper??? + mask_image = Image.open(mask.name).convert('L').resize((xi1 - xi0, yi1 - yi0), Image.BILINEAR) + mask_tensor = pil_to_tensor(mask_image) + # :: (1,h,w), each value is between 0 and 1 + area[:, yi0:yi1, xi0:xi1] = mask_tensor + + # (0.0, 1.0) -> (0.25, 1.0) + area.mul_(0.75).add_(0.25) + + for image_index in range(len(proc.images)): + is_grid = image_index < proc.index_of_first_image + if is_grid: + continue + + area_tensor = pil_to_tensor(proc.images[image_index]) + area_tensor.mul_(area) + area_image = tensor_to_pil(area_tensor, mode='RGB') + + i = image_index - proc.index_of_first_image + proc.images.append(area_image) + proc.all_prompts.append(proc.all_prompts[i]) + proc.all_negative_prompts.append(proc.all_negative_prompts[i]) + proc.all_seeds.append(proc.all_seeds[i]) + proc.all_subseeds.append(proc.all_subseeds[i]) + proc.infotexts.append(proc.infotexts[image_index]) + + +def js2py( + name: str, + id: Callable[[str], str], + js: Callable[[str], str], + sink: gr.components.IOComponent, +): + v_set = gr.Button(elem_id=id(f'{name}_set')) + v = gr.Textbox(elem_id=id(name)) + v_sink = gr.Textbox() + v_set.click(fn=None, _js=js(name), outputs=[v, v_sink]) + v_sink.change(fn=None, _js=js(f'{name}_after'), outputs=[sink]) + return v + + +init_xyz(Script) diff --git a/extensions/CHECK/sd-webui-llul/scripts/llul_hooker.py b/extensions/CHECK/sd-webui-llul/scripts/llul_hooker.py new file mode 100644 index 0000000000000000000000000000000000000000..b58cdcec84ff6d506a9524bcdb02a86b1c123530 --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/scripts/llul_hooker.py @@ -0,0 +1,382 @@ +import math +from typing import Union, Callable, List + +import torch +import torch.nn.functional as F +import torchvision.transforms.functional +from torch import nn, Tensor +from einops import rearrange +from PIL import Image +from modules.processing import StableDiffusionProcessing, slerp as Slerp + +from scripts.sdhook import ( + SDHook, + each_unet_attn_layers, + each_unet_transformers, + each_unet_resblock +) + + +class Upscaler: + + def __init__(self, mode: str, aa: bool): + mode = { + 'nearest': 'nearest-exact', + 'bilinear': 'bilinear', + 'bicubic': 'bicubic', + }.get(mode.lower(), mode) + self.mode = mode + self.aa = bool(aa) + + @property + def name(self): + s = self.mode + if self.aa: s += '-aa' + return s + + def __call__(self, x: Tensor, scale: float = 2.0): + return F.interpolate(x, scale_factor=scale, mode=self.mode, antialias=self.aa) + + +class Downscaler: + + def __init__(self, mode: str, aa: bool): + self._name = mode.lower() + intp, mode = { + 'nearest': (F.interpolate, 'nearest-exact'), + 'bilinear': (F.interpolate, 'bilinear'), + 'bicubic': (F.interpolate, 'bicubic'), + 'area': (F.interpolate, 'area'), + 'pooling max': (F.max_pool2d, ''), + 'pooling avg': (F.avg_pool2d, ''), + }[mode.lower()] + self.intp = intp + self.mode = mode + self.aa = bool(aa) + + @property + def name(self): + s = self._name + if self.aa: s += '-aa' + return s + + def __call__(self, x: Tensor, scale: float = 2.0): + if scale <= 1: + scale = float(scale) + scale_inv = 1 / scale + else: + scale_inv = float(scale) + scale = 1 / scale_inv + assert scale <= 1 + assert 1 <= scale_inv + + kwargs = {} + if len(self.mode) != 0: + kwargs['scale_factor'] = scale + kwargs['mode'] = self.mode + kwargs['antialias'] = self.aa + else: + kwargs['kernel_size'] = int(scale_inv) + return self.intp(x, **kwargs) + + +def lerp(v0, v1, t): + return torch.lerp(v0, v1, t) + +def slerp(v0, v1, t): + v = Slerp(t, v0, v1) + if torch.any(torch.isnan(v)).item(): + v = lerp(v0, v1, t) + return v + +class Hooker(SDHook): + + def __init__( + self, + enabled: bool, + multiply: int, + weight: float, + layers: Union[list,None], + apply_to: List[str], + start_steps: int, + max_steps: int, + up_fn: Callable[[Tensor,float], Tensor], + down_fn: Callable[[Tensor,float], Tensor], + intp: str, + x: float, + y: float, + force_float: bool, + mask_image: Union[Image.Image,None], + ): + super().__init__(enabled) + self.multiply = int(multiply) + self.weight = float(weight) + self.layers = layers + self.apply_to = apply_to + self.start_steps = int(start_steps) + self.max_steps = int(max_steps) + self.up = up_fn + self.down = down_fn + self.x0 = x + self.y0 = y + self.force_float = force_float + self.mask_image = mask_image + + if intp == 'lerp': + self.intp = lerp + elif intp == 'slerp': + self.intp = slerp + else: + raise ValueError(f'invalid interpolation method: {intp}') + + if not (1 <= self.multiply and (self.multiply & (self.multiply - 1) == 0)): + raise ValueError(f'multiplier must be power of 2, but not: {self.multiply}') + + if mask_image is not None: + if mask_image.mode != 'L': + raise ValueError(f'the mode of mask image is: {mask_image.mode}') + + def hook_unet(self, p: StableDiffusionProcessing, unet: nn.Module): + step = 0 + + def hook_step_pre(*args, **kwargs): + nonlocal step + step += 1 + + self.hook_layer_pre(unet, hook_step_pre) + + start_step = self.start_steps + max_steps = self.max_steps + M = self.multiply + + def create_pre_hook(name: str, ctx: dict): + def pre_hook(module: nn.Module, inputs: list): + ctx['skipped'] = True + + if step < start_step or max_steps < step: + return + + x, *rest = inputs + dim = x.dim() + if dim == 3: + # attension + bi, ni, chi = x.shape + wi, hi, Ni = self.get_size(p, ni) + x = rearrange(x, 'b (h w) c -> b c h w', w=wi) + if len(rest) != 0: + # x. attn. + rest[0] = torch.concat((rest[0], rest[0]), dim=0) + elif dim == 4: + # resblock, transformer + bi, chi, hi, wi = x.shape + if 0 < len(rest): + t_emb = rest[0] # t_emb (for resblock) or context (for transformer) + rest[0] = torch.concat((t_emb, t_emb), dim=0) + else: + # `out` layer + pass + else: + return + + # extract + w, h = wi // M, hi // M + if w == 0 or h == 0: + # input latent is too small to apply + return + + s0, t0 = int(wi * self.x0), int(hi * self.y0) + s1, t1 = s0 + w, t0 + h + if wi < s1: + s1 = wi + s0 = s1 - w + if hi < t1: + t1 = hi + t0 = t1 - h + + if s0 < 0 or t0 < 0: + raise ValueError(f'LLuL failed to process: s=({s0},{s1}), t=({t0},{t1})') + + x1 = x[:, :, t0:t1, s0:s1] + + # upscaling + x1 = self.up(x1, M) + if x1.shape[-1] < x.shape[-1] or x1.shape[-2] < x.shape[-2]: + dx = x.shape[-1] - x1.shape[-1] + dx1 = dx // 2 + dx2 = dx - dx1 + dy = x.shape[-2] - x1.shape[-2] + dy1 = dy // 2 + dy2 = dy - dy1 + x1 = F.pad(x1, (dx1, dx2, dy1, dy2), 'replicate') + + x = torch.concat((x, x1), dim=0) + if dim == 3: + x = rearrange(x, 'b c h w -> b (h w) c').contiguous() + + #print('I', tuple(inputs[0].shape), tuple(x.shape)) + ctx['skipped'] = False + return x, *rest + return pre_hook + + def create_post_hook(name: str, ctx: dict): + def post_hook(module: nn.Module, inputs: list, output: Tensor): + if step < start_step or max_steps < step: + return + + if ctx['skipped']: + return + + x = output + dim = x.dim() + if dim == 3: + bo, no, cho = x.shape + wo, ho, No = self.get_size(p, no) + x = rearrange(x, 'b (h w) c -> b c h w', w=wo) + elif dim == 4: + bo, cho, ho, wo = x.shape + else: + return + + assert bo % 2 == 0 + x, x1 = x[:bo//2], x[bo//2:] + + # downscaling + x1 = self.down(x1, M) + + # embed + w, h = x1.shape[-1], x1.shape[-2] + s0, t0 = int(wo * self.x0), int(ho * self.y0) + s1, t1 = s0 + w, t0 + h + if wo < s1: + s1 = wo + s0 = s1 - w + if ho < t1: + t1 = ho + t0 = t1 - h + + if s0 < 0 or t0 < 0: + raise ValueError(f'LLuL failed to process: s=({s0},{s1}), t=({t0},{t1})') + + x[:, :, t0:t1, s0:s1] = self.interpolate(x[:, :, t0:t1, s0:s1], x1, self.weight) + + if dim == 3: + x = rearrange(x, 'b c h w -> b (h w) c').contiguous() + + #print('O', tuple(inputs[0].shape), tuple(x.shape)) + return x + return post_hook + + def create_hook(name: str, **kwargs): + ctx = dict() + ctx.update(kwargs) + return ( + create_pre_hook(name, ctx), + create_post_hook(name, ctx) + ) + + def wrap_for_xattn(pre, post): + def f(module: nn.Module, o: Callable, *args, **kwargs): + inputs = list(args) + list(kwargs.values()) + inputs_ = pre(module, inputs) + if inputs_ is not None: + inputs = inputs_ + output = o(*inputs) + output_ = post(module, inputs, output) + if output_ is not None: + output = output_ + return output + return f + + # + # process each attention layers + # + for name, attn in each_unet_attn_layers(unet): + if self.layers is not None: + if not any(layer in name for layer in self.layers): + continue + + q_in = attn.to_q.in_features + k_in = attn.to_k.in_features + if q_in == k_in: + # self-attention + if 's. attn.' in self.apply_to: + pre, post = create_hook(name) + self.hook_layer_pre(attn, pre) + self.hook_layer(attn, post) + else: + # cross-attention + if 'x. attn.' in self.apply_to: + pre, post = create_hook(name) + self.hook_forward(attn, wrap_for_xattn(pre, post)) + + # + # process Resblocks + # + for name, res in each_unet_resblock(unet): + if 'resblock' not in self.apply_to: + continue + + if self.layers is not None: + if not any(layer in name for layer in self.layers): + continue + + pre, post = create_hook(name) + self.hook_layer_pre(res, pre) + self.hook_layer(res, post) + + # + # process Transformers (including s/x-attn) + # + for name, res in each_unet_transformers(unet): + if 'transformer' not in self.apply_to: + continue + + if self.layers is not None: + if not any(layer in name for layer in self.layers): + continue + + pre, post = create_hook(name) + self.hook_layer_pre(res, pre) + self.hook_layer(res, post) + + # + # process OUT + # + if 'out' in self.apply_to: + out = unet.out + pre, post = create_hook('out') + self.hook_layer_pre(out, pre) + self.hook_layer(out, post) + + def get_size(self, p: StableDiffusionProcessing, n: int): + # n := p.width / N * p.height / N + wh = p.width * p.height + N2 = wh // n + N = int(math.sqrt(N2)) + assert N*N == N2, f'N={N}, N2={N2}' + assert p.width % N == 0, f'width={p.width}, N={N}' + assert p.height % N == 0, f'height={p.height}, N={N}' + w, h = p.width // N, p.height // N + assert w * h == n, f'w={w}, h={h}, N={N}, n={n}' + return w, h, N + + def interpolate(self, v1: Tensor, v2: Tensor, t: float): + dtype = v1.dtype + if self.force_float: + v1 = v1.float() + v2 = v2.float() + + if self.mask_image is None: + v = self.intp(v1, v2, t) + else: + to_w, to_h = v1.shape[-1], v1.shape[-2] + resized_image = self.mask_image.resize((to_w, to_h), Image.BILINEAR) + mask = torchvision.transforms.functional.to_tensor(resized_image).to(device=v1.device, dtype=v1.dtype) + mask.unsqueeze_(0) # (C,H,W) -> (B,C,H,W) + mask.mul_(t) + v = self.intp(v1, v2, mask) + + if self.force_float: + v = v.to(dtype) + + return v diff --git a/extensions/CHECK/sd-webui-llul/scripts/llul_xyz.py b/extensions/CHECK/sd-webui-llul/scripts/llul_xyz.py new file mode 100644 index 0000000000000000000000000000000000000000..09c707ce8e435d7d3182a8e78d6edcf81f5d63b0 --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/scripts/llul_xyz.py @@ -0,0 +1,138 @@ +import os +from typing import Union, List, Callable + +from modules import scripts +from modules.processing import StableDiffusionProcessing, StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img + + +def __set_value(p: StableDiffusionProcessing, script: type, index: int, value): + args = list(p.script_args) + + if isinstance(p, StableDiffusionProcessingTxt2Img): + all_scripts = scripts.scripts_txt2img.scripts + else: + all_scripts = scripts.scripts_img2img.scripts + + froms = [x.args_from for x in all_scripts if isinstance(x, script)] + for idx in froms: + assert idx is not None + args[idx + index] = value + if 3 < index: + args[idx + 3] = True + + p.script_args = type(p.script_args)(args) + + +def to_bool(v: str): + if len(v) == 0: return False + v = v.lower() + if 'true' in v: return True + if 'false' in v: return False + + try: + w = int(v) + return bool(w) + except: + acceptable = ['True', 'False', '1', '0'] + s = ', '.join([f'`{v}`' for v in acceptable]) + raise ValueError(f'value must be one of {s}.') + + +class AxisOptions: + + def __init__(self, AxisOption: type, axis_options: list): + self.AxisOption = AxisOption + self.target = axis_options + self.options = [] + + def __enter__(self): + self.options.clear() + return self + + def __exit__(self, ex_type, ex_value, trace): + if ex_type is not None: + return + + for opt in self.options: + self.target.append(opt) + + self.options.clear() + + def create(self, name: str, type_fn: Callable, action: Callable, choices: Union[List[str],None]): + if choices is None or len(choices) == 0: + opt = self.AxisOption(name, type_fn, action) + else: + opt = self.AxisOption(name, type_fn, action, choices=lambda: choices) + return opt + + def add(self, axis_option): + self.target.append(axis_option) + + +__init = False + +def init_xyz(script: type): + global __init + + if __init: + return + + for data in scripts.scripts_data: + name = os.path.basename(data.path) + if name != 'xy_grid.py' and name != 'xyz_grid.py': + continue + + if not hasattr(data.module, 'AxisOption'): + continue + + if not hasattr(data.module, 'axis_options'): + continue + + AxisOption = data.module.AxisOption + axis_options = data.module.axis_options + + if not isinstance(AxisOption, type): + continue + + if not isinstance(axis_options, list): + continue + + try: + create_options(script, AxisOption, axis_options) + except: + pass + + __init = True + + +def create_options(script: type, AxisOptionClass: type, axis_options: list): + ext_name = 'LLuL' + + with AxisOptions(AxisOptionClass, axis_options) as opts: + def define(param: str, index: int, type_fn: Callable, choices: List[str] = []): + def fn(p, x, xs): + __set_value(p, script, index, x) + + name = f'{ext_name} {param}' + return opts.create(name, type_fn, fn, choices) + + idx = 3 + options = [ + define('Enabled', 0, to_bool, choices=['false', 'true']), + define('Multiply', 1, int), + define('Weight', 2, float), + + #define('Understand', idx+0, to_bool, choices=['false', 'true']), + define('Layers', idx+1, str), + define('Apply to', idx+2, str, choices=['Resblock', 'Transformer', 'S. Attn.', 'X. Attn.', 'OUT']), + define('Start steps', idx+3, int), + define('Max steps', idx+4, int), + define('Upscaler', idx+5, str, choices=['Nearest', 'Bilinear', 'Bicubic']), + define('Upscaler AA', idx+6, to_bool, choices=['false', 'true']), + define('Downscaler', idx+7, str, choices=['Nearest', 'Bilinear', 'Bicubic', 'Area', 'Pooling Max', 'Pooling Avg']), + define('Downscaler AA', idx+8, to_bool, choices=['false', 'true']), + define('Interpolation method', idx+9, str, choices=['Lerp', 'SLerp']), + ] + + for opt in options: + opts.add(opt) diff --git a/extensions/CHECK/sd-webui-llul/scripts/sdhook.py b/extensions/CHECK/sd-webui-llul/scripts/sdhook.py new file mode 100644 index 0000000000000000000000000000000000000000..32ba915e3841c571464685256114510835473b9d --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/scripts/sdhook.py @@ -0,0 +1,275 @@ +import sys +from typing import Any, Callable, Union + +from torch import nn +from torch.utils.hooks import RemovableHandle + +from ldm.modules.diffusionmodules.openaimodel import ( + TimestepEmbedSequential, +) +from ldm.modules.attention import ( + SpatialTransformer, + BasicTransformerBlock, + CrossAttention, + MemoryEfficientCrossAttention, +) +from ldm.modules.diffusionmodules.openaimodel import ( + ResBlock, +) +from modules.processing import StableDiffusionProcessing +from modules import shared + +class ForwardHook: + + def __init__(self, module: nn.Module, fn: Callable[[nn.Module, Callable[..., Any], Any], Any]): + self.o = module.forward + self.fn = fn + self.module = module + self.module.forward = self.forward + + def remove(self): + if self.module is not None and self.o is not None: + self.module.forward = self.o + self.module = None + self.o = None + self.fn = None + + def forward(self, *args, **kwargs): + if self.module is not None and self.o is not None: + if self.fn is not None: + return self.fn(self.module, self.o, *args, **kwargs) + return None + + +class SDHook: + + def __init__(self, enabled: bool): + self._enabled = enabled + self._handles: list[Union[RemovableHandle,ForwardHook]] = [] + + @property + def enabled(self): + return self._enabled + + @property + def batch_num(self): + return shared.state.job_no + + @property + def step_num(self): + return shared.state.current_image_sampling_step + + def __enter__(self): + if self.enabled: + pass + + def __exit__(self, exc_type, exc_value, traceback): + if self.enabled: + for handle in self._handles: + handle.remove() + self._handles.clear() + self.dispose() + + def dispose(self): + pass + + def setup( + self, + p: StableDiffusionProcessing + ): + if not self.enabled: + return + + wrapper = getattr(p.sd_model, "model", None) + + unet: Union[nn.Module,None] = getattr(wrapper, "diffusion_model", None) if wrapper is not None else None + vae: Union[nn.Module,None] = getattr(p.sd_model, "first_stage_model", None) + clip: Union[nn.Module,None] = getattr(p.sd_model, "cond_stage_model", None) + + assert unet is not None, "p.sd_model.diffusion_model is not found. broken model???" + self._do_hook(p, p.sd_model, unet=unet, vae=vae, clip=clip) # type: ignore + self.on_setup() + + def on_setup(self): + pass + + def _do_hook( + self, + p: StableDiffusionProcessing, + model: Any, + unet: Union[nn.Module,None], + vae: Union[nn.Module,None], + clip: Union[nn.Module,None] + ): + assert model is not None, "empty model???" + + if clip is not None: + self.hook_clip(p, clip) + + if unet is not None: + self.hook_unet(p, unet) + + if vae is not None: + self.hook_vae(p, vae) + + def hook_vae( + self, + p: StableDiffusionProcessing, + vae: nn.Module + ): + pass + + def hook_unet( + self, + p: StableDiffusionProcessing, + unet: nn.Module + ): + pass + + def hook_clip( + self, + p: StableDiffusionProcessing, + clip: nn.Module + ): + pass + + def hook_layer( + self, + module: Union[nn.Module,Any], + fn: Callable[[nn.Module, list, Any], Any] + ): + if not self.enabled: + return + + assert module is not None + assert isinstance(module, nn.Module) + self._handles.append(module.register_forward_hook(fn)) + + def hook_layer_pre( + self, + module: Union[nn.Module,Any], + fn: Callable[[nn.Module, list], Any] + ): + if not self.enabled: + return + + assert module is not None + assert isinstance(module, nn.Module) + self._handles.append(module.register_forward_pre_hook(fn)) + + def hook_forward( + self, + module: Union[nn.Module,Any], + fn: Callable[[nn.Module, Callable[..., Any], Any], Any] + ): + assert module is not None + assert isinstance(module, nn.Module) + self._handles.append(ForwardHook(module, fn)) + + def log(self, msg: str): + print(msg, file=sys.stderr) + + +# enumerate SpatialTransformer in TimestepEmbedSequential +def each_transformer(unet_block: TimestepEmbedSequential): + for block in unet_block.children(): + if isinstance(block, SpatialTransformer): + yield block + +# enumerate BasicTransformerBlock in SpatialTransformer +def each_basic_block(trans: SpatialTransformer): + for block in trans.transformer_blocks.children(): + if isinstance(block, BasicTransformerBlock): + yield block + +# enumerate Attention Layers in TimestepEmbedSequential +# each_transformer + each_basic_block +def each_attns(unet_block: TimestepEmbedSequential): + for n, trans in enumerate(each_transformer(unet_block)): + for depth, basic_block in enumerate(each_basic_block(trans)): + # attn1: Union[CrossAttention,MemoryEfficientCrossAttention] + # attn2: Union[CrossAttention,MemoryEfficientCrossAttention] + + attn1, attn2 = basic_block.attn1, basic_block.attn2 + assert isinstance(attn1, CrossAttention) or isinstance(attn1, MemoryEfficientCrossAttention) + assert isinstance(attn2, CrossAttention) or isinstance(attn2, MemoryEfficientCrossAttention) + + yield n, depth, attn1, attn2 + +def each_unet_attn_layers(unet: nn.Module): + def get_attns(layer_index: int, block: TimestepEmbedSequential, format: str): + for n, d, attn1, attn2 in each_attns(block): + kwargs = { + 'layer_index': layer_index, + 'trans_index': n, + 'block_index': d + } + yield format.format(attn_name='sattn', **kwargs), attn1 + yield format.format(attn_name='xattn', **kwargs), attn2 + + def enumerate_all(blocks: nn.ModuleList, format: str): + for idx, block in enumerate(blocks.children()): + if isinstance(block, TimestepEmbedSequential): + yield from get_attns(idx, block, format) + + inputs: nn.ModuleList = unet.input_blocks # type: ignore + middle: TimestepEmbedSequential = unet.middle_block # type: ignore + outputs: nn.ModuleList = unet.output_blocks # type: ignore + + yield from enumerate_all(inputs, 'IN{layer_index:02}_{trans_index:02}_{block_index:02}_{attn_name}') + yield from get_attns(0, middle, 'M{layer_index:02}_{trans_index:02}_{block_index:02}_{attn_name}') + yield from enumerate_all(outputs, 'OUT{layer_index:02}_{trans_index:02}_{block_index:02}_{attn_name}') + + +def each_unet_transformers(unet: nn.Module): + def get_trans(layer_index: int, block: TimestepEmbedSequential, format: str): + for n, trans in enumerate(each_transformer(block)): + kwargs = { + 'layer_index': layer_index, + 'block_index': n, + 'block_name': 'trans', + } + yield format.format(**kwargs), trans + + def enumerate_all(blocks: nn.ModuleList, format: str): + for idx, block in enumerate(blocks.children()): + if isinstance(block, TimestepEmbedSequential): + yield from get_trans(idx, block, format) + + inputs: nn.ModuleList = unet.input_blocks # type: ignore + middle: TimestepEmbedSequential = unet.middle_block # type: ignore + outputs: nn.ModuleList = unet.output_blocks # type: ignore + + yield from enumerate_all(inputs, 'IN{layer_index:02}_{block_index:02}_{block_name}') + yield from get_trans(0, middle, 'M{layer_index:02}_{block_index:02}_{block_name}') + yield from enumerate_all(outputs, 'OUT{layer_index:02}_{block_index:02}_{block_name}') + + +def each_resblock(unet_block: TimestepEmbedSequential): + for block in unet_block.children(): + if isinstance(block, ResBlock): + yield block + +def each_unet_resblock(unet: nn.Module): + def get_resblock(layer_index: int, block: TimestepEmbedSequential, format: str): + for n, res in enumerate(each_resblock(block)): + kwargs = { + 'layer_index': layer_index, + 'block_index': n, + 'block_name': 'resblock', + } + yield format.format(**kwargs), res + + def enumerate_all(blocks: nn.ModuleList, format: str): + for idx, block in enumerate(blocks.children()): + if isinstance(block, TimestepEmbedSequential): + yield from get_resblock(idx, block, format) + + inputs: nn.ModuleList = unet.input_blocks # type: ignore + middle: TimestepEmbedSequential = unet.middle_block # type: ignore + outputs: nn.ModuleList = unet.output_blocks # type: ignore + + yield from enumerate_all(inputs, 'IN{layer_index:02}_{block_index:02}_{block_name}') + yield from get_resblock(0, middle, 'M{layer_index:02}_{block_index:02}_{block_name}') + yield from enumerate_all(outputs, 'OUT{layer_index:02}_{block_index:02}_{block_name}') + diff --git a/extensions/CHECK/sd-webui-llul/style.css b/extensions/CHECK/sd-webui-llul/style.css new file mode 100644 index 0000000000000000000000000000000000000000..a7a35bdd45c3a4f0b0991d4cfbf4c813306dc60b --- /dev/null +++ b/extensions/CHECK/sd-webui-llul/style.css @@ -0,0 +1,38 @@ +.llul-bg-setting { + margin-top: 0.5em; +} + +.llul-bg-setting span { + display: inline-block; + margin: 0 0.5em; + padding: 0.5em; + outline: 1px solid black; + cursor: default; + user-select: none; +} + +.llul-bg-setting label { + user-select: none; +} + +#llul-txt2img-mask, +#llul-img2img-mask { + max-height: 2em; + overflow: visible; +} + +.llul-pos input { + display: inline-block; + border: none; + border-bottom: 1px solid black; + font-size: smaller !important; + width: 8em; + height: 1em; + background-color: transparent; + margin-right: 1em; +} + +.dark .llul-pos input { + border-bottom-color: white; + background-color: transparent; +} diff --git a/extensions/CHECK/sd-webui-lora-block-weight/.github/FUNDING.yml b/extensions/CHECK/sd-webui-lora-block-weight/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..fb0dbcbcd838723bc56e628f5f7dfadbae976893 --- /dev/null +++ b/extensions/CHECK/sd-webui-lora-block-weight/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github:[hako-mikan] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/extensions/CHECK/sd-webui-lora-block-weight/.gitignore b/extensions/CHECK/sd-webui-lora-block-weight/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..52f0b4fdefca009078d1bd798ada1b636602c2cd --- /dev/null +++ b/extensions/CHECK/sd-webui-lora-block-weight/.gitignore @@ -0,0 +1,3 @@ +scripts/__pycache__/ +scripts/elempresets.txt +scripts/lbwpresets.txt diff --git a/extensions/CHECK/sd-webui-lora-block-weight/README.md b/extensions/CHECK/sd-webui-lora-block-weight/README.md new file mode 100644 index 0000000000000000000000000000000000000000..945d5a08d1f36918447c35689b1332a22db1e248 --- /dev/null +++ b/extensions/CHECK/sd-webui-lora-block-weight/README.md @@ -0,0 +1,556 @@ +# LoRA Block Weight +- custom script for [AUTOMATIC1111's stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) +- When applying Lora, strength can be set block by block. + +- [AUTOMATIC1111's stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) 用のスクリプトです +- Loraを適用する際、強さを階層ごとに設定できます + +[](#overview) +[](#概要) +[](https://github.com/sponsors/hako-mikan) + +> [!IMPORTANT] +> If you have an error :`ValueError: could not convert string to float` +> use new syntax`` + + +## Updates/更新情報 +### 2024.04.06.0000(JST) +- add [new UI](#make-weights): make weights +- ウェイトを作成する[新しいUI](#ウェイトの作成)を追加 + +### 2023.11.22.2000(JST) +- bugfix +- added new feature:start in steps +- 機能追加:LoRAの途中開始 + +### 2023.11.21.1930(JST) +- added new feature:stop in steps +- 機能追加:LoRAの途中停止 +By specifying ``, you can disable the effect of LoRA at the specified step. In the case of character or composition LoRA, a sufficient effect is achieved in about 10 steps, and by cutting it off at this point, it is possible to minimize the impact on the style of the painting +``と指定することで指定したstepでLoRAの効果を無くします。キャラクターや構図LoRAの場合には10 step程度で十分な効果があり、ここで切ることで画風への影響を抑えることが可能です。 + +# Overview +Lora is a powerful tool, but it is sometimes difficult to use and can affect areas that you do not want it to affect. This script allows you to set the weights block-by-block. Using this script, you may be able to get the image you want. + +## Usage +Place lora_block_weight.py in the script folder. +Or you can install from Extentions tab in web-ui. When installing, please restart web-ui.bat. + +### Active +Check this box to activate it. + +### Prompt +In the prompt box, enter the Lora you wish to use as usual. Enter the weight or identifier by typing ":" after the strength value. The identifier can be edited in the Weights setting. +``` +. +. (a1111-sd-webui-locon, etc.) + (a1111-sd-webui-lycoris, web-ui 1.5 or later) + (a1111-sd-webui-lycoris, web-ui 1.5 or later) +``` +For LyCORIS using a1111-sd-webui-lycoris, syntax is different. +`lbw=IN02` is used and follow lycoirs syntax for others such as unet or else. +a1111-sd-webui-lycoris is under under development, so this syntax might be changed. + +Lora strength is in effect and applies to the entire Blocks. +It is case-sensitive. +For LyCORIS, full-model blobks used,so you need to input 26 weights. +You can use weight for LoRA, in this case, the weight of blocks not in LoRA is set to 0.   +If the above format is not used, the preset will treat it as a comment line. + +### start, stop step +By specifying ``, the effect of LoRA appears from the designated step. By specifying ``, the effect of LoRA is eliminated at the specified step. In the case of character or composition LoRA, a significant effect is achieved in about 10 steps, and by cutting it off at this point, it is possible to minimize the influence on the style of the painting. By specifying ``, LoRA is activated only between steps 5-10." + +### Weights Setting +Enter the identifier and weights. +Unlike the full model, Lora is divided into 17 blocks, including the encoder. Therefore, enter 17 values. +BASE, IN, OUT, etc. are the blocks equivalent to the full model. +Due to various formats such as Full Model and LyCORIS and SDXL, script currently accept weights for 12, 17, 20, and 26. Generally, even if weights in incompatible formats are inputted, the system will still function. However, any layers not provided will be treated as having a weight of 0. + +LoRA(17) +|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17| +|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| +|BASE|IN01|IN02|IN04|IN05|IN07|IN08|MID|OUT03|OUT04|OUT05|OUT06|OUT07|OUT08|OUT09|OUT10|OUT11| + +LyCORIS, etc. (26) +|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26| +|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| +|BASE|IN00|IN01|IN02|IN03|IN04|IN05|IN06|IN07|IN08|IN09|IN10|IN11|MID|OUT00|OUT01|OUT02|OUT03|OUT04|OUT05|OUT06|OUT07|OUT08|OUT09|OUT10|OUT11| + +SDXL LoRA(12) +|1|2|3|4|5|6|7|8|9|10|11|12| +|-|-|-|-|-|-|-|-|-|-|-|-| +|BASE|IN04|IN05|IN07|IN08|MID|OUT0|OUT1|OUT2|OUT3|OUT4|OUT05| + +SDXL - LyCORIS/LoCon(20) +|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20| +|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| +|BASE|IN00|IN01|IN02|IN03|IN04|IN05|IN06|IN07|IN08||MID|OUT00|OUT01|OUT02|OUT03|OUT04|OUT05|OUT06|OUT07|OUT08| + +### Special Values (Random) +Basically, a numerical value must be entered to work correctly, but by entering `R` and `U`, a random value will be entered. +R : Numerical value with 3 decimal places from 0~1 +U : 3 decimal places from -1.5 to 1.5 + +For example, if ROUT:1,1,1,1,1,1,1,1,R,R,R,R,R,R,R,R,R +Only the OUT blocks is randomized. +The randomized values will be displayed on the command prompt screen when the image is generated. + +### Special Values (Dynamic) +The special value `X` may also be included to use a dynamic weight specified in the LoRA syntax. This is activated by including an additional weight value after the specified `Original Weight`. + +For example, if ROUT:X,1,1,1,1,1,1,1,1,1,1,1,X,X,X,X,X and you had a prompt containing \. The `X` weights in ROUT would be replaced with `0.7` at runtime. + +> NOTE: If you select an `Original Weight` tag that has a dynamic weight (`X`) and you do not specify a value in the LoRA syntax, it will default to `1`. + +### Save Presets + +The "Save Presets" button saves the text in the current text box. It is better to use a text editor, so use the "Open TextEditor" button to open a text editor, edit the text, and reload it. +The text box above the Weights setting is a list of currently available identifiers, useful for copying and pasting into an XY plot. 17 identifiers are required to appear in the list. + +### Fun Usage +Used in conjunction with the XY plot, it is possible to examine the impact of each level of the hierarchy. +![xy_grid-0017-4285963917](https://user-images.githubusercontent.com/122196982/215341315-493ce5f9-1d6e-4990-a38c-6937e78c6b46.jpg) + +The setting values are as follows. +NOT:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +INS:1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +IND:1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0 +INALL:1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0 +MIDD:1,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0 +OUTD:1,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0 +OUTS:1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1 +OUTALL:1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1 + +## XYZ Plotting Function +The optimal value can be searched by changing the value of each layer individually. +### Usage +Check "Active" to activate the function. If Script (such as XYZ plot in Automatic1111) is enabled, it will take precedence. +Hires. fix is not supported. batch size is fixed to 1. batch count should be set to 1. +Enter XYZ as the identifier of the LoRA that you want to change. It will work even if you do not enter a value corresponding to XYZ in the preset. If a value corresponding to XYZ is entered, that value will be used as the initial value. +Inputting ZYX, inverted value will be automatically inputted. +This feature enables to match weights of two LoRAs. +Inputing XYZ for LoRA1 and ZYX for LoRA2, you get, +LoRA1 1,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0 +LoRA2 0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1 +### Axis type +#### values +Sets the weight of the hierarchy to be changed. Enter the values separated by commas. 0,0.25,0.5,0.75,1", etc. + +#### Block ID +If a block ID is entered, only that block will change to the value specified by value. As with the other types, use commas to separate them. Multiple blocks can be changed at the same time by separating them with a space or hyphen. The initial NOT will invert the change, so NOT IN09-OUT02 will change all blocks except IN09-OUT02. + +#### seed +Seed changes, and is intended to be specified on the Z-axis. + +#### Original Weights +Specify the initial value to change the weight of each block. If Original Weight is enabled, the value entered for XYZ is ignored. + +### Input example +X : value, value : 1,0.25,0.5,0.75,1 +Y : Block ID, value : BASE,IN01-IN08,IN05-OUT05,OUT03-OUT11,NOT OUT03-OUT11 +Z : Original Weights, Value : NONE,ALL0.5,ALL + +In this case, an XY plot is created corresponding to the initial values NONE,ALL0.5,ALL. +If you select Seed for Z and enter -1,-1,-1, the XY plot will be created 3 times with different seeds. + +### Original Weights Combined XY Plot +If both X and Y are set to Original Weights then an XY plot is made by combining the weights. If both X and Y have a weight in the same block then the Y case is set to zero before adding the arrays, this value will be used during the YX case where X's value is then set to zero. The intended usage is without overlapping blocks. + +Given these names and values in the "Weights setting": +INS:1,1,1,0,0,0,0,0,0,0,0,0 +MID:1,0,0,0,0,1,0,0,0,0,0,0 +OUTD:1,0,0,0,0,0,1,1,1,0,0,0 + +With: +X : Original Weights, value: INS,MID,OUTD +Y : Original Weights, value: INS,MID,OUTD +Z : none + +An XY plot is made with 9 elements. The diagonal is the X values: INS,MID,OUTD unchanged. So we have for the first row: +``` +INS+INS = 1,1,1,0,0,0,0,0,0,0,0,0 (Just INS unchanged, first image on the diagonal) +MID+INS = 1,1,1,0,0,1,0,0,0,0,0,0 (second column of first row) +OUTD+INS = 1,1,1,0,0,0,1,1,1,0,0,0 (third column of first row) +``` + +Then the next row is INS+MID, MID+MID, OUTD+MID, and so on. Example image [here](https://user-images.githubusercontent.com/55250869/270830887-dff65f45-823a-4dbd-94c5-34d37c84a84f.jpg) + +### Effective Block Analyzer +This function check which layers are working well. The effect of the block is visualized and quantified by setting the intensity of the other bocks to 1, decreasing the intensity of the block you want to examine, and taking the difference. +#### Range +If you enter 0.5, 1, all initial values are set to 1, and only the target block is calculated as 0.5. Normally, 0.5 will make a difference, but some LoRAs may have difficulty making a difference, in which case, set 0.5 to 0 or a negative value. + +#### settings +##### diff color +Specify the background color of the diff file. + +##### chnage X-Y +Swaps the X and Y axes. By default, Block is assigned to the Y axis. + +##### Threshold +Sets the threshold at which a change is recognized when calculating the difference. Basically, the default value is fine, but if you want to detect subtle differences in color, etc., lower the value. + +#### Blocks +Enter the blocks to be examined, using the same format as for XYZ plots. + +Here is the English translation in Markdown format: + +### Guide for API users +#### Regular Usage +By default, Active is checked in the initial settings, so you can use it simply by installing it. You can use it by entering the format as instructed in the prompt. If executed, the phrase "LoRA Block Weight" will appear on the command prompt screen. If for some reason Active is not enabled, you can make it active by entering a value in the API for `"alwayson_scripts"`. +When you enable API mode and use the UI, two extensions will appear. Please use the one on the bottom. +The default presets can be used for presets. If you want to use your own presets, you can either edit the preset file or use the following format for the data passed to the API. + +The code that can be used when passing to the API in json format is as follows. The presets you enter here will become available. If you want to use multiple presets, please separate them with `\n`. + +```json +"prompt": "myprompt, ", +"alwayson_scripts": { + "LoRA Block Weight": { + "args": ["MYSETS:1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\nYOURSETS:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1", true, 1 ,"","","","","","","","","","","","","",""] + } +} +``` +#### XYZ Plot +Please use the format below. Please delete `"alwayson_scripts"` as it will cause an error. + +```json +"prompt": "myprompt, ", +"script_name":"LoRA Block Weight", +"script_args": ["XYZ:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", true, 1 ,"seed","-1,-1","","","","","","","","","","","",""] +``` +In this case, the six following `True,1` correspond to `xtype,xvalues,ytype,yvalues,ztype,zvalues`. It will be ignored if left blank. Please follow the instructions in the XYZ plot section for entering values. Even numbers should be enclosed in `""`. + +The following types are available. + +```json +"none","Block ID","values","seed","Original Weights","elements" +``` +#### Effective Block Analyzer +It can be used by using the following format. + +```json +"prompt": "myprompt, ", +"script_name":"LoRA Block Weight", +"script_args": ["XYZ:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", true, 2 ,"","","","","","","0,1","17ALL",1,"white",20,true,"",""] +``` +For `"0,1"`, specify the weight. If you specify `"17ALL"`, it will examine all the layers of the normal LoRA. If you want to specify individually, please write like `"BASE,IN00,IN01,IN02"`. Specify whether to reverse XY for `True` in the `"1"` for the number of times you want to check (if it is 2 or more, multiple seeds will be set), and `white` for the background color. + +#### Make Weights +In "make weights," you can create a weight list from a slider. When you press the "add to preset" button, the weight specified by the identifier is added to the end of the preset. If a preset with the same name already exists, it will be overwritten. The "add to preset and save" button allows you to save the preset simultaneously. +![makeweights](https://github.com/hako-mikan/sd-webui-lora-block-weight/assets/122196982/9f0f3c1f-d824-45a6-926d-e1b431d5ef61) + +# 概要 +Loraは強力なツールですが、時に扱いが難しく、影響してほしくないところにまで影響がでたりします。このスクリプトではLoraを適用する際、適用度合いをU-Netの階層ごとに設定することができます。これを使用することで求める画像に近づけることができるかもしれません。 + +## 使い方 +インストール時はWeb-ui.batを再起動をしてください。 + +### Active +ここにチェックを入れることで動作します。 + +### プロンプト +プロンプト画面では通常通り使用したいLoraを記入してください。その際、強さの値の次に「:」を入力しウェイトか識別子を入力します。識別子はWeights setting で編集します。 +``` +. +. (a1111-sd-webui-locon, etc.) + (a1111-sd-webui-lycoris, web-ui 1.5 or later) + (a1111-sd-webui-lycoris, web-ui 1.5 or later) + +``` +Loraの強さは有効で、階層全体にかかります。大文字と小文字は区別されます。 +LyCORISに対してLoRAのプリセットも使用できますが、その場合LoRAで使われていない階層のウェイトは0に設定されます。 +上記の形式になっていない場合プリセットではコメント行として扱われます。 +a1111-sd-webui-lycoris版のLyCORISや、ver1.5以降のweb-uiを使用する場合構文が異なります。`lbw=IN02`を使って下さい。順番は問いません。その他の書式はlycorisの書式にしたがって下さい。詳しくはLyCORISのドキュメントを参照して下さい。識別子を入力して下さい。a1111-sd-webui-lycoris版は開発途中のためこの構文は変更される可能性があります。 +### start, stop step +``と指定すると、指定したstepからLoRAの効果が現れます。 +``と指定することで指定したstepでLoRAの効果を無くします。キャラクターや構図LoRAの場合には10 step程度で十分な効果があり、ここで切ることで画風への影響を抑えることが可能です。 +``と指定するとstep 5-10の間のみLoRAが有効化します。 + +### Weights setting +識別子とウェイトを入力します。 +フルモデルと異なり、Loraではエンコーダーを含め17のブロックに分かれています。よって、17個の数値を入力してください。 +BASE,IN,OUTなどはフルモデル相当の階層です。 +フルモデルやLyCORIS、SDXLなど様々な形式があるため、現状では12,17,20,26のウェイトを受け付けます。基本的に形式が合わないウェイトを入力しても動作しますが、未入力の層は0として扱われます。 +|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17| +|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| +|BASE|IN01|IN02|IN04|IN05|IN07|IN08|MID|OUT03|OUT04|OUT05|OUT06|OUT07|OUT08|OUT09|OUT10|OUT11| + +LyCORISなどの場合(26) +|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26| +|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| +|BASE|IN00|IN01|IN02|IN03|IN04|IN05|IN06|IN07|IN08|IN09|IN10|IN11|MID|OUT00|OUT01|OUT02|OUT03|OUT04|OUT05|OUT06|OUT07|OUT08|OUT09|OUT10|OUT11| + +SDXL LoRAの場合(12) +|1|2|3|4|5|6|7|8|9|10|11|12| +|-|-|-|-|-|-|-|-|-|-|-|-| +|BASE|IN04|IN05|IN07|IN08|MID|OUT0|OUT1|OUT2|OUT3|OUT4|OUT05| + +SDXL - LyCORISの場合(20) +|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20| +|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| +|BASE|IN00|IN01|IN02|IN03|IN04|IN05|IN06|IN07|IN08||MID|OUT00|OUT01|OUT02|OUT03|OUT04|OUT05|OUT06|OUT07|OUT08| + +### 特別な値 +基本的には数値を入れないと正しく動きませんが R および U を入力することでランダムな数値が入力されます。 +R : 0~1までの小数点3桁の数値 +U : -1.5~1.5までの小数点3桁の数値 + +例えば ROUT:1,1,1,1,1,1,1,1,R,R,R,R,R,R,R,R,R とすると +OUT層のみダンダム化されます +ランダム化された数値は画像生成時にコマンドプロンプト画面に表示されます + +saveボタンで現在のテキストボックスのテキストを保存できます。テキストエディタを使った方がいいので、open Texteditorボタンでテキストエディタ開き、編集後reloadしてください。 +Weights settingの上にあるテキストボックスは現在使用できる識別子の一覧です。XYプロットにコピペするのに便利です。17個ないと一覧に表示されません。 + +### 楽しい使い方 +XY plotと併用することで各階層の影響を調べることが可能になります。 +![xy_grid-0017-4285963917](https://user-images.githubusercontent.com/122196982/215341315-493ce5f9-1d6e-4990-a38c-6937e78c6b46.jpg) + +設定値は以下の通りです。 +NOT:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +INS:1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0 +IND:1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0 +INALL:1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0 +MIDD:1,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0 +OUTD:1,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0 +OUTS:1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1 +OUTALL:1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1 + +## XYZ プロット機能 +各層の値を個別に変化させることで最適値を総当たりに探せます。 +### 使い方 +Activeをチェックすることで動作します。 Script(Automatic1111本体のXYZプロットなど)が有効になっている場合そちらが優先されます。noneを選択してください。 +Hires. fixには対応していません。Batch sizeは1に固定されます。Batch countは1に設定してください。 +変化させたいLoRAの識別子にXYZと入力します\。 プリセットにXYZに対応する値を入力していなくても動作します。その場合すべてのウェイトが0の状態からスタートします。XYZに対応する値が入力されている場合はその値が初期値になります。 +ZYXと入力するとXYZとは反対の値が入力されます。これはふたつのLoRAのウェイトを合わせる際に有効です。 +例えばLoRA1にXYZ,LoRA2にZYXと入力すると、 +LoRA1 1,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0 +LoRA2 0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1 +となります。 +### 軸タイプ +#### values +変化させる階層のウェイトを設定します。カンマ区切りで入力してください。「0,0.25,0.5,0.75,1」など。 + +#### Block ID +ブロックIDを入力すると、そのブロックのみvalueで指定した値に変わります。他のタイプと同様にカンマで区切ります。スペースまたはハイフンで区切ることで複数のブロックを同時に変化させることもできます。最初にNOTをつけることで変化対象が反転します。NOT IN09-OUT02とすると、IN09-OUT02以外が変化します。NOTは最初に入力しないと効果がありません。IN08-M00-OUT03は繋がっています。 + +#### Seed +シードが変わります。Z軸に指定することを想定しています。 + +#### Original Weights +各ブロックのウェイトを変化させる初期値を指定します。プリセットに登録されている識別子を入力してください。Original Weightが有効になっている場合XYZに入力された値は無視されます。 + +### Original Weightsの合算 +もしXとYが両方ともOriginal Weightsに設定されている場合、その重みを組み合わせてXYプロットが作成されます。XとYの両方が同じブロックに重みがある場合、配列を加算する前にYケースはゼロに設定されます。この値は、Xの値がゼロに設定されるYXケースで使用されます。意図されている使用方法は、重複するブロックなしでのものです。 + +"Weights setting"に以下の名前と値が与えられているとします: +INS:1,1,1,0,0,0,0,0,0,0,0,0 +MID:1,0,0,0,0,1,0,0,0,0,0,0 +OUTD:1,0,0,0,0,0,1,1,1,0,0,0 + +以下の設定で: +X : Original Weights, 値: INS,MID,OUTD +Y : Original Weights, 値: INS,MID,OUTD +Z : なし + +9つの要素を持つXYプロットが作成されます。対角線上は、変更されていないXの値:INS,MID,OUTDです。したがって、最初の行は以下のようになります: +``` +INS+INS = 1,1,1,0,0,0,0,0,0,0,0,0 (変更されていないINSだけ、対角線上の最初の画像) +MID+INS = 1,1,1,0,0,1,0,0,0,0,0,0 (最初の行の第2列) +OUTD+INS = 1,1,1,0,0,0,1,1,1,0,0,0 (最初の行の第3列) +``` + +次の行は、INS+MID、MID+MID、OUTD+MIDなどです。例の画像は[こちら](https://user-images.githubusercontent.com/55250869/270830887-dff65f45-823a-4dbd-94c5-34d37c84a84f.jpg)です。 + +### 入力例 +X : value, 値 : 1,0.25,0.5,0.75,1 +Y : Block ID, 値 : BASE,IN01-IN08,IN05-OUT05,OUT03-OUT11,NOT OUT03-OUT11 +Z : Original Weights, 値 : NONE,ALL0.5,ALL + +この場合、初期値NONE,ALL0.5,ALLに対応したXY plotが作製されます。 +ZにSeedを選び、-1,-1,-1を入力すると、異なるseedでXY plotを3回作製します。 + +### Effective Block Analyzer +どの階層が良く効いているかを判別する機能です。対象の階層以外の強度を1にして、調べたい階層の強度を下げ、差分を取ることで階層の効果を可視化・数値化します。 +#### Range +0.5, 1 と入力した場合、初期値がすべて1になり、対象のブロックのみ0.5として計算が行われます。普通は0.5で差がでますが、LoRAによっては差が出にくい場合があるので、その場合は0.5を0あるいはマイナスの値に設定してください。 + +#### 設定 +##### diff color +差分ファイルの背景カラーを指定します。 + +##### chnage X-Y +X軸とY軸を入れ替えます。デフォルトではY軸にBlockが割り当てられています。 + +##### Threshold +差分を計算する際の変化したと認識される閾値を設定します。基本的にはデフォルト値で問題ありませんが、微妙な色の違いなどを検出したい場合は値を下げて下さい。 + +#### Blocks +調べたい階層を入力します。XYZプロットと同じ書式が使用可能です。 + +階層別マージについては下記を参照してください + +### elemental +詳細は[こちら](https://github.com/hako-mikan/sd-webui-supermerger/blob/main/elemental_ja.md)を参照して下さい。 +#### 使い方 +Elementaタブにて階層指定と同じように識別子を設定します。識別子は階層の識別子の後に入力します。 +\ +ATTNON: + +書式は +識別子:階層指定:要素指定:ウェイト +のように指定します。要素は部分一致で判定されます。attn1ならattn1のみ、attnならattn1及びattn2が反応します。階層、要素共に空白で区切ると複数指定できます。 +print changeをオンにすると反応した要素がコマンドプロンプト上に表示されます。 + +ALL0:::0 +はすべての要素のウェイトをゼロに設定します。 +IN1:IN00-IN11::1 +はINのすべての要素を1にします +ATTNON::attn:1 +はすべての階層のattnを1にします。 + +#### XYZプロット +XYZプロットのelementsの項ではカンマ区切りでXYZプロットが可能になります。 +その場合は +\ +と指定して下さい。 +elements +の項に +IN05-OUT05:attn:0,IN05-OUT05:attn:0.5,IN05-OUT05:attn:1 +と入力して走らせるとIN05からOUT05までのattnのみを変化させることができます。 +この際、XYZの値を変更することで初期値を変更できます。デフォルトではelementalのXYZはXYZ:::1となっており、これは全階層、全要素を1にしますが、ここをXYZ:encoder::1とするとテキストエンコーダーのみを有効にした状態で評価ができます。 + +#### ウェイトの作成 +make weightsではスライダーからウェイトリストを作成できます。 +add to presetボタンを押すと、identiferで指定されたウェイトがプリセットの末尾に追加されます。 +すでに同じ名前のプリセットが存在する場合、上書きされます。 +add to preset and saveボタンでは同時にプリセットの保存が行われます。 +![makeweights](https://github.com/hako-mikan/sd-webui-lora-block-weight/assets/122196982/9f0f3c1f-d824-45a6-926d-e1b431d5ef61) + +### APIを通しての利用について +#### 通常利用 +初期設定でActiveはチェックされているのでインストールするだけで利用可能になります。 +プロンプトに書式通りに入力することで利用できます。実行された場合にはコマンドプロンプト画面に「LoRA Block Weight」の文字が現れます。 +何らかの理由でActiveになっていない場合にはAPIに投げる値のうち、`"alwayson_scripts"`に値を入力することでActiveにできます。 +APIモードを有効にしてUIを使うとき、拡張がふたつ表示されます。下の方を使って下さい。 +プリセットはデフォルトのプリセットが利用できます。独自のプリセットを利用したい場合にはプリセットファイルを編集するか、APIに受け渡すデータに対して下記の書式を利用して下さい。 +json形式でAPIに受け渡すときに使用できるコードです。ここで入力したプリセットが利用可能になります。複数のプリセットを利用したい場合には`\n`で区切って下さい。 + + "prompt": "myprompt, ", + "alwayson_scripts": { + "LoRA Block Weight": { + "args": ["MYSETS:1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\nYOURSETS:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1", True, 1 ,"","","","","","","","","","","","","",""] + } + } + +#### XYZ plot +下記の書式を利用して下さい。`"alwayson_scripts"`は消して下さいエラーになります。 +``` + "prompt": "myprompt, ", + "script_name":"LoRA Block Weight", + "script_args": ["XYZ:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", True, 1 ,"seed","-1,-1","","","","","","","","","","","",""] + +``` +この際、`True,1`に続く6個が`xtype,xvalues,ytype,yvalues,ztype,zvalues`に対応します。空白だと無視されます。入力する値などはXYZ plotの項に従って下さい。数字でもすべて`""`で囲って下さい。 +使用できるタイプは次の通りです。 +``` +"none","Block ID","values","seed","Original Weights","elements" +``` +#### Effective Block Analyzer +下記のような書式を使うことで使用できます。 +``` + "prompt": "myprompt, ", + "script_name":"LoRA Block Weight", + "script_args": ["XYZ:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1", True, 2 ,"","","","","","","0,1","17ALL",1,"white",20,True,"",""] + +``` +`"0,1"`にはウェイト。`"17ALL"`を指定すると普通のLoRAすべての階層を調べます。個別に指定したい場合は`"BASE,IN00,IN01,IN02"`のように記述して下さい。`1`には調べたい回数(2以上だと複数のシードを設定します),`white`には背景色,`True`にはXYを反転するかどうかを指定して下さい。 + +### updates/更新情報 +### 2023.10.26.2000(JST) +- bugfix:Effective block checker does not work correctly. +- bugfix:Does not work correctly when lora in memory is set to a value other than 0. + +### 2023.10.04.2000(JST) +XYZ plotに[新たな機能](#Original-Weightsの合算)が追加されました。[sometimesacoder](https://github.com/sometimesacoder)氏に感謝します。 +A [new feature](#Original-Weights-Combined-XY-Plot) was added to the XYZ plot. Many thanks to [sometimesacoder](https://github.com/sometimesacoder). + +### 2023.07.22.0030(JST) +- support SDXL +- support web-ui 1.5 +- support no buildin-LoRA system(lycoris required) + +to use with web-ui 1.5/web-ui1.5で使うときは +``` + + +``` + +### 2023.07.14.2000(JST) +- APIでXYZプロットが利用可能になりました +- [APIの利用方法](#apiを通しての利用について)を追記しました +- XYZ plot can be used in API +- Added [guide for API users](#guide-for-api-users) + +### 2023.5.24.2000(JST) +- changed directory for presets(extentions/sd-webui-lora-block-weight/scripts/) +- プリセットの保存フォルダがextentions/sd-webui-lora-block-weight/scripts/に変更になりました。 + +### 2023.5.12.2100(JST) +- changed syntax of lycoris +- lycorisの書式を変更しました + +### 2023.04.14.2000(JST) +- support LyCORIS(a1111-sd-webui-lycoris) +- LyCORIS(a1111-sd-webui-lycoris)に対応 + +### 2023.03.20.2030(JST) +- Comment lines can now be added to presets +- プリセットにコメント行を追加できるようになりました +- support XYZ plot hires.fix +- XYZプロットがhires.fixに対応しました + +### 2023.03.16.2030(JST) +- [LyCORIS](https://github.com/KohakuBlueleaf/LyCORIS)に対応しました +- Support [LyCORIS](https://github.com/KohakuBlueleaf/LyCORIS) + +別途[LyCORIS Extention](https://github.com/KohakuBlueleaf/a1111-sd-webui-locon)が必要です。 +For use LyCORIS, [Extension](https://github.com/KohakuBlueleaf/a1111-sd-webui-locon) for LyCORIS needed. + +### 2023.02.07 1250(JST) +- Changed behavior when XYZ plot Active (Script of the main UI is prioritized). + +### 2023.02.06 2000(JST) +- Feature added: XYZ plotting is added. + +### 2023.01.31 0200(JST) +- Feature added: Random feature is added +- Fixed: Weighting now works for negative values. + +### 2023.02.16 2040(JST) +- Original Weight をxやyに設定できない問題を解決しました +- Effective Weight Analyzer選択時にXYZのXやYがValuesとBlockIdになっていないとエラーになる問題を解決しました + +### 2023.02.08 2120(JST) +- 階層適応した後通常使用する際、階層適応が残る問題を解決しました +- 効果のある階層をワンクリックで判別する機能を追加しました + +### 2023.02.08 0050(JST) +- 一部環境でseedが固定されない問題を解決しました + +### 2023.02.07 2015(JST) +- マイナスのウェイトが正常に働かない問題を修正しました + +### 2023.02.07 1250(JST) +- XYZプロットActive時の動作を変更しました(本体のScriptが優先されるようになります) + +### 2023.02.06 2000(JST) +- 機能追加:XYZプロット機能を追加しました + +### 2023.01.31 0200(JST) +- 機能追加:ランダム機能を追加しました +- 機能修正:ウェイトがマイナスにも効くようになりました diff --git a/extensions/CHECK/sd-webui-lora-block-weight/scripts/Roboto-Regular.ttf b/extensions/CHECK/sd-webui-lora-block-weight/scripts/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..500b1045b0c94d83d2e6798aaf1faa55a2dab6fc Binary files /dev/null and b/extensions/CHECK/sd-webui-lora-block-weight/scripts/Roboto-Regular.ttf differ diff --git a/extensions/CHECK/sd-webui-lora-block-weight/scripts/__pycache__/lora_block_weight.cpython-310.pyc b/extensions/CHECK/sd-webui-lora-block-weight/scripts/__pycache__/lora_block_weight.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d9555f37caec1e4050a79a0473a3df6e41e4388 Binary files /dev/null and b/extensions/CHECK/sd-webui-lora-block-weight/scripts/__pycache__/lora_block_weight.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-lora-block-weight/scripts/elempresets.txt b/extensions/CHECK/sd-webui-lora-block-weight/scripts/elempresets.txt new file mode 100644 index 0000000000000000000000000000000000000000..9f12614fb0efa5b29ba9f962b1b5f0050e2d0d8f --- /dev/null +++ b/extensions/CHECK/sd-webui-lora-block-weight/scripts/elempresets.txt @@ -0,0 +1,7 @@ +ATTNDEEPON:IN05-OUT05:attn:1 + +ATTNDEEPOFF:IN05-OUT05:attn:0 + +PROJDEEPOFF:IN05-OUT05:proj:0 + +XYZ:::1 \ No newline at end of file diff --git a/extensions/CHECK/sd-webui-lora-block-weight/scripts/lbwpresets.txt b/extensions/CHECK/sd-webui-lora-block-weight/scripts/lbwpresets.txt new file mode 100644 index 0000000000000000000000000000000000000000..99373d55db50d2e063c0912edeb6caf3aba22a48 --- /dev/null +++ b/extensions/CHECK/sd-webui-lora-block-weight/scripts/lbwpresets.txt @@ -0,0 +1,10 @@ +NONE:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +INS:1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0 +IND:1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0 +INALL:1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0 +MIDD:1,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0 +OUTD:1,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0 +OUTS:1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1 +OUTALL:1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1 +ALL0.5:0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5 \ No newline at end of file diff --git a/extensions/CHECK/sd-webui-lora-block-weight/scripts/lora_block_weight.py b/extensions/CHECK/sd-webui-lora-block-weight/scripts/lora_block_weight.py new file mode 100644 index 0000000000000000000000000000000000000000..d766cff0392b70af7c7c3a6c78c3a6ee669d845a --- /dev/null +++ b/extensions/CHECK/sd-webui-lora-block-weight/scripts/lora_block_weight.py @@ -0,0 +1,1255 @@ +import cv2 +import json +import os +import gc +import re +import sys +import torch +import shutil +import math +import importlib +import numpy as np +import gradio as gr +import os.path +import random +from pprint import pprint +import modules.ui +import modules.scripts as scripts +from PIL import Image, ImageFont, ImageDraw +import modules.shared as shared +from modules import devices, sd_models, images,cmd_args, extra_networks, sd_hijack +from modules.shared import cmd_opts, opts, state +from modules.processing import process_images, Processed +from modules.script_callbacks import CFGDenoiserParams, on_cfg_denoiser + +LBW_T = "customscript/lora_block_weight.py/txt2img/Active/value" +LBW_I = "customscript/lora_block_weight.py/img2img/Active/value" + +if os.path.exists(cmd_opts.ui_config_file): + with open(cmd_opts.ui_config_file, 'r', encoding="utf-8") as json_file: + ui_config = json.load(json_file) +else: + print("ui config file not found, using default values") + ui_config = {} + +startup_t = ui_config[LBW_T] if LBW_T in ui_config else None +startup_i = ui_config[LBW_I] if LBW_I in ui_config else None +active_t = "Active" if startup_t else "Not Active" +active_i = "Active" if startup_i else "Not Active" + +lxyz = "" +lzyx = "" +prompts = "" +xyelem = "" +princ = False + +try: + from ldm_patched.modules import model_management + forge = True +except: + forge = False + +BLOCKID26=["BASE","IN00","IN01","IN02","IN03","IN04","IN05","IN06","IN07","IN08","IN09","IN10","IN11","M00","OUT00","OUT01","OUT02","OUT03","OUT04","OUT05","OUT06","OUT07","OUT08","OUT09","OUT10","OUT11"] +BLOCKID17=["BASE","IN01","IN02","IN04","IN05","IN07","IN08","M00","OUT03","OUT04","OUT05","OUT06","OUT07","OUT08","OUT09","OUT10","OUT11"] +BLOCKID12=["BASE","IN04","IN05","IN07","IN08","M00","OUT00","OUT01","OUT02","OUT03","OUT04","OUT05"] +BLOCKID20=["BASE","IN00","IN01","IN02","IN03","IN04","IN05","IN06","IN07","IN08","M00","OUT00","OUT01","OUT02","OUT03","OUT04","OUT05","OUT06","OUT07","OUT08"] +BLOCKNUMS = [12,17,20,26] +BLOCKIDS=[BLOCKID12,BLOCKID17,BLOCKID20,BLOCKID26] + +BLOCKS=["encoder", +"diffusion_model_input_blocks_0_", +"diffusion_model_input_blocks_1_", +"diffusion_model_input_blocks_2_", +"diffusion_model_input_blocks_3_", +"diffusion_model_input_blocks_4_", +"diffusion_model_input_blocks_5_", +"diffusion_model_input_blocks_6_", +"diffusion_model_input_blocks_7_", +"diffusion_model_input_blocks_8_", +"diffusion_model_input_blocks_9_", +"diffusion_model_input_blocks_10_", +"diffusion_model_input_blocks_11_", +"diffusion_model_middle_block_", +"diffusion_model_output_blocks_0_", +"diffusion_model_output_blocks_1_", +"diffusion_model_output_blocks_2_", +"diffusion_model_output_blocks_3_", +"diffusion_model_output_blocks_4_", +"diffusion_model_output_blocks_5_", +"diffusion_model_output_blocks_6_", +"diffusion_model_output_blocks_7_", +"diffusion_model_output_blocks_8_", +"diffusion_model_output_blocks_9_", +"diffusion_model_output_blocks_10_", +"diffusion_model_output_blocks_11_", +"embedders", +"transformer_resblocks"] + +loopstopper = True + +ATYPES =["none","Block ID","values","seed","Original Weights","elements"] + +DEF_WEIGHT_PRESET = "\ +NONE:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\n\ +ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1\n\ +INS:1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0\n\ +IND:1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0\n\ +INALL:1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0\n\ +MIDD:1,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0\n\ +OUTD:1,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0\n\ +OUTS:1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1\n\ +OUTALL:1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1\n\ +ALL0.5:0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5" + +scriptpath = os.path.dirname(os.path.abspath(__file__)) + +class Script(modules.scripts.Script): + def __init__(self): + self.log = {} + self.stops = {} + self.starts = {} + self.active = False + self.lora = {} + self.lycoris = {} + self.networks = {} + + self.stopsf = [] + self.startsf = [] + self.uf = [] + self.lf = [] + self.ef = [] + + def title(self): + return "LoRA Block Weight" + + def show(self, is_img2img): + return modules.scripts.AlwaysVisible + + def ui(self, is_img2img): + LWEIGHTSPRESETS = DEF_WEIGHT_PRESET + + runorigin = scripts.scripts_txt2img.run + runorigini = scripts.scripts_img2img.run + + scriptpath = os.path.dirname(os.path.abspath(__file__)) + path_root = scripts.basedir() + + extpath = os.path.join(scriptpath, "lbwpresets.txt") + extpathe = os.path.join(scriptpath, "elempresets.txt") + filepath = os.path.join(path_root,"scripts", "lbwpresets.txt") + filepathe = os.path.join(path_root,"scripts", "elempresets.txt") + + if os.path.isfile(filepath) and not os.path.isfile(extpath): + shutil.move(filepath,extpath) + + if os.path.isfile(filepathe) and not os.path.isfile(extpathe): + shutil.move(filepathe,extpathe) + + lbwpresets="" + + try: + with open(extpath,encoding="utf-8") as f: + lbwpresets = f.read() + except OSError as e: + lbwpresets=LWEIGHTSPRESETS + if not os.path.isfile(extpath): + try: + with open(extpath,mode = 'w',encoding="utf-8") as f: + f.write(lbwpresets) + except: + pass + + try: + with open(extpathe,encoding="utf-8") as f: + elempresets = f.read() + except OSError as e: + elempresets=ELEMPRESETS + if not os.path.isfile(extpathe): + try: + with open(extpathe,mode = 'w',encoding="utf-8") as f: + f.write(elempresets) + except: + pass + + loraratios=lbwpresets.splitlines() + lratios={} + for i,l in enumerate(loraratios): + if checkloadcond(l) : continue + lratios[l.split(":")[0]]=l.split(":")[1] + ratiostags = [k for k in lratios.keys()] + ratiostags = ",".join(ratiostags) + + if os.environ.get('IGNORE_CMD_ARGS_ERRORS', None) is None: + args = cmd_args.parser.parse_args() + else: + args, _ = cmd_args.parser.parse_known_args() + if args.api: + register() + + with gr.Accordion(f"LoRA Block Weight : {active_i if is_img2img else active_t}",open = False) as acc: + with gr.Row(): + with gr.Column(min_width = 50, scale=1): + lbw_useblocks = gr.Checkbox(value = True,label="Active",interactive =True,elem_id="lbw_active") + debug = gr.Checkbox(value = False,label="Debug",interactive =True,elem_id="lbw_debug") + with gr.Column(scale=5): + bw_ratiotags= gr.TextArea(label="",value=ratiostags,visible =True,interactive =True,elem_id="lbw_ratios") + with gr.Accordion("XYZ plot",open = False): + gr.HTML(value='

changeable blocks : BASE,IN00,IN01,IN02,IN03,IN04,IN05,IN06,IN07,IN08,IN09,IN10,IN11,M00,OUT00,OUT01,OUT02,OUT03,OUT04,OUT05,OUT06,OUT07,OUT08,OUT09,OUT10,OUT11

') + xyzsetting = gr.Radio(label = "Active",choices = ["Disable","XYZ plot","Effective Block Analyzer"], value ="Disable",type = "index") + with gr.Row(visible = False) as esets: + diffcol = gr.Radio(label = "diff image color",choices = ["black","white"], value ="black",type = "value",interactive =True) + revxy = gr.Checkbox(value = False,label="change X-Y",interactive =True,elem_id="lbw_changexy") + thresh = gr.Textbox(label="difference threshold",lines=1,value="20",interactive =True,elem_id="diff_thr") + xtype = gr.Dropdown(label="X Types", choices=[x for x in ATYPES], value=ATYPES [2],interactive =True,elem_id="lbw_xtype") + xmen = gr.Textbox(label="X Values",lines=1,value="0,0.25,0.5,0.75,1",interactive =True,elem_id="lbw_xmen") + ytype = gr.Dropdown(label="Y Types", choices=[y for y in ATYPES], value=ATYPES [1],interactive =True,elem_id="lbw_ytype") + ymen = gr.Textbox(label="Y Values" ,lines=1,value="IN05-OUT05",interactive =True,elem_id="lbw_ymen") + ztype = gr.Dropdown(label="Z type", choices=[z for z in ATYPES], value=ATYPES[0],interactive =True,elem_id="lbw_ztype") + zmen = gr.Textbox(label="Z values",lines=1,value="",interactive =True,elem_id="lbw_zmen") + + exmen = gr.Textbox(label="Range",lines=1,value="0.5,1",interactive =True,elem_id="lbw_exmen",visible = False) + eymen = gr.Textbox(label="Blocks (12ALL,17ALL,20ALL,26ALL also can be used)" ,lines=1,value="BASE,IN00,IN01,IN02,IN03,IN04,IN05,IN06,IN07,IN08,IN09,IN10,IN11,M00,OUT00,OUT01,OUT02,OUT03,OUT04,OUT05,OUT06,OUT07,OUT08,OUT09,OUT10,OUT11",interactive =True,elem_id="lbw_eymen",visible = False) + ecount = gr.Number(value=1, label="number of seed", interactive=True, visible = True) + + with gr.Accordion("Weights setting",open = True): + with gr.Row(): + reloadtext = gr.Button(value="Reload Presets",variant='primary',elem_id="lbw_reload") + reloadtags = gr.Button(value="Reload Tags",variant='primary',elem_id="lbw_reload") + savetext = gr.Button(value="Save Presets",variant='primary',elem_id="lbw_savetext") + openeditor = gr.Button(value="Open TextEditor",variant='primary',elem_id="lbw_openeditor") + lbw_loraratios = gr.TextArea(label="",value=lbwpresets,visible =True,interactive = True,elem_id="lbw_ratiospreset") + + with gr.Accordion("Elemental",open = False): + with gr.Row(): + e_reloadtext = gr.Button(value="Reload Presets",variant='primary',elem_id="lbw_reload") + e_savetext = gr.Button(value="Save Presets",variant='primary',elem_id="lbw_savetext") + e_openeditor = gr.Button(value="Open TextEditor",variant='primary',elem_id="lbw_openeditor") + elemsets = gr.Checkbox(value = False,label="print change",interactive =True,elem_id="lbw_print_change") + elemental = gr.TextArea(label="Identifer:BlockID:Elements:Ratio,...,separated by empty line ",value = elempresets,interactive =True,elem_id="element") + + d_true = gr.Checkbox(value = True,visible = False) + d_false = gr.Checkbox(value = False,visible = False) + + with gr.Accordion("Make Weights",open = False): + with gr.Row(): + m_text = gr.Textbox(value="",label="Weights") + with gr.Row(): + m_add = gr.Button(value="Add to presets",size="sm",variant='primary') + m_add_save = gr.Button(value="Add to presets and Save",size="sm",variant='primary') + m_name = gr.Textbox(value="",label="Identifier") + with gr.Row(): + m_type = gr.Radio(label="Weights type",choices=["17(1.X/2.X)", "26(1.X/2.X full)", "12(XL)","20(XL full)"], value="17(1.X/2.X)") + with gr.Row(): + m_set_0 = gr.Button(value="Set All 0",variant='primary') + m_set_1 = gr.Button(value="Set All 1",variant='primary') + m_custom = gr.Button(value="Set custom",variant='primary') + m_custom_v = gr.Slider(show_label=False, minimum=-1.0, maximum=1, step=0.1, value=0, interactive=True) + with gr.Row(): + with gr.Column(scale=1, min_width=100): + gr.Slider(visible=False) + with gr.Column(scale=2, min_width=200): + base = gr.Slider(label="BASE", minimum=-1, maximum=1, step=0.1, value=0.0) + with gr.Column(scale=1, min_width=100): + gr.Slider(visible=False) + with gr.Row(): + with gr.Column(scale=2, min_width=200): + ins = [gr.Slider(label=block, minimum=-1.0, maximum=1, step=0.1, value=0, interactive=True) for block in BLOCKID26[1:13]] + with gr.Column(scale=2, min_width=200): + outs = [gr.Slider(label=block, minimum=-1.0, maximum=1, step=0.1, value=0, interactive=True) for block in reversed(BLOCKID26[14:])] + with gr.Row(): + with gr.Column(scale=1, min_width=100): + gr.Slider(visible=False) + with gr.Column(scale=2, min_width=200): + m00 = gr.Slider(label="M00", minimum=-1, maximum=1, step=0.1, value=0.0) + with gr.Column(scale=1, min_width=100): + gr.Slider(visible=False) + + blocks = [base] + ins + [m00] + outs[::-1] + for block in blocks: + if block.label not in BLOCKID17: + block.visible = False + + m_set_0.click(fn=lambda x:[0]*26 + [",".join(["0"]*int(x[:2]))],inputs=[m_type],outputs=blocks + [m_text]) + m_set_1.click(fn=lambda x:[1]*26 + [",".join(["1"]*int(x[:2]))],inputs=[m_type],outputs=blocks + [m_text]) + m_custom.click(fn=lambda x,y:[x]*26 + [",".join([str(x)]*int(y[:2]))],inputs=[m_custom_v,m_type],outputs=blocks + [m_text]) + + def addweights(weights, id, presets, save = False): + if id == "":id = "NONAME" + lines = presets.strip().split("\n") + id_found = False + for i, line in enumerate(lines): + if line.startswith("#"): + continue + if line.split(":")[0] == id: + lines[i] = f"{id}:{weights}" + id_found = True + break + if not id_found: + lines.append(f"{id}:{weights}") + + if save: + with open(extpath,mode = 'w',encoding="utf-8") as f: + f.write("\n".join(lines)) + + return "\n".join(lines) + + def changetheblocks(sdver,*blocks): + sdver = int(sdver[:2]) + output = [] + targ_blocks = BLOCKIDS[BLOCKNUMS.index(sdver)] + for i, block in enumerate(BLOCKID26): + if block in targ_blocks: + output.append(str(blocks[i])) + return [",".join(output)] + [gr.update(visible = True if block in targ_blocks else False) for block in BLOCKID26] + + m_add.click(fn=addweights, inputs=[m_text,m_name,lbw_loraratios],outputs=[lbw_loraratios]) + m_add_save.click(fn=addweights, inputs=[m_text,m_name,lbw_loraratios, d_true],outputs=[lbw_loraratios]) + m_type.change(fn=changetheblocks, inputs=[m_type] + blocks,outputs=[m_text] + blocks) + + d_true = gr.Checkbox(value = True,visible = False) + d_false = gr.Checkbox(value = False,visible = False) + + lbw_useblocks.change(fn=lambda x:gr.update(label = f"LoRA Block Weight : {'Active' if x else 'Not Active'}"),inputs=lbw_useblocks, outputs=[acc]) + + def makeweights(sdver, *blocks): + sdver = int(sdver[:2]) + output = [] + targ_blocks = BLOCKIDS[BLOCKNUMS.index(sdver)] + for i, block in enumerate(BLOCKID26): + if block in targ_blocks: + output.append(str(blocks[i])) + return ",".join(output) + + changes = [b.release(fn=makeweights,inputs=[m_type] + blocks,outputs=[m_text]) for b in blocks] + + import subprocess + def openeditors(b): + path = extpath if b else extpathe + subprocess.Popen(['start', path], shell=True) + + def reloadpresets(isweight): + if isweight: + try: + with open(extpath,encoding="utf-8") as f: + return f.read() + except OSError as e: + pass + else: + try: + with open(extpath,encoding="utf-8") as f: + return f.read() + except OSError as e: + pass + + def tagdicter(presets): + presets=presets.splitlines() + wdict={} + for l in presets: + if checkloadcond(l) : continue + w=[] + if ":" in l : + key = l.split(":",1)[0] + w = l.split(":",1)[1] + if any(len([w for w in w.split(",")]) == x for x in BLOCKNUMS): + wdict[key.strip()]=w + return ",".join(list(wdict.keys())) + + def savepresets(text,isweight): + if isweight: + with open(extpath,mode = 'w',encoding="utf-8") as f: + f.write(text) + else: + with open(extpathe,mode = 'w',encoding="utf-8") as f: + f.write(text) + + reloadtext.click(fn=reloadpresets,inputs=[d_true],outputs=[lbw_loraratios]) + reloadtags.click(fn=tagdicter,inputs=[lbw_loraratios],outputs=[bw_ratiotags]) + savetext.click(fn=savepresets,inputs=[lbw_loraratios,d_true],outputs=[]) + openeditor.click(fn=openeditors,inputs=[d_true],outputs=[]) + + e_reloadtext.click(fn=reloadpresets,inputs=[d_false],outputs=[elemental]) + e_savetext.click(fn=savepresets,inputs=[elemental,d_false],outputs=[]) + e_openeditor.click(fn=openeditors,inputs=[d_false],outputs=[]) + + def urawaza(active): + if active > 0: + register() + scripts.scripts_txt2img.run = newrun + scripts.scripts_img2img.run = newrun + if active == 1:return [*[gr.update(visible = True) for x in range(6)],*[gr.update(visible = False) for x in range(4)]] + else:return [*[gr.update(visible = False) for x in range(6)],*[gr.update(visible = True) for x in range(4)]] + else: + scripts.scripts_txt2img.run = runorigin + scripts.scripts_img2img.run = runorigini + return [*[gr.update(visible = True) for x in range(6)],*[gr.update(visible = False) for x in range(4)]] + + xyzsetting.change(fn=urawaza,inputs=[xyzsetting],outputs =[xtype,xmen,ytype,ymen,ztype,zmen,exmen,eymen,ecount,esets]) + + return lbw_loraratios,lbw_useblocks,xyzsetting,xtype,xmen,ytype,ymen,ztype,zmen,exmen,eymen,ecount,diffcol,thresh,revxy,elemental,elemsets,debug + + def process(self, p, loraratios,useblocks,xyzsetting,xtype,xmen,ytype,ymen,ztype,zmen,exmen,eymen,ecount,diffcol,thresh,revxy,elemental,elemsets,debug): + #print("self =",self,"p =",p,"presets =",loraratios,"useblocks =",useblocks,"xyzsettings =",xyzsetting,"xtype =",xtype,"xmen =",xmen,"ytype =",ytype,"ymen =",ymen,"ztype =",ztype,"zmen =",zmen) + #Note that this does not use the default arg syntax because the default args are supposed to be at the end of the function + if(loraratios == None): + loraratios = DEF_WEIGHT_PRESET + if(useblocks == None): + useblocks = True + + lorachecker(self) + self.log["enable LBW"] = useblocks + self.log["registerd"] = registerd + + if useblocks: + self.active = True + loraratios=loraratios.splitlines() + elemental = elemental.split("\n\n") if elemental is not None else [] + lratios={} + elementals={} + for l in loraratios: + if checkloadcond(l) : continue + l0=l.split(":",1)[0] + lratios[l0.strip()]=l.split(":",1)[1] + for e in elemental: + if ":" not in e: continue + e0=e.split(":",1)[0] + elementals[e0.strip()]=e.split(":",1)[1] + if elemsets : print(xyelem) + if xyzsetting and "XYZ" in p.prompt: + lratios["XYZ"] = lxyz + lratios["ZYX"] = lzyx + if xyelem != "": + if "XYZ" in elementals.keys(): + elementals["XYZ"] = elementals["XYZ"] + ","+ xyelem + else: + elementals["XYZ"] = xyelem + self.lratios = lratios + self.elementals = elementals + global princ + princ = elemsets + + if not hasattr(self,"lbt_dr_callbacks"): + self.lbt_dr_callbacks = on_cfg_denoiser(self.denoiser_callback) + + def denoiser_callback(self, params: CFGDenoiserParams): + def setparams(self, key, te, u ,sets): + for dicts in [self.lora,self.lycoris,self.networks]: + for lora in dicts: + if lora.name.split("_in_LBW_")[0] == key: + lora.te_multiplier = te + lora.unet_multiplier = u + sets.append(key) + + if forge and self.active: + if params.sampling_step in self.startsf: + shared.sd_model.forge_objects.unet.unpatch_model(device_to=devices.device) + for key, vals in shared.sd_model.forge_objects.unet.patches.items(): + n_vals = [] + lvals = [val for val in vals if val[1][0] in LORAS] + for s, v, m, l, e in zip(self.startsf, lvals, self.uf, self.lf, self.ef): + if s is not None and s == params.sampling_step: + ratio, errormodules = ratiodealer(key.replace(".","_"), l, e) + n_vals.append((ratio * m, *v[1:])) + else: + n_vals.append(v) + shared.sd_model.forge_objects.unet.patches[key] = n_vals + shared.sd_model.forge_objects.unet.patch_model() + + if params.sampling_step in self.stopsf: + shared.sd_model.forge_objects.unet.unpatch_model(device_to=devices.device) + for key, vals in shared.sd_model.forge_objects.unet.patches.items(): + n_vals = [] + lvals = [val for val in vals if val[1][0] in LORAS] + for s, v, m, l, e in zip(self.stopsf, lvals, self.uf, self.lf, self.ef): + if s is not None and s == params.sampling_step: + n_vals.append((0, *v[1:])) + else: + n_vals.append(v) + shared.sd_model.forge_objects.unet.patches[key] = n_vals + shared.sd_model.forge_objects.unet.patch_model() + + elif self.active: + if self.starts and params.sampling_step == 0: + for key, step_te_u in self.starts.items(): + setparams(self, key, 0, 0, []) + #print("\nstart 0", self, key, 0, 0, []) + + if self.starts: + sets = [] + for key, step_te_u in self.starts.items(): + step, te, u = step_te_u + if params.sampling_step > step - 2: + setparams(self, key, te, u, sets) + #print("\nstart", self, key, u, te, sets) + for key in sets: + if key in self.starts: + del self.starts[key] + + if self.stops: + sets = [] + for key, step in self.stops.items(): + if params.sampling_step > step - 2: + setparams(self, key, 0, 0, sets) + #print("\nstop", self, key, 0, 0, sets) + for key in sets: + if key in self.stops: + del self.stops[key] + + def before_process_batch(self, p, loraratios,useblocks,*args,**kwargs): + if useblocks: + resetmemory() + if not self.isnet: p.disable_extra_networks = False + global prompts + prompts = kwargs["prompts"].copy() + + def process_batch(self, p, loraratios,useblocks,*args,**kwargs): + if useblocks: + if not self.isnet: p.disable_extra_networks = True + + o_prompts = [p.prompt] + for prompt in prompts: + if "0: + lorachecker(self) + lora = importer(self) + loraratios=presets.splitlines() + lratios={} + for l in loraratios: + if checkloadcond(l) : continue + l0=l.split(":",1)[0] + lratios[l0.strip()]=l.split(":",1)[1] + + if "XYZ" in p.prompt: + base = lratios["XYZ"] if "XYZ" in lratios.keys() else "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" + else: return + + for i, all in enumerate(["12ALL","17ALL","20ALL","26ALL"]): + if eymen == all: + eymen = ",".join(BLOCKIDS[i]) + + if xyzsetting > 1: + xmen,ymen = exmen,eymen + xtype,ytype = "values","ID" + ebase = xmen.split(",")[1] + ebase = [ebase.strip()]*26 + base = ",".join(ebase) + ztype = "" + if ecount > 1: + ztype = "seed" + zmen = ",".join([str(random.randrange(4294967294)) for x in range(int(ecount))]) + + #ATYPES =["none","Block ID","values","seed","Base Weights"] + + def dicedealer(am): + for i,a in enumerate(am): + if a =="-1": am[i] = str(random.randrange(4294967294)) + print(f"the die was thrown : {am}") + + if p.seed == -1: p.seed = str(random.randrange(4294967294)) + + #print(f"xs:{xmen},ys:{ymen},zs:{zmen}") + + def adjuster(a,at): + if "none" in at:a = "" + a = [a.strip() for a in a.split(',')] + if "seed" in at:dicedealer(a) + return a + + xs = adjuster(xmen,xtype) + ys = adjuster(ymen,ytype) + zs = adjuster(zmen,ztype) + + ids = alpha =seed = "" + p.batch_size = 1 + + print(f"xs:{xs},ys:{ys},zs:{zs}") + + images = [] + + def weightsdealer(alpha,ids,base): + #print(f"weights from : {base}") + ids = [z.strip() for z in ids.split(' ')] + weights_t = [w.strip() for w in base.split(',')] + blockid = BLOCKIDS[BLOCKNUMS.index(len(weights_t))] + if ids[0]!="NOT": + flagger=[False]*len(weights_t) + changer = True + else: + flagger=[True]*len(weights_t) + changer = False + for id in ids: + if id =="NOT":continue + if "-" in id: + it = [it.strip() for it in id.split('-')] + if blockid.index(it[1]) > blockid.index(it[0]): + flagger[blockid.index(it[0]):blockid.index(it[1])+1] = [changer]*(blockid.index(it[1])-blockid.index(it[0])+1) + else: + flagger[blockid.index(it[1]):blockid.index(it[0])+1] = [changer]*(blockid.index(it[0])-blockid.index(it[1])+1) + else: + flagger[blockid.index(id)] =changer + for i,f in enumerate(flagger): + if f:weights_t[i]=alpha + outext = ",".join(weights_t) + #print(f"weights changed: {outext}") + return outext + + generatedbases=[] + def xyzdealer(a,at): + nonlocal ids,alpha,p,base,c_base,generatedbases + if "ID" in at:return + if "values" in at:alpha = a + if "seed" in at: + p.seed = int(a) + generatedbases=[] + if "Weights" in at:base =c_base = lratios[a] + if "elements" in at: + global xyelem + xyelem = a + + def imagedupewatcher(baselist,basetocheck,currentiteration): + for idx,alreadygenerated in enumerate(baselist): + if (basetocheck == alreadygenerated): + # E.g., we already generated IND+OUTS and this is now OUTS+IND with identical weights. + baselist.insert(currentiteration-1, basetocheck) + return idx + return -1 + + def strThree(someNumber): # Returns 1.12345 as 1.123 and 1.0000 as 1 + return format(someNumber, ".3f").rstrip('0').rstrip('.') + + # Adds X and Y together using array addition. + # If both X and Y have a value in the same block then Y's is set to 0; + # both values are used due to both XY and YX being generated, but the diagonal then only show the first value. + # imagedupwatcher prevents duplicate images from being generated; + # when X and Y have non-overlapping blocks then the upper triangular images are identical to the lower ones. + def xyoriginalweightsdealer(x,y): + xweights = np.asarray(lratios[x].split(','), dtype=np.float32) # np array easier to add later + yweights = np.asarray(lratios[y].split(','), dtype=np.float32) + for idx,xval in np.ndenumerate(xweights): + yval = yweights[idx] + if xval != 0 and yval != 0: + yweights[idx] = 0 + # Add xweights to yweights, round to 3 places, + # map floats to string with format of 3 decimals trailing zeroes and decimal stripped + baseListToStrings = list(map(strThree, np.around(np.add(xweights,yweights,),3).tolist())) + return ",".join(baseListToStrings) + + grids = [] + images =[] + + totalcount = len(xs)*len(ys)*len(zs) if xyzsetting < 2 else len(xs)*len(ys)*len(zs) //2 +1 + shared.total_tqdm.updateTotal(totalcount) + xc = yc =zc = 0 + state.job_count = totalcount + totalcount = len(xs)*len(ys)*len(zs) + c_base = base + + for z in zs: + generatedbases=[] + images = [] + yc = 0 + xyzdealer(z,ztype) + for y in ys: + xc = 0 + xyzdealer(y,ytype) + for x in xs: + xyzdealer(x,xtype) + if "Weights" in xtype and "Weights" in ytype: + c_base = xyoriginalweightsdealer(x,y) + else: + if "ID" in xtype: + if "values" in ytype:c_base = weightsdealer(y,x,base) + if "values" in ztype:c_base = weightsdealer(z,x,base) + if "ID" in ytype: + if "values" in xtype:c_base = weightsdealer(x,y,base) + if "values" in ztype:c_base = weightsdealer(z,y,base) + if "ID" in ztype: + if "values" in xtype:c_base = weightsdealer(x,z,base) + if "values" in ytype:c_base = weightsdealer(y,z,base) + + iteration = len(xs)*len(ys)*zc + yc*len(xs) +xc +1 + print(f"X:{xtype}, {x},Y: {ytype},{y}, Z:{ztype},{z}, base:{c_base} ({iteration}/{totalcount})") + + dupe_index = imagedupewatcher(generatedbases,c_base,iteration) + if dupe_index > -1: + print(f"Skipping generation of duplicate base:{c_base}") + images.append(images[dupe_index].copy()) + xc += 1 + continue + + global lxyz,lzyx + lxyz = c_base + + cr_base = c_base.split(",") + cr_base_t=[] + for x in cr_base: + if not identifier(x): + cr_base_t.append(str(1-float(x))) + else: + cr_base_t.append(x) + lzyx = ",".join(cr_base_t) + + if not(xc == 1 and not (yc ==0 ) and xyzsetting >1): + lora.loaded_loras.clear() + p.cached_c = [None,None] + p.cached_uc = [None,None] + p.cached_hr_c = [None, None] + p.cached_hr_uc = [None, None] + processed:Processed = process_images(p) + images.append(processed.images[0]) + generatedbases.insert(iteration-1, c_base) + xc += 1 + yc += 1 + zc += 1 + origin = loranames(processed.all_prompts) + ", "+ znamer(ztype,z,base) + images,xst,yst = effectivechecker(images,xs.copy(),ys.copy(),diffcol,thresh,revxy) if xyzsetting >1 else (images,xs.copy(),ys.copy()) + grids.append(smakegrid(images,xst,yst,origin,p)) + processed.images= grids + lora.loaded_loras.clear() + return processed + +def identifier(char): + return char[0] in ["R", "U", "X"] + +def znamer(at,a,base): + if "ID" in at:return f"Block : {a}" + if "values" in at:return f"value : {a}" + if "seed" in at:return f"seed : {a}" + if "Weights" in at:return f"original weights :\n {base}" + else: return "" + +def loranames(all_prompts): + _, extra_network_data = extra_networks.parse_prompts(all_prompts[0:1]) + calledloras = extra_network_data["lora"] if "lyco" not in extra_network_data.keys() else extra_network_data["lyco"] + names = "" + for called in calledloras: + if len(called.items) <3:continue + names += called.items[0] + return names + +def lorachecker(self): + try: + import networks + self.isnet = True + self.layer_name = "network_layer_name" + except: + self.isnet = False + self.layer_name = "lora_layer_name" + try: + import lora + self.islora = True + except: + pass + try: + import lycoris + self.islyco = True + except: + pass + self.onlyco = (not self.islora) and self.islyco + self.isxl = hasattr(shared.sd_model,"conditioner") + + self.log["isnet"] = self.isnet + self.log["isxl"] = self.isxl + self.log["islora"] = self.islora + +def resetmemory(): + try: + import networks as nets + nets.networks_in_memory = {} + gc.collect() + + except: + pass + +def importer(self): + if self.onlyco: + # lycorisモジュールを動的にインポート + lora_module = importlib.import_module("lycoris") + return lora_module + else: + # loraモジュールを動的にインポート + lora_module = importlib.import_module("lora") + return lora_module + +def loradealer(self, prompts,lratios,elementals, extra_network_data = None): + if extra_network_data is None: + _, extra_network_data = extra_networks.parse_prompts(prompts) + moduletypes = extra_network_data.keys() + + for ltype in moduletypes: + lorans = [] + lorars = [] + te_multipliers = [] + unet_multipliers = [] + elements = [] + starts = [] + stops = [] + fparams = [] + load = False + go_lbw = False + + if not (ltype == "lora" or ltype == "lyco") : continue + for called in extra_network_data[ltype]: + items = called.items + setnow = False + name = items[0] + te = syntaxdealer(items,"te=",1) + unet = syntaxdealer(items,"unet=",2) + te,unet = multidealer(te,unet) + + weights = syntaxdealer(items,"lbw=",2) if syntaxdealer(items,"lbw=",2) is not None else syntaxdealer(items,"w=",2) + elem = syntaxdealer(items, "lbwe=",3) + start = syntaxdealer(items,"start=",None) + stop = syntaxdealer(items,"stop=",None) + start, stop = stepsdealer(syntaxdealer(items,"step=",None), start, stop) + + if weights is not None and (weights in lratios or any(weights.count(",") == x - 1 for x in BLOCKNUMS)): + wei = lratios[weights] if weights in lratios else weights + ratios = [w.strip() for w in wei.split(",")] + for i,r in enumerate(ratios): + if r =="R": + ratios[i] = round(random.random(),3) + elif r == "U": + ratios[i] = round(random.uniform(-0.5,1.5),3) + elif r[0] == "X": + base = syntaxdealer(items,"x=", 3) if len(items) >= 4 else 1 + ratios[i] = getinheritedweight(base, r) + else: + ratios[i] = float(r) + + if len(ratios) != 26: + ratios = to26(ratios) + setnow = True + else: + ratios = [1] * 26 + + if elem in elementals: + setnow = True + elem = elementals[elem] + else: + elem = "" + + if setnow: + print(f"LoRA Block weight ({ltype}): {name}: (Te:{te},Unet:{unet}) x {ratios}") + go_lbw = True + fparams.append([unet,ratios,elem]) + settolist([lorans,te_multipliers,unet_multipliers,lorars,elements,starts,stops],[name,te,unet,ratios,elem,start,stop]) + + if start: + self.starts[name] = [int(start),te,unet] + self.log["starts"] = load = True + + if stop: + self.stops[name] = int(stop) + self.log["stops"] = load = True + + self.startsf = [int(s) if s is not None else None for s in starts] + self.stopsf = [int(s) if s is not None else None for s in stops] + self.uf = unet_multipliers + self.lf = lorars + self.ef = elements + + if self.isnet: ltype = "nets" + if forge: ltype = "forge" + if go_lbw or load: load_loras_blocks(self, lorans,lorars,te_multipliers,unet_multipliers,elements,ltype, starts=starts) + +def stepsdealer(step, start, stop): + if step is None or "-" not in step: + return start, stop + return step.split("-") + +def settolist(ls,vs): + for l, v in zip(ls,vs): + l.append(v) + +def syntaxdealer(items,target,index): #type "unet=", "x=", "lwbe=" + for item in items: + if target in item: + return item.replace(target,"") + if index is None or index + 1> len(items): return None + if "=" in items[index]:return None + return items[index] if "@" not in items[index] else 1 + +def isfloat(t): + try: + float(t) + return True + except: + return False + +def multidealer(t, u): + if t is None and u is None: + return 1,1 + elif t is None: + return float(u),float(u) + elif u is None: + return float(t), float(t) + else: + return float(t),float(u) + +re_inherited_weight = re.compile(r"X([+-])?([\d.]+)?") + +def getinheritedweight(weight, offset): + match = re_inherited_weight.search(offset) + if match.group(1) == "+": + return float(weight) + float(match.group(2)) + elif match.group(1) == "-": + return float(weight) - float(match.group(2)) + else: + return float(weight) + +def load_loras_blocks(self, names, lwei,te,unet,elements,ltype = "lora", starts = None): + oldnew=[] + if "lora" == ltype: + lora = importer(self) + self.lora = lora.loaded_loras + for loaded in lora.loaded_loras: + for n, name in enumerate(names): + if name == loaded.name: + if lwei[n] == [1] * 26 and elements[n] == "": continue + lbw(loaded,lwei[n],elements[n]) + setall(loaded,te[n],unet[n]) + newname = loaded.name +"_in_LBW_"+ str(round(random.random(),3)) + oldname = loaded.name + loaded.name = newname + oldnew.append([oldname,newname]) + + elif "lyco" == ltype: + import lycoris as lycomo + self.lycoris = lycomo.loaded_lycos + for loaded in lycomo.loaded_lycos: + for n, name in enumerate(names): + if name == loaded.name: + lbw(loaded,lwei[n],elements[n]) + setall(loaded,te[n],unet[n]) + + elif "nets" == ltype: + import networks as nets + self.networks = nets.loaded_networks + for loaded in nets.loaded_networks: + for n, name in enumerate(names): + if name == loaded.name: + lbw(loaded,lwei[n],elements[n]) + setall(loaded,te[n],unet[n]) + + elif "forge" == ltype: + lbwf(te, unet, lwei, elements, starts) + + try: + import lora_ctl_network as ctl + for old,new in oldnew: + if old in ctl.lora_weights.keys(): + ctl.lora_weights[new] = ctl.lora_weights[old] + except: + pass + +def setall(m,te,unet): + m.name = m.name + "_in_LBW_"+ str(round(random.random(),3)) + m.te_multiplier = te + m.unet_multiplier = unet + m.multiplier = unet + +def smakegrid(imgs,xs,ys,currentmodel,p): + ver_texts = [[images.GridAnnotation(y)] for y in ys] + hor_texts = [[images.GridAnnotation(x)] for x in xs] + + w, h = imgs[0].size + grid = Image.new('RGB', size=(len(xs) * w, len(ys) * h), color='black') + + for i, img in enumerate(imgs): + grid.paste(img, box=(i % len(xs) * w, i // len(xs) * h)) + + grid = images.draw_grid_annotations(grid,w, h, hor_texts, ver_texts) + grid = draw_origin(grid, currentmodel,w*len(xs),h*len(ys),w) + if opts.grid_save: + images.save_image(grid, opts.outdir_txt2img_grids, "xy_grid", extension=opts.grid_format, prompt=p.prompt, seed=p.seed, grid=True, p=p) + + return grid + +def get_font(fontsize): + fontpath = os.path.join(scriptpath, "Roboto-Regular.ttf") + try: + return ImageFont.truetype(opts.font or fontpath, fontsize) + except Exception: + return ImageFont.truetype(fontpath, fontsize) + +def draw_origin(grid, text,width,height,width_one): + grid_d= Image.new("RGB", (grid.width,grid.height), "white") + grid_d.paste(grid,(0,0)) + + d= ImageDraw.Draw(grid_d) + color_active = (0, 0, 0) + fontsize = (width+height)//25 + fnt = get_font(fontsize) + + if grid.width != width_one: + while d.multiline_textsize(text, font=fnt)[0] > width_one*0.75 and fontsize > 0: + fontsize -=1 + fnt = get_font(fontsize) + d.multiline_text((0,0), text, font=fnt, fill=color_active,align="center") + return grid_d + +def newrun(p, *args): + script_index = args[0] + + if args[0] ==0: + script = None + for obj in scripts.scripts_txt2img.alwayson_scripts: + if "lora_block_weight" in obj.filename: + script = obj + script_args = args[script.args_from:script.args_to] + else: + script = scripts.scripts_txt2img.selectable_scripts[script_index-1] + + if script is None: + return None + + script_args = args[script.args_from:script.args_to] + + processed = script.run(p, *script_args) + + shared.total_tqdm.clear() + + return processed + +registerd = False + +def register(): + global registerd + registerd = True + for obj in scripts.scripts_txt2img.alwayson_scripts: + if "lora_block_weight" in obj.filename: + if obj not in scripts.scripts_txt2img.selectable_scripts: + scripts.scripts_txt2img.selectable_scripts.append(obj) + scripts.scripts_txt2img.titles.append("LoRA Block Weight") + for obj in scripts.scripts_img2img.alwayson_scripts: + if "lora_block_weight" in obj.filename: + if obj not in scripts.scripts_img2img.selectable_scripts: + scripts.scripts_img2img.selectable_scripts.append(obj) + scripts.scripts_img2img.titles.append("LoRA Block Weight") + +def effectivechecker(imgs,ss,ls,diffcol,thresh,revxy): + orig = imgs[1] + imgs = imgs[::2] + diffs = [] + outnum =[] + + for img in imgs: + abs_diff = cv2.absdiff(np.array(img) , np.array(orig)) + + abs_diff_t = cv2.threshold(abs_diff, int(thresh), 255, cv2.THRESH_BINARY)[1] + res = abs_diff_t.astype(np.uint8) + percentage = (np.count_nonzero(res) * 100)/ res.size + if "white" in diffcol: abs_diff = cv2.bitwise_not(abs_diff) + outnum.append(percentage) + + abs_diff = Image.fromarray(abs_diff) + + diffs.append(abs_diff) + + outs = [] + for i in range(len(ls)): + ls[i] = ls[i] + "\n Diff : " + str(round(outnum[i],3)) + "%" + + if not revxy: + for diff,img in zip(diffs,imgs): + outs.append(diff) + outs.append(img) + outs.append(orig) + ss = ["diff",ss[0],"source"] + return outs,ss,ls + else: + outs = [orig]*len(diffs) + imgs + diffs + ss = ["source",ss[0],"diff"] + return outs,ls,ss + +def lbw(lora,lwei,elemental): + elemental = elemental.split(",") + errormodules = [] + for key in lora.modules.keys(): + ratio, errormodule = ratiodealer(key, lwei, elemental) + if errormodule: + errormodules.append(errormodule) + + ltype = type(lora.modules[key]).__name__ + set = False + if ltype in LORAANDSOON.keys(): + if "OFT" not in ltype: + setattr(lora.modules[key],LORAANDSOON[ltype],torch.nn.Parameter(getattr(lora.modules[key],LORAANDSOON[ltype]) * ratio)) + else: + setattr(lora.modules[key],LORAANDSOON[ltype],getattr(lora.modules[key],LORAANDSOON[ltype]) * ratio) + set = True + else: + if hasattr(lora.modules[key],"up_model"): + lora.modules[key].up_model.weight= torch.nn.Parameter(lora.modules[key].up_model.weight *ratio) + #print("LoRA using LoCON") + set = True + else: + lora.modules[key].up.weight= torch.nn.Parameter(lora.modules[key].up.weight *ratio) + #print("LoRA") + set = True + if not set : + print("unkwon LoRA") + + if len(errormodules) > 0: + print(errormodules) + return lora + +LORAS = ["lora", "loha", "lokr"] + +def lbwf(mt, mu, lwei, elemental, starts): + for key, vals in shared.sd_model.forge_objects_after_applying_lora.unet.patches.items(): + n_vals = [] + errormodules = [] + lvals = [val for val in vals if val[1][0] in LORAS] + for v, m, l, e ,s in zip(lvals, mu, lwei, elemental, starts): + ratio, errormodule = ratiodealer(key.replace(".","_"), l, e) + n_vals.append((ratio * m if s is None else 0, *v[1:])) + if errormodule:errormodules.append(errormodule) + shared.sd_model.forge_objects_after_applying_lora.unet.patches[key] = n_vals + + for key, vals in shared.sd_model.forge_objects_after_applying_lora.clip.patcher.patches.items(): + n_vals = [] + lvals = [val for val in vals if val[1][0] in LORAS] + for v, m, l, e in zip(lvals, mt, lwei, elemental): + ratio, errormodule = ratiodealer(key.replace(".","_"), l, e) + n_vals.append((ratio * m, *v[1:])) + if errormodule:errormodules.append(errormodule) + shared.sd_model.forge_objects_after_applying_lora.clip.patcher.patches[key] = n_vals + + if len(errormodules) > 0: + print("Unknown modules:",errormodules) + +def ratiodealer(key, lwei, elemental): + ratio = 1 + picked = False + errormodules = [] + currentblock = 0 + + for i,block in enumerate(BLOCKS): + if block in key: + if i == 26 or i == 27: + i = 0 + ratio = lwei[i] + picked = True + currentblock = i + + if not picked: + errormodules.append(key) + + if len(elemental) > 0: + skey = key + BLOCKID26[currentblock] + for d in elemental: + if d.count(":") != 2 :continue + dbs,dws,dr = (hyphener(d.split(":")[0]),d.split(":")[1],d.split(":")[2]) + dbs,dws = (dbs.split(" "), dws.split(" ")) + dbn,dbs = (True,dbs[1:]) if dbs[0] == "NOT" else (False,dbs) + dwn,dws = (True,dws[1:]) if dws[0] == "NOT" else (False,dws) + flag = dbn + for db in dbs: + if db in skey: + flag = not dbn + if flag:flag = dwn + else:continue + for dw in dws: + if dw in skey: + flag = not dwn + if flag: + dr = float(dr) + if princ :print(dbs,dws,key,dr) + ratio = dr + + return ratio, errormodules + +LORAANDSOON = { + "LoraHadaModule" : "w1a", + "LycoHadaModule" : "w1a", + "NetworkModuleHada": "w1a", + "FullModule" : "weight", + "NetworkModuleFull": "weight", + "IA3Module" : "w", + "NetworkModuleIa3" : "w", + "LoraKronModule" : "w1", + "LycoKronModule" : "w1", + "NetworkModuleLokr": "w1", + "NetworkModuleGLora": "w1a", + "NetworkModuleNorm": "w_norm", + "NetworkModuleOFT": "scale" +} + +def hyphener(t): + t = t.split(" ") + for i,e in enumerate(t): + if "-" in e: + e = e.split("-") + if BLOCKID26.index(e[1]) > BLOCKID26.index(e[0]): + t[i] = " ".join(BLOCKID26[BLOCKID26.index(e[0]):BLOCKID26.index(e[1])+1]) + else: + t[i] = " ".join(BLOCKID26[BLOCKID26.index(e[1]):BLOCKID26.index(e[0])+1]) + return " ".join(t) + +ELEMPRESETS="\ +ATTNDEEPON:IN05-OUT05:attn:1\n\n\ +ATTNDEEPOFF:IN05-OUT05:attn:0\n\n\ +PROJDEEPOFF:IN05-OUT05:proj:0\n\n\ +XYZ:::1" + +def to26(ratios): + ids = BLOCKIDS[BLOCKNUMS.index(len(ratios))] + output = [0]*26 + for i, id in enumerate(ids): + output[BLOCKID26.index(id)] = ratios[i] + return output + +def checkloadcond(l:str)->bool: + # ここの条件分岐は読み込んだ行がBlock Waightの書式にあっているかを確認している。 + # [:]が含まれ、16個(LoRa)か25個(LyCORIS),11,19(XL),のカンマが含まれる形式であるうえ、 + # それがコメントアウト行(# foobar)でないことが求められている。 + # 逆に言うとコメントアウトしたいなら絶対"# "から始めることを要求している。 + + # This conditional branch is checking whether the loaded line conforms to the Block Weight format. + # It is required that "[:]" is included, and the format contains either 16 commas (for LoRa) or 25 commas (for LyCORIS), + # and it's not a comment line (e.g., "# foobar"). + # Conversely, if you want to comment out, it requires that it absolutely starts with "# ". + res=(":" not in l) or (not any(l.count(",") == x - 1 for x in BLOCKNUMS)) or ("#" in l) + #print("[debug]", res,repr(l)) + return res diff --git a/extensions/CHECK/sd-webui-negpip/.github/FUNDING.yml b/extensions/CHECK/sd-webui-negpip/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..fb0dbcbcd838723bc56e628f5f7dfadbae976893 --- /dev/null +++ b/extensions/CHECK/sd-webui-negpip/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github:[hako-mikan] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/extensions/CHECK/sd-webui-negpip/LICENSE b/extensions/CHECK/sd-webui-negpip/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..0ad25db4bd1d86c452db3f9602ccdbe172438f52 --- /dev/null +++ b/extensions/CHECK/sd-webui-negpip/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/extensions/CHECK/sd-webui-negpip/README.md b/extensions/CHECK/sd-webui-negpip/README.md new file mode 100644 index 0000000000000000000000000000000000000000..543858caa710c2824cfb12c763469b252d9fa716 --- /dev/null +++ b/extensions/CHECK/sd-webui-negpip/README.md @@ -0,0 +1,70 @@ +# NegPiP - Negative Prompt in Prompt +[](README.md) +[](README_jp.md) +[](README_cn.md) +[](https://github.com/sponsors/hako-mikan) + + +Extension for Stable Diffusion web-ui enables negative prompt in prompt + +## Update 2023.10.29.2100(JST) +- Option to hide this extention in t2i/i2i tab [Detail](#hide-this-extention-in-text2imgimg2img-tab),[詳細](README_jp.md#txt2imgimg2imgタブで拡張を表示しない),[解释](README_cn.md#在txt2imgimg2img标签中不显示扩展) + +### [For users of ADetailer](#for-users-of-adetailer)/[ADetailerとの併用について](README_jp.md#adetailerとの併用について)/[关于与ADetailer的同时使用](README_cn.md#关于与adetailer的同时使用) + +# Overview +This extension enhances the stable diffusion web-ui prompts and cross-attention, allowing for the use of prompts with negative effects within regular prompts and prompts with positive effects within negative prompts. Typically, unwanted elements are placed in negative prompts, but negative prompts may not always have a significant impact in calculations. With this extension, it becomes possible to use negative prompts with effects comparable to regular prompts. This enables stronger effects even for words that might have collapsed when their values were increased too much in negative prompts before, by incorporating negative effects into the prompts. + +# Instructions +By checking the "Active" box, it will become effective. In the prompt input screen, entering a negative value like `(word:-1)` will give it a negative effect. It also works with negative prompts, in which case it will have a positive effect. + +This was created with the prompt "gothic dress". Despite including `(black:1.8)` in the negative prompt, it's still black. It seems impossible to completely eliminate the blackness of word `gothic`. + +![image1](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample.jpg) + +Following image created using `(black:-1.8)` in the prompt with NegPiP. It's no longer black. + +![image2](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample2.jpg) + +By the way, this is what happens when you don't use either NegPiP or negative prompts. +![image2](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample3.jpg) + +## Magical Dandy +Magical Dandy is a magical dandy. Summoning a magical dandy is very difficult. That's because it requires coexistence of a magical girl and a dandy. But the dandy is weak. The girl is strong. Very strong. So the dandy ends up losing. Even if you put `(girl:1.8)` in the negative prompt, it won't come up. +![](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample4.jpg) + +Therefore, it may be necessary to input `(girl:-1.6)` in the prompt to remove the girl. +![](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample5.jpg) + +## Hide this extention in text2img/img2img tab +In the Web-UI, go to Settings > NegPiP. +Check the "Hide in Hide in Txt2Img/Img2Img tab" option. +If you check this, the "Active" in Settings will be effective. + +## How to Use via API +The following format is used when utilizing this extension via the API. + +``` +"alwayson_scripts": { + "NegPiP": { + "args": [True] +}} +``` + +## For users of ADetailer +In the Web-UI, go to Settings > ADetailer. +Add ",negpip" to the end of the text box labeled "Script names to apply to ADetailer (separated by comma)" +Click "Apply Settings. + +### Update 2023.09.05.2000(JST) +- Prompt Edittingに対応 +- Regional Prompterに対応(最新版のRegional Prompterが必要) +- 負の値を入れていないときでも有効化したときに生成結果が変わる問題を修正 + +- Supports Prompt Editing +- Supports Regional Prompter (latest version of Regional Prompter required) +- Fixed the issue where generated results change even when negative values are not entered + +- 支持Prompt Editting +- 支持区Regional Prompter(需要最新版Regional Prompter) +- 修复了即使没有输入负值时激活也会改变生成结果的问题 diff --git a/extensions/CHECK/sd-webui-negpip/README_cn.md b/extensions/CHECK/sd-webui-negpip/README_cn.md new file mode 100644 index 0000000000000000000000000000000000000000..20cdd27362a58dfd6b8b6810712817549b0215b1 --- /dev/null +++ b/extensions/CHECK/sd-webui-negpip/README_cn.md @@ -0,0 +1,51 @@ +# NegPiP - Negative Prompt in Prompt +[](README.md) +[](README_jp.md) +[](README_cn.md) +[](https://github.com/sponsors/hako-mikan) + + +在 SD WebUI 中允许使用反向咒语(提示词) + +# 摘要 +该扩展增强了 SD WebUI 的 提示 和 交叉注意力 功能,允许在常规咒语中使用反向咒语,在反向咒语中使用正向咒语。通常情况下,不需要的元素会被放置在反向咒语中,但反向咒语在计算中不一定会产生重大影响。有了这一扩展,就可以使用效果与常规咒语相当的反向咒语。通过在咒语中加入反向效果,即使是以前在反向咒语中数值增加过多而可能崩溃的咒语,也能获得更强的效果。 + +# 使用 +选中“Active”复选框后,该插件将生效。在提示词输入框中,输入负值(如`(word:-1)`)时会产生反向作用。它也适用于反向咒语,在这种情况下,它将产生正向效应。 + +这是根据“gothic dress”的提示创建的。尽管在否定提示中包含了`(black:1.8)`,但它仍然是黑色的。要完全消除`gothic`一词的黑色似乎是不可能的。 + +![image1](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample.jpg) + +下图在 NegPiP 中使用`(black:-1.8)`创建,不再是黑色。 + +![image2](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample2.jpg) + +顺带一提,这是不使用 NegPiP 或负面提示时的结果。 +![image2](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample3.jpg) + +## 魔法花公 +魔法花公就是带有花花公子属性的魔法少男,但是想要召唤他异常困难。这是因为它需要魔法少女(magical girl)和花花公子(dandy)共存。但dandy属性却很弱。girl属性很坚强。非常强。所以,dandy最终还是输了。即使在反向中输入`(girl:1.8)`,花花公子也还算是不会出现。 +![](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample4.jpg) + +因此,可能有必要在正向咒语中输入`(girl:-1.6)`来削弱 girl。 +![](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample5.jpg) + +## 在Txt2Img/Img2Img标签中不显示扩展 +在Web-UI中,转到Settings > NegPiP。 +勾选"Hide in Hide in Txt2Img/Img2Img tab"选项。 +如果您勾选此选项,Settings中的"Active"将生效。 + +## 通过 API 使用的方法 +通过 API 使用此扩展时,使用以下格式。 +``` +"alwayson_scripts": { + "NegPiP": { + "args": [True] +}} +``` + +## 关于与ADetailer的同时使用 +在Web-UI中,前往“Settings” > “ADetailer”。 +在标有"Script names to apply to ADetailer (separated by comma)"”的文本框末尾添加“,negpip”。 +点击“Apply Settings”。 diff --git a/extensions/CHECK/sd-webui-negpip/README_jp.md b/extensions/CHECK/sd-webui-negpip/README_jp.md new file mode 100644 index 0000000000000000000000000000000000000000..7a7157e1b8890fddb76307e3b25615c891e72f0c --- /dev/null +++ b/extensions/CHECK/sd-webui-negpip/README_jp.md @@ -0,0 +1,51 @@ +# NegPiP - Negative Prompt in Prompt +[](README.md) +[](README_jp.md) +[](README_cn.md) +[](https://github.com/sponsors/hako-mikan) + + +負の効果を持つプロンプトを使えるようになります + +# 概要 +この拡張は、stable diffusion web-uiのプロンプトおよびクロスアテンションを拡張して、負の効果を持つプロンプトをプロンプト内で、正の効果を持つプロンプトをネガティブプロンプト内で使用できるようにします。通常、描きたくないものはネガティブプロンプトに書かれますが、ネガティブプロンプトの計算上、あまり効果が現れないことがあります。この拡張では、プロンプトと同程度の効果を持つ負のプロンプトを使用できるようにします。これにより、以前はネガティブプロンプトに置いて値を大きくしすぎて崩壊していたような単語でも、プロンプトに負の効果を持たせることができ、より強い効果が期待できます。 + +# 使い方 +Activeにチェックを入れることで有効になります。プロンプト入力画面において (word:-1)のようにマイナスの値を入れることで負の効果を持つようになります。ネガティブプロンプトでも有効で、この場合は正の効果を持ちます。値は1より大きな値を入力しないと効果が現れない場合があります。 + +これはgothic dressというプロンプトで作りました。ネガティブプロンプトに`(black:1.8)`と入れているにもかかわらず黒いですね。gothicの黒を消しきれないです。 +![image1](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample.jpg) + +これはNegPiPでプロンプトに`(black:-1.8)`を入れました。黒くなくなりましたね。 +![image2](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample2.jpg) + +ちなみに、NegPiPもネガティブプロンプトも使わないとこうなります。 +![image3](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample3.jpg) + +## マジカルダンディ +マジカルダンディはマジカルなダンディです。マジカルなダンディを呼び出すことはとても難しいです。それはmagical girlとdandyを共存させる必要があるからです。でもダンディは弱いです。girlは強いです。とても強いです。なのでダンディは負けてしまいます。ネガティブプロンプトに`(girl:1.8)`って入れても出てきません。 +![](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample4.jpg) + +なのでプロンプトの方に`(girl:-1.6)`と入れてgirlを消す必要があるんじゃんよ。 +![](https://github.com/hako-mikan/sd-webui-negpip/blob/imgs/sample5.jpg) + +## Txt2Img/Img2Imgタブで拡張を表示しない +Web-UIで、Settings > NegPiPに移動します。 +"Hide in Hide in Txt2Img/Img2Img tab"のオプションをチェックしてください。 +これをチェックすると、Settingsの"Active"が有効になります。 + +## APIで有効化する +スクリプトの指定を以下のように記述してください。 + +``` +"alwayson_scripts": { + "NegPiP": { + "args": [True] +}} +``` + +## ADetailerとの併用について +Web-UIで、Settings > ADetailerに移動してください。 +「Script names to apply to ADetailer (separated by comma)」と書かれたテキストボックスの末尾に「,negpip」を追加し、Apply Settings + + diff --git a/extensions/CHECK/sd-webui-negpip/scripts/__pycache__/negpip.cpython-310.pyc b/extensions/CHECK/sd-webui-negpip/scripts/__pycache__/negpip.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19005329e1cd2d00e96843db2984071d3c90d860 Binary files /dev/null and b/extensions/CHECK/sd-webui-negpip/scripts/__pycache__/negpip.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-negpip/scripts/negpip.py b/extensions/CHECK/sd-webui-negpip/scripts/negpip.py new file mode 100644 index 0000000000000000000000000000000000000000..8955931a8d78e3e8cd4bd207cff427b219ba39f6 --- /dev/null +++ b/extensions/CHECK/sd-webui-negpip/scripts/negpip.py @@ -0,0 +1,487 @@ +import gradio as gr +import torch +import re +import json +import ldm.modules.attention as atm +import modules.ui +import modules +from modules import prompt_parser + +from modules import shared +from modules.script_callbacks import CFGDenoiserParams, on_cfg_denoiser, on_ui_settings + +debug = False +debug_p = False + +try: + from ldm_patched.modules import model_management + forge = True +except: + forge = False + +OPT_ACT = "negpip_active" +OPT_HIDE = "negpip_hide" + +NEGPIP_T = "customscript/negpip.py/txt2img/Active/value" +NEGPIP_I = "customscript/negpip.py/img2img/Active/value" +CONFIG = shared.cmd_opts.ui_config_file + +with open(CONFIG, 'r', encoding="utf-8") as json_file: + ui_config = json.load(json_file) + +startup_t = ui_config[NEGPIP_T] if NEGPIP_T in ui_config else None +startup_i = ui_config[NEGPIP_I] if NEGPIP_I in ui_config else None +active_t = "Active" if startup_t else "Not Active" +active_i = "Active" if startup_i else "Not Active" + +opt_active = getattr(shared.opts,OPT_ACT, True) +opt_hideui = getattr(shared.opts,OPT_HIDE, False) + +minusgetter = r'\(([^(:)]*):\s*-[\d]+(\.[\d]+)?(?:\s*)\)' + +class Script(modules.scripts.Script): + def __init__(self): + self.active = False + self.conds = None + self.unconds = None + self.conlen = [] + self.unlen = [] + self.contokens = [] + self.untokens = [] + self.hr = False + self.x = None + + self.ipa = None + + self.enable_rp_latent = False + + def title(self): + return "NegPiP" + + def show(self, is_img2img): + return modules.scripts.AlwaysVisible + + infotext_fields = None + paste_field_names = [] + + def ui(self, is_img2img): + with gr.Accordion(f"NegPiP : {active_i if is_img2img else active_t}",open = False, visible = not opt_hideui) as acc: + with gr.Row(): + active = gr.Checkbox(value=False, label="Active",interactive=True) + toggle = gr.Button(elem_id="switch_default", value=f"Toggle startup with Active(Now:{startup_i if is_img2img else startup_t})",variant="primary") + + def f_toggle(is_img2img): + key = NEGPIP_I if is_img2img else NEGPIP_T + + with open(CONFIG, 'r', encoding="utf-8") as json_file: + data = json.load(json_file) + data[key] = not data[key] + + with open(CONFIG, 'w', encoding="utf-8") as json_file: + json.dump(data, json_file, indent=4) + + return gr.update(value = f"Toggle startup Active(Now:{data[key]})") + + toggle.click(fn=f_toggle,inputs=[gr.Checkbox(value = is_img2img, visible = False)],outputs=[toggle]) + active.change(fn=lambda x:gr.update(label = f"NegPiP : {'Active' if x else 'Not Active'}"),inputs=active, outputs=[acc]) + + self.infotext_fields = [ + (active, "NegPiP Active"), + ] + + for _,name in self.infotext_fields: + self.paste_field_names.append(name) + + return [active] + + def process_batch(self, p, active,**kwargs): + self.__init__() + flag = False + + if getattr(shared.opts,OPT_HIDE, False) and not getattr(shared.opts,OPT_ACT, False): return + elif not active: return + + self.rpscript = None + #get infomation of regponal prompter + from modules.scripts import scripts_txt2img + for script in scripts_txt2img.alwayson_scripts: + if "rp.py" in script.filename: + self.rpscript = script + + self.hrp, self.hrn = hr_dealer(p) + + self.active = active + self.batch = p.batch_size + self.isxl = hasattr(shared.sd_model,"conditioner") + + self.rev = p.sampler_name in ["DDIM", "PLMS", "UniPC"] + if forge: self.rev = not self.rev + + tokenizer = shared.sd_model.conditioner.embedders[0].tokenize_line if self.isxl else shared.sd_model.cond_stage_model.tokenize_line + + + def getshedulednegs(scheduled,prompts): + output = [] + nonlocal flag + for i, batch_shedule in enumerate(scheduled): + stepout = [] + seps = None + if self.rpscript: + if hasattr(self.rpscript,"seps"): + seps = self.rpscript.seps + self.enable_rp_latent = seps == "AND" + + for step,prompt in batch_shedule: + sep_prompts = prompt.split(seps) if seps else [prompt] + padd = 0 + padtextweight = [] + for sep_prompt in sep_prompts: + minusmatches = re.finditer(minusgetter, sep_prompt) + minus_targets = [] + textweights = [] + for minusmatch in minusmatches: + minus_targets.append(minusmatch.group().replace("(","").replace(")","")) + + prompts[i] = prompts[i].replace(minusmatch.group(),"") + minus_targets = [x.split(":") for x in minus_targets] + #print(minus_targets) + for text,weight in minus_targets: + weight = float(weight) + if text == "BREAK": continue + if weight < 0: + textweights.append([text,weight]) + flag = True + padtextweight.append([padd,textweights]) + tokens, tokensnum = tokenizer(sep_prompt) + padd = tokensnum // 75 + 1 + padd + stepout.append([step,padtextweight]) + output.append(stepout) + return output + + scheduled_p = prompt_parser.get_learned_conditioning_prompt_schedules(p.prompts,p.steps) + scheduled_np = prompt_parser.get_learned_conditioning_prompt_schedules(p.negative_prompts,p.steps) + + if self.hrp: scheduled_hr_p = prompt_parser.get_learned_conditioning_prompt_schedules(p.hr_prompts,p.hr_second_pass_steps if p.hr_second_pass_steps > 0 else p.steps) + if self.hrn: scheduled_hr_np = prompt_parser.get_learned_conditioning_prompt_schedules(p.hr_negative_prompts,p.hr_second_pass_steps if p.hr_second_pass_steps > 0 else p.steps) + + nip = getshedulednegs(scheduled_p,p.prompts) + pin = getshedulednegs(scheduled_np,p.negative_prompts) + + if self.hrp: hr_nip = getshedulednegs(scheduled_hr_p,p.hr_prompts) + if self.hrn: hr_pin = getshedulednegs(scheduled_hr_np,p.hr_negative_prompts) + + def conddealer(targets): + conds =[] + start = None + end = None + for target in targets: + input = SdConditioning([f"({target[0]}:{-target[1]})"], width=p.width, height=p.height) + cond = prompt_parser.get_learned_conditioning(shared.sd_model,input,p.steps) + if start is None: start = cond[0][0].cond[0:1,:] if not self.isxl else cond[0][0].cond["crossattn"][0:1,:] + if end is None: end = cond[0][0].cond[-1:,:] if not self.isxl else cond[0][0].cond["crossattn"][-1:,:] + token, tokenlen = tokenizer(target[0]) + conds.append(cond[0][0].cond[1:tokenlen +2,:] if not self.isxl else cond[0][0].cond["crossattn"][1:tokenlen +2,:] ) + conds = torch.cat(conds, 0) + + conds = torch.split(conds, 75, dim=0) + condsout = [] + condcount = [] + for cond in conds: + condcount.append(cond.shape[0]) + repeat = 0 if cond.shape[0] == 75 else 75 - cond.shape[0] + cond = torch.cat((start,cond,end.repeat(repeat + 1,1)),0) + condsout.append(cond) + condout = torch.cat(condsout,0).unsqueeze(0) + return condout.repeat(self.batch,1,1), condcount + + def calcconds(targetlist): + outconds = [] + for batch in targetlist: + stepconds = [] + for step, regions in batch: + regionconds = [] + for region, targets in regions: + if targets: + conds, contokens = conddealer(targets) + regionconds.append([region, conds, contokens]) + else: + regionconds.append([region, None, None]) + stepconds.append([step,regionconds]) + outconds.append(stepconds) + return outconds + + self.conds_all = calcconds(nip) + self.unconds_all = calcconds(pin) + + if self.hrp: self.hr_conds_all = calcconds(hr_nip) + if self.hrn: self.hr_unconds_all = calcconds(hr_pin) + + #print(self.conds_all) + #print(self.unconds_all) + + resetpcache(p) + + def calcsets(A, B): + return A // B if A % B == 0 else A // B + 1 + + self.conlen = calcsets(tokenizer(p.prompts[0])[1],75) + self.unlen = calcsets(tokenizer(p.negative_prompts[0])[1],75) + + if not flag: + self.active = False + unload(self,p) + return + + if not hasattr(self,"negpip_dr_callbacks"): + self.negpip_dr_callbacks = on_cfg_denoiser(self.denoiser_callback) + + #disable hookforward if hookfoward in regional prompter is eanble. + #negpip operation is treated in regional prompter + + already_hooked = False + if self.rpscript is not None and hasattr(self.rpscript,"hooked"):already_hooked = self.rpscript.hooked + + if not already_hooked: + self.handle = hook_forwards(self, p.sd_model.model.diffusion_model) + + print(f"NegPiP enable, Positive:{self.conds_all[0][0][1][0][2]},Negative:{self.unconds_all[0][0][1][0][2]}") + + p.extra_generation_params.update({ + "NegPiP Active":active, + }) + + def postprocess(self, p, processed, *args): + unload(self,p) + self.conds_all = None + self.unconds_all = None + + def denoiser_callback(self, params: CFGDenoiserParams): + if debug: print(params.text_cond.shape) + if self.active: + if self.x is None: self.x = params.x.shape + if self.x != params.x.shape: self.hr = True + + self.latenti = 0 + + condslist = [] + tokenslist = [] + conds = self.hr_conds_all if self.hrp and self.hr else self.conds_all + if conds is not None: + for step, regions in conds[0]: + if step >= params.sampling_step + 2: + for region, conds, tokens in regions: + condslist.append(conds) + tokenslist.append(tokens) + if debug: print(f"current:{params.sampling_step + 2},selected:{step}") + break + self.conds = condslist + self.contokens = tokenslist + + uncondslist = [] + untokenslist = [] + unconds = self.hr_unconds_all if self.hrn and self.hr else self.unconds_all + if unconds is not None: + for step, regions in unconds[0]: + if step >= params.sampling_step + 2: + for region, unconds, untokens in regions: + uncondslist.append(unconds) + untokenslist.append(untokens) + break + + self.unconds = uncondslist + self.untokens = untokenslist + +from pprint import pprint + +def unload(self,p): + if hasattr(self,"handle"): + hook_forwards(self, p.sd_model.model.diffusion_model, remove=True) + del self.handle + +def hook_forward(self, module): + def forward(x, context=None, mask=None, additional_tokens=None, n_times_crossframe_attn_in_self=0, value = None, transformer_options=None): + if debug: print(" x.shape:",x.shape,"context.shape:",context.shape,"self.contokens",self.contokens,"self.untokens",self.untokens) + + def sub_forward(x, context, mask, additional_tokens, n_times_crossframe_attn_in_self,conds,contokens,unconds,untokens, latent = None): + if debug: print(" x.shape[0]:",x.shape[0],"batch:",self.batch *2) + + if x.shape[0] == self.batch *2: + if debug: print(" x.shape[0] == self.batch *2") + + if self.rev: + contn,contp = context.chunk(2) + ixn,ixp = x.chunk(2) + else: + contp,contn = context.chunk(2) + ixp,ixn = x.chunk(2) #x[0:self.batch,:,:],x[self.batch:,:,:] + + if conds is not None: + if contp.shape[0] != conds.shape[0]: + conds = conds.expand(contp.shape[0],-1,-1) + contp = torch.cat((contp,conds),1) + if unconds is not None: + if contn.shape[0] != unconds.shape[0]: + unconds = unconds.expand(contn.shape[0],-1,-1) + contn = torch.cat((contn,unconds),1) + xp = main_foward(self, module, ixp,contp,mask,additional_tokens,n_times_crossframe_attn_in_self,contokens) + xn = main_foward(self, module, ixn,contn,mask,additional_tokens,n_times_crossframe_attn_in_self,untokens) + + out = torch.cat([xn,xp]) if self.rev else torch.cat([xp,xn]) + return out + + elif latent is not None: + if debug:print(" latent is not None") + if latent: + conds = conds if conds is not None else None + else: + conds = unconds if unconds is not None else None + if conds is not None: + if context.shape[0] != conds.shape[0]: + conds = conds.expand(context.shape[0],-1,-1) + context = torch.cat([context,conds],1) + + tokens = contokens if contokens is not None else untokens + + out = main_foward(self, module, x,context,mask,additional_tokens,n_times_crossframe_attn_in_self,tokens) + return out + + else: + if debug: + print(" Else") + print(context.shape[1] , self.conlen,self.unlen) + + tokens = [] + concon = counter(self.isxl) + if debug: print(concon) + if context.shape[1] == self.conlen * 77 and concon: + if conds is not None: + if context.shape[0] != conds.shape[0]: + conds = conds.expand(context.shape[0],-1,-1) + context = torch.cat([context,conds],1) + tokens = contokens + elif context.shape[1] == self.unlen * 77 and concon: + if unconds is not None: + if context.shape[0] != unconds.shape[0]: + unconds = unconds.expand(context.shape[0],-1,-1) + context = torch.cat([context,unconds],1) + tokens = untokens + out = main_foward(self, module, x,context,mask,additional_tokens,n_times_crossframe_attn_in_self,tokens) + return out + + if self.enable_rp_latent: + if len(self.conds) - 1 >= self.latenti: + out = sub_forward(x, context, mask, additional_tokens, n_times_crossframe_attn_in_self,self.conds[self.latenti],self.contokens[self.latenti],None,None ,latent = True) + self.latenti += 1 + else: + out = sub_forward(x, context, mask, additional_tokens, n_times_crossframe_attn_in_self,None,None,self.unconds[0],self.untokens[0], latent = False) + self.latenti = 0 + return out + else: + if self.conds is not None and self.unconds is not None and len(self.conds) > 0 and len(self.unconds) > 0: + return sub_forward(x, context, mask, additional_tokens, n_times_crossframe_attn_in_self,self.conds[0],self.contokens[0],self.unconds[0],self.untokens[0]) + else: + return sub_forward(x, context, mask, additional_tokens, n_times_crossframe_attn_in_self,None,None,None,None) + + return forward + +count = 0 +pn = True + +def counter(isxl): + global count, pn + count += 1 + + limit = 70 if isxl else 16 + outpn = pn + + if count == limit: + pn = not pn + count = 0 + return outpn + +def main_foward(self, module, x, context, mask, additional_tokens, n_times_crossframe_attn_in_self, tokens): + h = module.heads + context = context.to(x.dtype) + q = module.to_q(x) + + context = atm.default(context, x) + k = module.to_k(context) + v = module.to_v(context) + if debug: print(h,context.shape,q.shape,k.shape,v.shape) + + _, _, dim_head = q.shape + dim_head //= h + scale = dim_head ** -0.5 + + q, k, v = map(lambda t: atm.rearrange(t, 'b n (h d) -> (b h) n d', h=h), (q, k, v)) + sim = atm.einsum('b i d, b j d -> b i j', q, k) * scale + + if self.active: + if tokens: + for token in tokens: + start = (v.shape[1]//77 - len(tokens)) * 77 + #print("v.shape:",v.shape,"start:",start+1,"stop:",start+token) + v[:,start+1:start+token,:] = -v[:,start+1:start+token,:] + + if atm.exists(mask): + mask = atm.rearrange(mask, 'b ... -> b (...)') + max_neg_value = -torch.finfo(sim.dtype).max + mask = atm.repeat(mask, 'b j -> (b h) () j', h=h) + sim.masked_fill_(~mask, max_neg_value) + + attn = sim.softmax(dim=-1) + #print(h,context.shape,q.shape,k.shape,v.shape,attn.shape) + out = atm.einsum('b i j, b j d -> b i d', attn, v) + + out = atm.rearrange(out, '(b h) n d -> b n (h d)', h=h) + + return module.to_out(out) + +import inspect + +def hook_forwards(self, root_module: torch.nn.Module, remove=False): + for name, module in root_module.named_modules(): + if "attn2" in name and module.__class__.__name__ == "CrossAttention": + module.forward = hook_forward(self, module) + if remove: + del module.forward + +def resetpcache(p): + p.cached_c = [None,None] + p.cached_uc = [None,None] + p.cached_hr_c = [None, None] + p.cached_hr_uc = [None, None] + + +class SdConditioning(list): + def __init__(self, prompts, is_negative_prompt=False, width=None, height=None, copy_from=None): + super().__init__() + self.extend(prompts) + + if copy_from is None: + copy_from = prompts + + self.is_negative_prompt = is_negative_prompt or getattr(copy_from, 'is_negative_prompt', False) + self.width = width or getattr(copy_from, 'width', None) + self.height = height or getattr(copy_from, 'height', None) + +def ext_on_ui_settings(): + # [setting_name], [default], [label], [component(blank is checkbox)], [component_args]debug_level_choices = [] + negpip_options = [ + (OPT_HIDE, False, "Hide in Txt2Img/Img2Img tab(Reload UI required)"), + (OPT_ACT, True, "Active(Effective when Hide is Checked)",), + ] + section = ('negpip', "NegPiP") + + for cur_setting_name, *option_info in negpip_options: + shared.opts.add_option(cur_setting_name, shared.OptionInfo(*option_info, section=section)) + +on_ui_settings(ext_on_ui_settings) + +def hr_dealer(p): + if not hasattr(p, "hr_prompts"): + p.hr_prompts = None + if not hasattr(p, "hr_negative_prompts"): + p.hr_negative_prompts = None + + return bool(p.hr_prompts), bool(p.hr_negative_prompts ) diff --git a/extensions/CHECK/sd-webui-noise-scheduler-modifier/scripts/__pycache__/noise-scheduler-modifier.cpython-310.pyc b/extensions/CHECK/sd-webui-noise-scheduler-modifier/scripts/__pycache__/noise-scheduler-modifier.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86edc486743cf91ed8c854517c513ff3bd7e2b82 Binary files /dev/null and b/extensions/CHECK/sd-webui-noise-scheduler-modifier/scripts/__pycache__/noise-scheduler-modifier.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-noise-scheduler-modifier/scripts/noise-scheduler-modifier.py b/extensions/CHECK/sd-webui-noise-scheduler-modifier/scripts/noise-scheduler-modifier.py new file mode 100644 index 0000000000000000000000000000000000000000..7d521774867c3429aa6feb4d317598c1d477fa25 --- /dev/null +++ b/extensions/CHECK/sd-webui-noise-scheduler-modifier/scripts/noise-scheduler-modifier.py @@ -0,0 +1,185 @@ +import gradio +import torch + +from modules import devices +from modules import sd_samplers_cfg_denoiser +from modules import script_callbacks +from modules import scripts +from modules import shared +from modules.shared import opts +from modules.processing import StableDiffusionProcessing +import k_diffusion +from k_diffusion import sampling + +_schedulers = ['karras', 'exponential', 'polyexponential', 'vp'] +_p: StableDiffusionProcessing = None +_number_of_controls = 8 +_enable_default = [False] * _number_of_controls +_enable = _enable_default +_use_raw_sigma_default = [False] * _number_of_controls +_use_raw_sigma = _use_raw_sigma_default +_scheduler_default = ['karras'] * _number_of_controls +_scheduler = _scheduler_default +_start_value_default = [999] * _number_of_controls +_start_value = _start_value_default +_end_value_default = [0] * _number_of_controls +_end_value = _end_value_default +_step_default = [50] * _number_of_controls +_step = _step_default +_rho_default = [7] * _number_of_controls +_rho = _rho_default + +class Noise_scheduler_modifier(scripts.Script): + def title(self): + return 'Noise scheduler modifier' + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + global _number_of_controls, _enable_default, _use_raw_sigma_default, _scheduler_default, _start_value_default, _end_value_default, _step_default, _rho_default + enable = [] + use_raw_sigma = [] + scheduler = [] + start_value = [] + end_value = [] + step = [] + rho = [] + with gradio.Accordion('Noise scheduler modifier', open=False) as accordion: + for i in range(_number_of_controls): + with gradio.Accordion(f'Control {i + 1}', open=False): + with gradio.Row(): + enable.append(gradio.Checkbox(False, label=f'Enable control {i + 1}')) + use_raw_sigma.append(gradio.Checkbox(False, label=f'Use raw sigma {i + 1}')) + scheduler.append(gradio.Dropdown(_schedulers, value='karras', multiselect=False, label=f'Schaduler {i + 1}')) + with gradio.Row(): + start_value.append(gradio.Number(999, label=f'Start value {i + 1}', step=0.000001)) + end_value.append(gradio.Number(0, label=f'End value {i + 1}', step=0.000001)) + with gradio.Row(): + step.append(gradio.Number(50, label=f'Step {i + 1}', step=1)) + rho.append(gradio.Number(7, label=f'rho {i + 1}', step=0.000001)) + self.infotext_fields = [(accordion, lambda d: gradio.Accordion.update(open=True in d.get('Noise shceduler modifier enable', _enable_default)))] + \ + [(enable[i], lambda d: gradio.Dropdown.update(value=d.get('Noise shceduler modifier enable', _enable_default)[i])) for i in range(_number_of_controls)] + \ + [(use_raw_sigma[i], lambda d: gradio.Dropdown.update(value=d.get('Noise shceduler modifier use raw sigma', _use_raw_sigma_default)[i])) for i in range(_number_of_controls)] + \ + [(scheduler[i], lambda d: gradio.Dropdown.update(value=d.get('Noise shceduler modifier scheduler', _scheduler_default)[i])) for i in range(_number_of_controls)] + \ + [(start_value[i], lambda d: gradio.Dropdown.update(value=d.get('Noise shceduler modifier start value', _start_value_default)[i])) for i in range(_number_of_controls)] + \ + [(end_value[i], lambda d: gradio.Dropdown.update(value=d.get('Noise shceduler modifier end value', _end_value_default)[i])) for i in range(_number_of_controls)] + \ + [(step[i], lambda d: gradio.Dropdown.update(value=d.get('Noise shceduler modifier step', _step_default)[i])) for i in range(_number_of_controls)] + \ + [(rho[i], lambda d: gradio.Dropdown.update(value=d.get('Noise shceduler modifier rho', _rho_default)[i])) for i in range(_number_of_controls)] + return *enable, *use_raw_sigma, *scheduler, *start_value, *end_value, *step, *rho + + def process_batch(self, p:StableDiffusionProcessing, *args, **kwargs): + global _p, _number_of_controls, _enable, _use_raw_sigma, _scheduler, _start_value, _end_value, _step, _rho + _enable, _use_raw_sigma, _scheduler, _start_value, _end_value, _step, _rho = ([], [], [], [], [], [], []) + for i in range(_number_of_controls): + _enable.append(getattr(p, f'Noise_shceduler_modifier_enable_{i + 1}', args[0 * _number_of_controls + i])) + _use_raw_sigma.append(getattr(p, f'Noise_shceduler_modifier_use_raw_sigma_{i + 1}', args[1 * _number_of_controls + i])) + _scheduler.append(getattr(p, f'Noise_shceduler_modifier_scheduler_{i + 1}', args[2 * _number_of_controls + i])) + _start_value.append(getattr(p, f'Noise_shceduler_modifier_start_value_{i + 1}', args[3 * _number_of_controls + i])) + _end_value.append(getattr(p, f'Noise_shceduler_modifier_end_value_{i + 1}', args[4 * _number_of_controls + i])) + _step.append(getattr(p, f'Noise_shceduler_modifier_step_{i + 1}', args[5 * _number_of_controls + i])) + _rho.append(getattr(p, f'Noise_shceduler_modifier_rho_{i + 1}', args[6 * _number_of_controls + i])) + if True in _enable: + p.sampler_noise_scheduler_override = Noise_scheduler_modifier.sampler_noise_scheduler_override + p.extra_generation_params['Noise shceduler modifier enable'] = _enable + p.extra_generation_params['Noise shceduler modifier use raw sigma'] = _use_raw_sigma + p.extra_generation_params['Noise shceduler modifier scheduler'] = _scheduler + p.extra_generation_params['Noise shceduler modifier start value'] = _start_value + p.extra_generation_params['Noise shceduler modifier end value'] = _end_value + p.extra_generation_params['Noise shceduler modifier steps'] = _step + p.extra_generation_params['Noise shceduler modifier rho'] = _rho + _p = p + + def sampler_noise_scheduler_override(steps): + global _p, _number_of_controls, _enable, _use_raw_sigma, _scheduler, _start_value, _end_value, _step, _rho + model_wrap_cfg = CFGDenoiserKDiffusion(_p.sampler) + model_wrap = model_wrap_cfg.inner_model + sigmas = None + for i in range(_number_of_controls): + if _enable[i]: + _step[i] = int(_step[i]) + if not _use_raw_sigma[i]: + _start_value[i] = int(_start_value[i]) + _end_value[i] = int(_end_value[i]) + if _start_value[i] < 0 or _start_value[i] > 999 or _end_value[i] < 0 or _end_value[i] > 999: + raise IndexError('Specify value between 0 and 999 if "Use raw sigma" is unchecked.') + else: + if _end_value[i] == 0: + raise UserWarning('Avoid specifing 0 to end value if "Use raw sigma" is checked.') + if _scheduler[i] == 'karras': + if sigmas is None: + sigma_min = _end_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_end_value[i]].item() + sigma_max = _start_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_start_value[i]].item() + sigmas = sampling.get_sigmas_karras(_step[i], sigma_min, sigma_max, _rho[i], devices.device) + else: + sigma_min = _end_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_end_value[i]].item() + sigma_max = _start_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_start_value[i]].item() + sigmas = torch.cat((sigmas[:-1], sampling.get_sigmas_karras(_step[i], sigma_min, sigma_max, _rho[i], devices.device))) + elif _scheduler[i] == 'exponential': + if sigmas is None: + sigma_min = _end_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_end_value[i]].item() + sigma_max = _start_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_start_value[i]].item() + sigmas = sampling.get_sigmas_exponential(_step[i], sigma_min, sigma_max, devices.device) + else: + sigma_min = _end_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_end_value[i]].item() + sigma_max = _start_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_start_value[i]].item() + sigmas = torch.cat((sigmas[:-1], sampling.get_sigmas_exponential(_step[i], sigma_min, sigma_max, devices.device))) + elif _scheduler[i] == 'polyexponential': + if sigmas is None: + sigma_min = _end_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_end_value[i]].item() + sigma_max = _start_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_start_value[i]].item() + sigmas = sampling.get_sigmas_polyexponential(_step[i], sigma_min, sigma_max, _rho[i], devices.device) + else: + sigma_min = _end_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_end_value[i]].item() + sigma_max = _start_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_start_value[i]].item() + sigmas = torch.cat((sigmas[:-1], sampling.get_sigmas_polyexponential(_step[i], sigma_min, sigma_max, _rho[i], devices.device))) + elif _scheduler[i] == 'vp': + if sigmas is None: + sigma_min = _end_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_end_value[i]].item() + sigma_max = _start_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_start_value[i]].item() + sigmas = sampling.get_sigmas_vp(_step[i], sigma_max, sigma_min, _rho[i], devices.device) + else: + sigma_min = _end_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_end_value[i]].item() + sigma_max = _start_value[i] if _use_raw_sigma[i] else model_wrap.sigmas[_start_value[i]].item() + sigmas = torch.cat((sigmas[:-1], sampling.get_sigmas_vp(_step[i], sigma_max, sigma_min, _rho[i], devices.device))) + else: + raise RuntimeError("Unavailable scheduler option") + return sigmas + +class CFGDenoiserKDiffusion(sd_samplers_cfg_denoiser.CFGDenoiser): + @property + def inner_model(self): + if self.model_wrap is None: + denoiser = k_diffusion.external.CompVisVDenoiser if shared.sd_model.parameterization == "v" else k_diffusion.external.CompVisDenoiser + self.model_wrap = denoiser(shared.sd_model, quantize=shared.opts.enable_quantization) + + return self.model_wrap + +def make_axis_options(): + xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "xyz_grid.py"][0].module + + def confirm_scheduler(p, xs): + for x in xs: + if x not in _schedulers: + raise RuntimeError(f"Unknown Scheduler: {x}") + + extra_axis_options = [] + for i in range(_number_of_controls): + extra_axis_options.append(xyz_grid.AxisOption(f'[Noise scheduler modifier] enabled {i + 1}', bool, xyz_grid.apply_field(f'Noise_shceduler_modifier_enable_{i + 1}'))) + extra_axis_options.append(xyz_grid.AxisOption(f'[Noise scheduler modifier] use raw sigma {i + 1}', bool, xyz_grid.apply_field(f'Noise_shceduler_modifier_use_raw_sigma_{i + 1}'))) + extra_axis_options.append(xyz_grid.AxisOption(f'[Noise scheduler modifier] scheduler {i + 1}', str, xyz_grid.apply_field(f'Noise_shceduler_modifier_scheduler_{i + 1}'), confirm=confirm_scheduler, choices=lambda: _schedulers)) + extra_axis_options.append(xyz_grid.AxisOption(f'[Noise scheduler modifier] start value {i + 1}', float, xyz_grid.apply_field(f'Noise_shceduler_modifier_start_value_{i + 1}'))) + extra_axis_options.append(xyz_grid.AxisOption(f'[Noise scheduler modifier] end value {i + 1}', float, xyz_grid.apply_field(f'Noise_shceduler_modifier_end_value_{i + 1}'))) + extra_axis_options.append(xyz_grid.AxisOption(f'[Noise scheduler modifier] steps {i + 1}', int, xyz_grid.apply_field(f'Noise_shceduler_modifier_step_{i + 1}'))) + extra_axis_options.append(xyz_grid.AxisOption(f'[Noise scheduler modifier] rho {i + 1}', float, xyz_grid.apply_field(f'Noise_shceduler_modifier_rho_{i + 1}'))) + + if not any("[Noise scheduler modifier]" in x.label for x in xyz_grid.axis_options): + xyz_grid.axis_options.extend(extra_axis_options) + +def callback_before_ui(): + try: + make_axis_options() + except Exception as e: + print(e) + +script_callbacks.on_before_ui(callback_before_ui) diff --git a/extensions/CHECK/sd-webui-reuse-seed-plus/LICENSE.md b/extensions/CHECK/sd-webui-reuse-seed-plus/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..d809603ca276c3ff310514890fcaaea9fdf0484a --- /dev/null +++ b/extensions/CHECK/sd-webui-reuse-seed-plus/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Takenoko3333 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/CHECK/sd-webui-reuse-seed-plus/README.md b/extensions/CHECK/sd-webui-reuse-seed-plus/README.md new file mode 100644 index 0000000000000000000000000000000000000000..306d3228e6792dc5c064f2eb21b13e4d2684f5a2 --- /dev/null +++ b/extensions/CHECK/sd-webui-reuse-seed-plus/README.md @@ -0,0 +1,197 @@ +# sd-webui-reuse-seed-plus + +[日本語](#日本語) | [English](#english) + +# 日本語 + +# 説明 + +## 機能付き生成ボタンの追加 + +- txt2img, img2imgの画面に三つの機能付き生成ボタンを追加します。 +- 🎲: ランダムに設定される新たなシードで生成します。 +- ♻️: 前回生成時のシードがある場合、シードを再利用して生成します。 +- +1: シードが設定されている場合、シードに1を足して生成します。シードが-1の場合、このボタンは無効化されます。 +

+

+ +

+
+ +## Hires.fix 連動機能 + +- Hires.fixのオン/オフに連動して他の機能のオン/オフを切り替えます。 +- Hires.fixボタンの上に各機能のチェックボックスが追加されます。 +- Hires.fix with ♻️ & ADetailer: チェックボックスがオンの場合、Hires.fixに連動して♻️(Reuse seed)とADetailerを切り替えます。ADetailerがインストールされていない場合は♻️(Reuse seed)のみ連動します。 +- Hires.fix with ADetailer: チェックがオンの場合、Hires.fix に連動してADetailerを切り替えます。ADetailerがインストールされていない場合は非表示となります。 +- Hires.fix with Tiled VAE: チェックボックスがオンの場合、Hires.fix に連動してTiled VAEを切り替えます。Forge及び、Tiled VAEがインストールされていない場合は非表示となります。 +- Reuse delay time: シード再利用生成ボタンを使用した時、シードが再利用されなかった場合はこの値を大きくしてください。初期値は500(msec)です。 +

+

+ +

+
+ +# インストール方法 + +AUTOMATIC1111のExtensionsタブをクリック、Install from URLタブをクリックし、URL for extension's git repositoryに以下のurlを入力しInstallボタンをクリックしてください。 + +``` +https://github.com/Takenoko3333/sd-webui-reuse-seed-plus.git +``` + +
+ +# 変更履歴 + +## [0.3.0] - 2024-7-28 + +### 追加, 修正 + +- ADetailerの仕様変更で連動しない問題を修正 +- 非対応の機能を非表示にする対応 +- シード再利用生成ボタン使用時の遅延時間を変更できる機能を追加 +- READMEの誤字を修正 + +## [0.2.0] - 2024-6-8 + +### 追加, 修正 + +- 機能付き生成ボタン(🎲, ♻️, +1)を追加 +- Hires.fix 連動機能に'Hires.fix ADetailer', 'Hires.fix with Tiled VAE'を追加 +- 文言を修正 + +## [0.1.4] - 2024-6-6 + +### 修正 + +- チェックボックスのidを修正 +- チェックボックスのセレクターを修正 + +## [0.1.3] - 2024-6-6 + +### 修正 + +- 関数を修正 + +## [0.1.2] - 2024-6-6 + +### 修正 + +- cssを修正 + +## [0.1.1] - 2024-6-6 + +### 修正 + +- READMEを修正 + +## [0.1.0] - 2024-6-6 + +### 追加 + +- v0.1.0リリース +

+ +# ライセンス + +Copyright © 2024 Takenoko +Released under the [MIT License](https://opensource.org/licenses/mit-license.php). +


+ +# English + +# Description + +## Adding Functional Generation Buttons + +- Three functional generation buttons will be added to the txt2img and img2img screens. +- 🎲: Generates with a new seed set randomly. +- ♻️: Reuses the seed from the previous generation if available. +- +1: Adds 1 to the current seed if a seed is set. If the seed is -1, this button is disabled. +

+

+ +

+
+ +## Hires.fix Integration Feature + +- The other functions are switched on and off in conjunction with the Hires.fix on/off. +- A checkbox for each function is added above the Hires.fix button. +- Hires.fix with ♻️ & ADetailer: If the checkbox is on, switches ♻️ (Reuse seed) and ADetailer in conjunction with Hires.fix. If ADetailer is not installed, only ♻️ (Reuse seed) is linked. +- Hires.fix with ADetailer: if checked, switches ADetailer in conjunction with Hires.fix; if ADetailer is not installed, it is hidden. +- Hires.fix with Tiled VAE: if checked, switches Tiled VAE in conjunction with Hires.fix, hidden if Forge and Tiled VAE are not installed. +- Reuse delay time: increase this value if the seed is not reused when using the Generate Seed Reuse button. The default value is 500 (msec). +

+

+ +

+
+ +# Installation Instructions + +Click on the Extensions tab in AUTOMATIC1111, then click on the Install from URL tab. Enter the following URL in the URL for extension's git repository field and click the Install button. + +``` +https://github.com/Takenoko3333/sd-webui-reuse-seed-plus.git +``` + +
+ +# Change Log + +## [0.3.0] - 2024-7-28 + +### Additions, Fixes. + +- Fixes issues not linked to ADetailer specification changes. +- Support for hiding unsupported functions. +- Added function to change the delay time when using the seed reuse generation button +- Fixed typos in README + +## [0.2.0] - 2024-6-8 + +### Add and Fixed + +- Added functional generation buttons (🎲, ♻️, +1) +- Added 'Hires.fix ADetailer' and 'Hires.fix with Tiled VAE' to the Hires.fix integration feature +- Revised text and wording + +# Changelog + +## [0.1.4] - 2024-6-6 + +### Fixed + +- Fixed chexbox id name +- Fixed chexbox selector + +## [0.1.3] - 2024-6-6 + +### Fixed + +- Fixed function + +## [0.1.2] - 2024-6-6 + +### Fixed + +- Fixed css + +## [0.1.1] - 2024-6-6 + +### Fixed + +- Fixed README + +## [0.1.0] - 2024-6-6 + +### Added + +- v0.1.0 release + +# License + +Copyright © 2024 Takenoko +Released under the [MIT License](https://opensource.org/licenses/mit-license.php). diff --git a/extensions/CHECK/sd-webui-reuse-seed-plus/images/image.png b/extensions/CHECK/sd-webui-reuse-seed-plus/images/image.png new file mode 100644 index 0000000000000000000000000000000000000000..b624d6dfc70b09e52020118c19c2d2bdefb314fb Binary files /dev/null and b/extensions/CHECK/sd-webui-reuse-seed-plus/images/image.png differ diff --git a/extensions/CHECK/sd-webui-reuse-seed-plus/images/image1.png b/extensions/CHECK/sd-webui-reuse-seed-plus/images/image1.png new file mode 100644 index 0000000000000000000000000000000000000000..adad2a733202e0105632e9cb423b529f9e04f593 Binary files /dev/null and b/extensions/CHECK/sd-webui-reuse-seed-plus/images/image1.png differ diff --git a/extensions/CHECK/sd-webui-reuse-seed-plus/images/image2.png b/extensions/CHECK/sd-webui-reuse-seed-plus/images/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..c7fa84490e0eab9a8aa6d6e2cc625eb305c3f2f6 Binary files /dev/null and b/extensions/CHECK/sd-webui-reuse-seed-plus/images/image2.png differ diff --git a/extensions/CHECK/sd-webui-reuse-seed-plus/javascript/reuse-seed-plus.js b/extensions/CHECK/sd-webui-reuse-seed-plus/javascript/reuse-seed-plus.js new file mode 100644 index 0000000000000000000000000000000000000000..0fae09a0c1689846553951049f5719099dab7af2 --- /dev/null +++ b/extensions/CHECK/sd-webui-reuse-seed-plus/javascript/reuse-seed-plus.js @@ -0,0 +1,289 @@ +// Seed Reuse Generate button delay time +function reuseDelayTime() { + let reuseDelayTime = 500; + const reuseDelayTimeInput = document.getElementById("reuse-seed-delay-time"); + if (reuseDelayTimeInput) { + const reuseDelayTimeInputValue = reuseDelayTimeInput.value; + if (parseInt(reuseDelayTimeInputValue) != reuseDelayTime) + reuseDelayTime = parseInt(reuseDelayTimeInputValue); + } + return reuseDelayTime; +} + +// Add Generate Button (🎲, ♻, +1) to txt2img and img2img +function addButtons(tab) { + const normalDelayTime = 10; + const generateButton = document.getElementById(`${tab}_generate`); + const reuseSeedButton = document.getElementById(`${tab}_reuse_seed`); + const skipButton = document.getElementById(`${tab}_skip`); + const seedInput = document.querySelector(`#${tab}_seed > label > input`); + const buttonClass = "lg primary gradio-button svelte-cmf5ev"; + const randomGenerateButtonId = `${tab}_rsp_random_generate_button`; + const reuseGenerateButtonId = `${tab}_rsp_reuse_generate_button`; + const plus1GenerateButtonId = `${tab}_rsp_plus1_generate_button`; + + function createElementFromHTML(html, callback) { + const tempEl = document.createElement("div"); + tempEl.innerHTML = html; + const child = tempEl.firstElementChild; + child.addEventListener("click", callback); + return child; + } + + function delayGenerate(time = normalDelayTime) { + setTimeout(function () { + generateButton.click(); + }, time); + } + + const box = document.querySelector(`#${tab}_generate_box`); + box.style.gap = "4px"; + box.append( + createElementFromHTML( + ``, + function () { + seedInput.value = -1; + seedInput.dispatchEvent(new Event("input")); + delayGenerate(); + } + ) + ); + box.append( + createElementFromHTML( + ``, + function () { + reuseSeedButton.click(); + delayGenerate(reuseDelayTime()); + } + ) + ); + box.append( + createElementFromHTML( + ``, + function () { + if (seedInput.value != -1) { + seedInput.value = parseInt(seedInput.value) + 1; + seedInput.dispatchEvent(new Event("input")); + delayGenerate(); + } else { + } + } + ) + ); + + // Add css to skip button + skipButton.style.zIndex = 1; + + // Monitor seed + ["txt2img", "img2img"].forEach(function (tab) { + const seedInput = document.querySelector(`#${tab}_seed > label > input`); + const plus1GenerateButton = document.getElementById( + `${tab}_rsp_plus1_generate_button` + ); + setInterval(function () { + // console.log(seedInput.value); + if (plus1GenerateButton) { + if (seedInput.value === "-1") { + plus1GenerateButton.disabled = true; + } else { + plus1GenerateButton.disabled = false; + } + } + }, 1000); + }); +} + +// Add New Generate Butttons +function addNewGenerateButtons() { + addButtons("txt2img"); + addButtons("img2img"); +} + +// Add 'Hires.fix with ♻️ & ADetailer' checkbox to txt2img +function createReuseSeedPlusArea() { + const reuseSeedPlus = document.getElementById("reuse_seed_plus"); + const hiresFixCheckbox = document.getElementById( + "txt2img_hr-visible-checkbox" + ); + const settings = document.getElementById("txt2img_settings"); + const accordions = document.getElementById("txt2img_accordions"); + const adetailerCheckbox = + document.querySelector("#script_txt2img_adetailer_ad_enable input") || + document.querySelector( + "#script_txt2img_adetailer_ad_main_accordion-visible-checkbox" + ); + const tiledvaeCheckbox = document.getElementById( + "MDV-t2i-enabled-visible-checkbox" + ); + + if (!accordions || reuseSeedPlus) return; + + const reuseSeedPlusArea = document.createElement("div"); + reuseSeedPlusArea.id = "reuse_seed_plus"; + reuseSeedPlusArea.style = + "display: flex; flex-wrap: wrap; gap: 0.6em 1em; padding: 0.4em 0 0.2em"; + + // Create checkbox item + ["rsp_reuse_seed_plus", "rsp_adetailer", "rsp_tiled_vae"].forEach(function ( + idName, + index + ) { + if (idName === "rsp_adetailer" && !adetailerCheckbox) return; + if (idName === "rsp_tiled_vae" && !tiledvaeCheckbox) return; + + const labelTitle = [ + adetailerCheckbox + ? "Enable 'Reuse seed' and 'ADetailer' when enabling Hires.fix" + : "Enable 'Reuse seed' when enabling Hires.fix", + "Enable 'ADetailer' when enabling Hires.fix", + "Enable 'Tiled VAE' when enabling Hires.fix", + ]; + + const spanText = [ + adetailerCheckbox ? "Hires.fix with ♻️ & ADetailer" : "Hires.fix with ♻️", + "Hires.fix with ADetailer", + "Hires.fix with Tiled VAE", + ]; + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.id = `${idName}_toggle`; + checkbox.className = "svelte-1ojmf70"; + + const label = document.createElement("label"); + label.id = `${idName}_label}`; + label.className = "svelte-1ojmf70"; + label.style = "display: inline-block;"; + label.setAttribute("title", labelTitle[index]); + + const span = document.createElement("span"); + span.className = "ml-2 svelte-1ojmf70"; + span.textContent = spanText[index]; + + label.appendChild(checkbox); + label.appendChild(span); + reuseSeedPlusArea.appendChild(label); + }); + + reuseSeedPlusArea.innerHTML += ` +
+
+ + +
+
+ `; + + if (accordions) { + settings.insertBefore(reuseSeedPlusArea, accordions); + } + + hiresFixCheckbox.addEventListener("change", handleHiresFixChange); +} + +function handleHiresFixChange() { + const hiresFixCheckbox = document.getElementById( + "txt2img_hr-visible-checkbox" + ); + const reuseSeedPlusToggle = document.querySelector( + "#reuse_seed_plus #rsp_reuse_seed_plus_toggle" // Use of querySelector for conflict avoidance + ); + const adetailerToggle = document.querySelector( + "#reuse_seed_plus #rsp_adetailer_toggle" // Use of querySelector for conflict avoidance + ); + const tiledVaeToggle = document.querySelector( + "#reuse_seed_plus #rsp_tiled_vae_toggle" // Use of querySelector for conflict avoidance + ); + // ADetailer + const targetCheckbox1 = + document.querySelector("#script_txt2img_adetailer_ad_enable input") || + document.querySelector( + "#script_txt2img_adetailer_ad_main_accordion-visible-checkbox" + ); + // Tiled VAE + const targetCheckbox2 = document.getElementById( + "MDV-t2i-enabled-visible-checkbox" + ); + const targetButton1 = document.getElementById("txt2img_random_seed"); + const targetButton2 = document.getElementById("txt2img_reuse_seed"); + + if (reuseSeedPlusToggle.checked) { + setTimeout(function () { + if (hiresFixCheckbox.checked) { + if (targetCheckbox1) { + targetCheckbox1.checked = true; + targetCheckbox1.dispatchEvent(new Event("change", { bubbles: true })); + } + if (targetButton2) targetButton2.click(); + } else { + if (targetCheckbox1) { + targetCheckbox1.checked = false; + targetCheckbox1.dispatchEvent(new Event("change", { bubbles: true })); + } + if (targetButton1) targetButton1.click(); + } + }, 10); + } + + if (!reuseSeedPlusToggle.checked) { + if (adetailerToggle.checked) { + setTimeout(function () { + if (hiresFixCheckbox.checked) { + if (targetCheckbox1) { + targetCheckbox1.checked = true; + targetCheckbox1.dispatchEvent( + new Event("change", { bubbles: true }) + ); + } + } else { + if (targetCheckbox1) { + targetCheckbox1.checked = false; + targetCheckbox1.dispatchEvent( + new Event("change", { bubbles: true }) + ); + } + } + }, 10); + } + } + + if (tiledVaeToggle.checked) { + setTimeout(function () { + if (hiresFixCheckbox.checked) { + if (targetCheckbox2) { + // Use click() because in some cases it does not work with checked,dispatchEvent + targetCheckbox2.click(); + // console.log(targetCheckbox2.checked); + setTimeout(function () { + if (!targetCheckbox2.checked) targetCheckbox2.click(); + }, 5); + } + } else { + if (targetCheckbox2) { + // Use click() because in some cases it does not work with checked,dispatchEvent + targetCheckbox2.click(); + // console.log(targetCheckbox2.checked); + setTimeout(function () { + if (targetCheckbox2.checked) targetCheckbox2.click(); + }, 5); + } + } + }, 10); + } +} + +// Function Execution +onUiLoaded(function () { + addNewGenerateButtons(); + createReuseSeedPlusArea(); +}); diff --git a/extensions/CHECK/sd-webui-sdxl-latent-tweaking/.gitignore b/extensions/CHECK/sd-webui-sdxl-latent-tweaking/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..68bc17f9ff2104a9d7b6777058bb4c343ca72609 --- /dev/null +++ b/extensions/CHECK/sd-webui-sdxl-latent-tweaking/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/extensions/CHECK/sd-webui-sdxl-latent-tweaking/LICENSE b/extensions/CHECK/sd-webui-sdxl-latent-tweaking/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/extensions/CHECK/sd-webui-sdxl-latent-tweaking/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/extensions/CHECK/sd-webui-sdxl-latent-tweaking/README.md b/extensions/CHECK/sd-webui-sdxl-latent-tweaking/README.md new file mode 100644 index 0000000000000000000000000000000000000000..fae8f396bf98ca983292e76a55d5c2f04e7228b6 --- /dev/null +++ b/extensions/CHECK/sd-webui-sdxl-latent-tweaking/README.md @@ -0,0 +1,23 @@ +# sd-webui-sdxl-latent-tweaking + +Implementing 3 tweaks introduced in [HF Blog](https://huggingface.co/blog/TimothyAlexisVass/explaining-the-sdxl-latent-space), go check the blog out if you are interested in the details. + +## TL;DR + +SDXL's latent has 4 channels, first three are CIELAB color space, the last is pattern/structure: Brightness(L*), Color1(negative a*), Color2(b*), Pattern/Structure + +by tweaking those 4 channels, you can get a lot of interesting results. For more manual control, checkout [CD Tuner](https://github.com/hako-mikan/sd-webui-cd-tuner) + +In this extension, the following 3 tweaks are implemented: + +- Soft Clamping: by remove outliers in the latent space, less messy objects will be generated. This is intended to be activated during begining of sampling. +- Centering Latent: by centering the latent space, you will get more "neutral" results. Depending by the selected channel, you can get "auto exposure" effect (Contrast Channel), "White ballance" effect (Color Channel), "Minor improvement" effect (Other Channel), or all above. +- Maximizing Latent: by maximizing the latent space (-4.0~4.0), you will get some "HDR" looking image. The effect is also dependent on the selected channel. In latest version, the Brightness channel has specific operation to mimic 'level' in PhotoShop, so that the half tone won't shift away. + +## For SD1.5 + +According to [sd-webui-diffusion-cg](https://github.com/Haoming02/sd-webui-diffusion-cg?tab=readme-ov-file#stable-diffusion-structures), SD1.5's 4 channels are: Negative Black, Negative Magenta, Cyan, Yellow, so if you want to tweak color, you have to select both `Color` and `Pattern` to get correct results. + +## New option for Hires Fix + +If you find hires fix shifting the color off, you may disable the tweakings when hires fix is activated by checking `Disable When HR Fix`. diff --git a/extensions/CHECK/sd-webui-sdxl-latent-tweaking/scripts/__pycache__/sdxl_latent_tweak.cpython-310.pyc b/extensions/CHECK/sd-webui-sdxl-latent-tweaking/scripts/__pycache__/sdxl_latent_tweak.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cbfa1aa496958a7ea47ebc4dc2f9f48e2c8772c9 Binary files /dev/null and b/extensions/CHECK/sd-webui-sdxl-latent-tweaking/scripts/__pycache__/sdxl_latent_tweak.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-sdxl-latent-tweaking/scripts/sdxl_latent_tweak.py b/extensions/CHECK/sd-webui-sdxl-latent-tweaking/scripts/sdxl_latent_tweak.py new file mode 100644 index 0000000000000000000000000000000000000000..e295ee32b33e228c410c388236c6bf553a6cf7c5 --- /dev/null +++ b/extensions/CHECK/sd-webui-sdxl-latent-tweaking/scripts/sdxl_latent_tweak.py @@ -0,0 +1,511 @@ +# Copyright 2023 SLAPaper +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import itertools as it +import sys +import typing as tg + +import gradio as gr # type: ignore +import torch + +import modules.processing as mp +import modules.script_callbacks as msc +import modules.scripts as ms + +_G_CURR_STATE: dict[str, tg.Any] = {} + + +class SdxlLatentTweaking(ms.Script): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + def title(self): + """this function should return the title of the script. This is what will be displayed in the dropdown menu.""" + return "SDXL Latent Tweaking" + + def show(self, is_img2img: bool): + """ + is_img2img is True if this function is called for the img2img interface, and Fasle otherwise + + This function should return: + - False if the script should not be shown in UI at all + - True if the script should be shown in UI if it's selected in the scripts dropdown + - script.AlwaysVisible if the script should be shown in UI at all times + """ + return ms.AlwaysVisible + + @staticmethod + def _parse_channels(infodict: dict[str, str], infokey: str) -> list[str]: + """parse channels from info dict""" + channels = infodict[infokey] + if ";" in channels: + return channels.split(";") + return [channels] + + def ui(self, is_img2img: bool): + """this function should create gradio UI elements. See https://gradio.app/docs/#components + The return value should be an array of all components that are used in processing. + Values of those returned components will be passed to run() and process() functions. + """ + with gr.Accordion(self.title(), open=False): + with gr.Row(): + with gr.Column(): + enable_clamping = gr.Checkbox(label="Enable Clamping", value=False) + clamping_factor = gr.Slider( + label="Clamping Factor", + value=0.998, + minimum=0.0, + maximum=1.0, + step=0.001, + ) + clamping_start = gr.Slider( + label="Clamping Start", + value=0.0, + minimum=0.0, + maximum=1.0, + step=0.01, + ) + clamping_end = gr.Slider( + label="Clamping End", + value=0.05, + minimum=0.0, + maximum=1.0, + step=0.01, + ) + with gr.Row(): + with gr.Column(): + enable_centering = gr.Checkbox( + label="Enable Centering", value=False + ) + centering_channels = gr.CheckboxGroup( + label="Centering Channels", + choices=["Brightness", "Color", "Pattern"], + ) + centering_start = gr.Slider( + label="Centering Start", + value=0.0, + minimum=0.0, + maximum=1.0, + step=0.01, + ) + centering_end = gr.Slider( + label="Centering End", + value=0.3, + minimum=0.0, + maximum=1.0, + step=0.01, + ) + with gr.Row(): + with gr.Column(): + enable_maximizing = gr.Checkbox( + label="Enable Maximizing", value=False + ) + maximizing_channels = gr.CheckboxGroup( + label="Maximizing Channels", + choices=["Brightness", "Color", "Pattern"], + ) + maximizing_start = gr.Slider( + label="Maximizing Start", + value=0.9, + minimum=0.0, + maximum=1.0, + step=0.01, + ) + maximizing_end = gr.Slider( + label="Maximizing End", + value=1.0, + minimum=0.0, + maximum=1.0, + step=0.01, + ) + with gr.Row(): + enable_debug_log = gr.Checkbox(label="Enable Debug Log", value=False) + disable_when_hr = gr.Checkbox(label="Disable When HR Fix", value=False) + + self.infotext_fields = [ # type: ignore + (enable_clamping, "Latent Soft Clamping"), + (clamping_factor, "Latent Soft Clamping Factor"), + ( + clamping_start, + lambda d: d["Latent Soft Clamping Range"].split(";")[0] + if "Latent Soft Clamping Range" in d + and ";" in d["Latent Soft Clamping Range"] + else gr.update(), + ), + ( + clamping_end, + lambda d: d["Latent Soft Clamping Range"].split(";")[1] + if "Latent Soft Clamping Range" in d + and ";" in d["Latent Soft Clamping Range"] + else gr.update(), + ), + (enable_centering, "Latent Centering"), + ( + centering_channels, + lambda d: self._parse_channels(d, "Latent Centering Channels") + if "Latent Centering Channels" in d + else gr.update(), + ), + ( + centering_start, + lambda d: d["Latent Centering Range"].split(";")[0] + if "Latent Centering Range" in d and ";" in d["Latent Centering Range"] + else gr.update(), + ), + ( + centering_end, + lambda d: d["Latent Centering Range"].split(";")[1] + if "Latent Centering Range" in d and ";" in d["Latent Centering Range"] + else gr.update(), + ), + (enable_maximizing, "Latent Maximizing"), + ( + maximizing_channels, + lambda d: self._parse_channels(d, "Latent Maximizing Channels") + if "Latent Maximizing Channels" in d + else gr.update(), + ), + ( + maximizing_start, + lambda d: d["Latent Maximizing Range"].split(";")[0] + if "Latent Maximizing Range" in d + and ";" in d["Latent Maximizing Range"] + else gr.update(), + ), + ( + maximizing_end, + lambda d: d["Latent Maximizing Range"].split(";")[1] + if "Latent Maximizing Range" in d + and ";" in d["Latent Maximizing Range"] + else gr.update(), + ), + (disable_when_hr, "Latent Tweaking Disable HR"), + ] + + self.paste_field_names = [f for _, f in self.infotext_fields] # type: ignore + + msc.on_cfg_denoised(SdxlLatentTweaking.denoise_callback) + + return [ + enable_clamping, + clamping_factor, + clamping_start, + clamping_end, + enable_centering, + centering_channels, + centering_start, + centering_end, + enable_maximizing, + maximizing_channels, + maximizing_start, + maximizing_end, + enable_debug_log, + disable_when_hr, + ] + + def process(self, p: mp.StableDiffusionProcessing, *args): + """ + This function is called before processing begins for AlwaysVisible scripts. + You can modify the processing object (p) here, inject hooks, etc. + args contains all values returned by components from ui() + """ + + ( + enable_clamping, + clamping_factor, + clamping_start, + clamping_end, + enable_centering, + centering_channels, + centering_start, + centering_end, + enable_maximizing, + maximizing_channels, + maximizing_start, + maximizing_end, + enable_debug_log, + disable_when_hr, + ) = args + + _G_CURR_STATE["enable_clamping"] = enable_clamping + _G_CURR_STATE["clamping_factor"] = clamping_factor + _G_CURR_STATE["clamping_start"] = clamping_start + _G_CURR_STATE["clamping_end"] = clamping_end + _G_CURR_STATE["enable_centering"] = enable_centering + _G_CURR_STATE["centering_channels"] = centering_channels + _G_CURR_STATE["centering_start"] = centering_start + _G_CURR_STATE["centering_end"] = centering_end + _G_CURR_STATE["enable_maximizing"] = enable_maximizing + _G_CURR_STATE["maximizing_channels"] = maximizing_channels + _G_CURR_STATE["maximizing_start"] = maximizing_start + _G_CURR_STATE["maximizing_end"] = maximizing_end + _G_CURR_STATE["enable_debug_log"] = enable_debug_log + _G_CURR_STATE["disable_when_hr"] = disable_when_hr + _G_CURR_STATE["in_hr"] = False + + if enable_clamping: + p.extra_generation_params["Latent Soft Clamping"] = True + p.extra_generation_params["Latent Soft Clamping Factor"] = clamping_factor + p.extra_generation_params[ + "Latent Soft Clamping Range" + ] = f"{clamping_start};{clamping_end}" + + if enable_centering: + p.extra_generation_params["Latent Centering"] = True + p.extra_generation_params["Latent Centering Channels"] = ";".join( + centering_channels + ) + p.extra_generation_params[ + "Latent Centering Range" + ] = f"{centering_start};{centering_end}" + + if enable_maximizing: + p.extra_generation_params["Latent Maximizing"] = True + p.extra_generation_params["Latent Maximizing Channels"] = ";".join( + maximizing_channels + ) + p.extra_generation_params[ + "Latent Maximizing Range" + ] = f"{maximizing_start};{maximizing_end}" + + if ( + any( + ( + enable_clamping, + enable_centering, + enable_maximizing, + ) + ) + and disable_when_hr + ): + p.extra_generation_params["Latent Tweaking Disable HR"] = True + + def before_hr(self, p: mp.StableDiffusionProcessing, *args): + """ + This function is called before hires fix start. + """ + _G_CURR_STATE["in_hr"] = True + + @staticmethod + def denoise_callback(params: msc.CFGDenoisedParams): + """callback of denoise process""" + + current_step = params.sampling_step + total_step = params.total_sampling_steps + + def print_debug_log(stage: str) -> None: + """print debug log""" + if not _G_CURR_STATE.get("enable_debug_log"): + return + + print( + f"SDXL Latent Tweaking DEBUG: {stage}", + f"({current_step}/{total_step})", + f"(size:{params.x.size()})", + f"(chmax:{torch.amax(params.x, (0, 1, 2, 3))})", + f"(chmin:{torch.amin(params.x, (0, 1, 2, 3))})", + f"(mean:{torch.mean(params.x)})", + "...", + file=sys.stderr, + ) + + if not _G_CURR_STATE: + print_debug_log("no state") + return + + if _G_CURR_STATE["disable_when_hr"] and _G_CURR_STATE["in_hr"]: + print_debug_log("disable when hires") + return + + if ( + _G_CURR_STATE["enable_clamping"] + and current_step >= _G_CURR_STATE["clamping_start"] * total_step + and current_step <= _G_CURR_STATE["clamping_end"] * total_step + ): + upper = torch.abs(torch.max(params.x)) + lower = torch.abs(torch.min(params.x)) + print_debug_log("before soft clamping") + threshold = torch.max(upper, lower) * _G_CURR_STATE["clamping_factor"] + params.x = soft_clamp_tensor( + params.x, + threshold=threshold * _G_CURR_STATE["clamping_factor"], + boundary=threshold, + ) + print_debug_log("after soft clamping") + + if ( + _G_CURR_STATE["enable_centering"] + and current_step >= _G_CURR_STATE["centering_start"] * total_step + and current_step <= _G_CURR_STATE["centering_end"] * total_step + ): + print_debug_log("before centering") + params.x = center_tensor( + params.x, + 0.8, + 0.8, + channels=channel_name_to_channel_index( + _G_CURR_STATE["centering_channels"] + ), + ) + print_debug_log("after centering") + + if ( + _G_CURR_STATE["enable_maximizing"] + and current_step >= _G_CURR_STATE["maximizing_start"] * total_step + and current_step <= _G_CURR_STATE["maximizing_end"] * total_step + ): + print_debug_log("before maximizing") + # use v2 maximizing for brightness channel + selected_channel_names: list[str] = _G_CURR_STATE["maximizing_channels"] + if "Brightness" in selected_channel_names: + selected_channel_names.remove("Brightness") + params.x = maximize_tensor_v2( + params.x, channels=channel_name_to_channel_index(["Brightness"]) + ) + print_debug_log("after brightness v2 maximizing") + + channels = channel_name_to_channel_index(selected_channel_names) + params.x = center_tensor( + params.x, + 0.6, + 1.0, + channels=channels, + ) + print_debug_log("in maximizing") + params.x = maximize_tensor( + params.x, + channels=channels, + ) + print_debug_log("after maximizing") + + +CHANNEL_NAME_TO_INDEX: dict[str, list[int]] = { + "Brightness": [0], + "Color": [1, 2], + "Pattern": [3], +} + + +def channel_name_to_channel_index(name_list: list[str]) -> list[int]: + """convert channel name to index""" + return list( + it.chain.from_iterable( + CHANNEL_NAME_TO_INDEX[name] + for name in name_list + if name in CHANNEL_NAME_TO_INDEX + ) + ) + + +# Shrinking towards the mean (will also remove outliers) +def soft_clamp_tensor( + input_tensor: torch.Tensor, + threshold: float | torch.Tensor = 3.5, + boundary: float | torch.Tensor = 4, +): + """huggingface.co/blog/TimothyAlexisVass/explaining-the-sdxl-latent-space#outlier-removal""" + if ( + torch.max(torch.abs(input_tensor.max()), torch.abs(input_tensor.min())) + < threshold + ): + return input_tensor + + channel_dim = 1 + + max_vals = input_tensor.max(channel_dim, keepdim=True)[0] + max_replace = ((input_tensor - threshold) / (max_vals - threshold)) * ( + boundary - threshold + ) + threshold + over_mask = input_tensor > threshold + + min_vals = input_tensor.min(channel_dim, keepdim=True)[0] + min_replace = ((input_tensor + threshold) / (min_vals + threshold)) * ( + -boundary + threshold + ) - threshold + under_mask = input_tensor < -threshold + + return torch.where( + over_mask, max_replace, torch.where(under_mask, min_replace, input_tensor) + ) + + +# Center tensor (balance colors) +def center_tensor( + input_tensor: torch.Tensor, + per_channel_shift: float = 1.0, + full_tensor_shift: float = 1.0, + channels=[1, 2], +): + """huggingface.co/blog/TimothyAlexisVass/explaining-the-sdxl-latent-space#color-balancing-and-increased-range""" + for channel in channels: + input_tensor[:, channel] -= input_tensor[:, channel].mean() * per_channel_shift + return input_tensor - input_tensor.mean() * full_tensor_shift + + +# Maximize/normalize tensor +def maximize_tensor( + input_tensor: torch.Tensor, + boundary: float = 4.0, + channels=[0, 1, 2], +): + """huggingface.co/blog/TimothyAlexisVass/explaining-the-sdxl-latent-space#color-balancing-and-increased-range""" + min_val = input_tensor.min() + max_val = input_tensor.max() + + normalization_factor = boundary / torch.max(torch.abs(min_val), torch.abs(max_val)) + input_tensor[:, channels] *= normalization_factor + + return input_tensor + + +# Maximize tensor (v2) +def maximize_tensor_v2( + input_tensor: torch.Tensor, + boundary: float = 4.0, + channels=[0, 1, 2], +): + """like the 'level' in PhotoShop, scale min/max value while keeping the mean + + Input: (batch, channel, width, height) + """ + for i in range(input_tensor.size(0)): + # Select the specific channel for this batch item + channel_data = input_tensor[i, channels, :, :] + + # Check if data contains both positive and negative values + if torch.any(channel_data < 0) and torch.any(channel_data > 0): + # Calculate the mean + mean = channel_data.mean() + + # Scale values differently based on their relation to the mean + channel_data = torch.where( + channel_data < mean, + -boundary * (channel_data / channel_data[channel_data < mean].min()), + channel_data, + ) + channel_data = torch.where( + channel_data > mean, + boundary * (channel_data / channel_data[channel_data > mean].max()), + channel_data, + ) + else: + # Rescale such that the absolute maximum value is boundary + max_abs_val = torch.max(torch.abs(channel_data)) + if max_abs_val > 0: + channel_data = boundary * channel_data / max_abs_val + + # Update the tensor + input_tensor[i, channels, :, :] = channel_data + + return input_tensor diff --git a/extensions/CHECK/sd-webui-weight-helper/.github/FUNDING.yml b/extensions/CHECK/sd-webui-weight-helper/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..0a87b5c7824372770bc8682b2ab020413e73fd9b --- /dev/null +++ b/extensions/CHECK/sd-webui-weight-helper/.github/FUNDING.yml @@ -0,0 +1,2 @@ +ko_fi: nihedon +buy_me_a_coffee: nihedon diff --git a/extensions/CHECK/sd-webui-weight-helper/LICENSE b/extensions/CHECK/sd-webui-weight-helper/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..6f0da084b8535f2e3c094505138597a1d0f178b1 --- /dev/null +++ b/extensions/CHECK/sd-webui-weight-helper/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 t.nihei + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/extensions/CHECK/sd-webui-weight-helper/README.md b/extensions/CHECK/sd-webui-weight-helper/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9269223065a5660843893892cb47a69427551b51 --- /dev/null +++ b/extensions/CHECK/sd-webui-weight-helper/README.md @@ -0,0 +1,69 @@ +[English](README.md) | [日本語](README_JP.md) + + + +# Weight Helper Extension + +## Overview + +**Weight Helper** is an extension that allows you to visually adjust the weights of Lora and Lyco with mouse operations.
+You can adjust the magnification of each weight block through the context menu.
+Note: The LoRA Block Weight extension is required to use this extension. + +## Installation + +1. Open the [Extensions] tab in Stable Diffusion Web UI. +2. Select [Install from URL]. +3. Enter the following URL and execute the installation. +``` +https://github.com/nihedon/sd-webui-weight-helper.git +``` + +## Usage + +1. Right-click on a Lora or Lyco tag to open the context menu. + - `` + - `` +2. Choose "LoRA" or "LyCORIS" according to the tag, and select "SDXL" if it's for the SDXL version. +3. Move the weight slider to adjust the magnification of each weight block. +4. UNet, Dyn, Start, and Stop will be hidden by default, but if configured, you can display them by pressing the "show more options" button. +5. Save your favorite weight settings by clicking the "★" icon at the top left of the context menu. + Important: Saved settings are stored in LocalStorage, but any unsaved history will be deleted upon refreshing the screen. + +## Features + +- Easy to set weight values with mouse operations. +- Use of preset values from Lora Block Weight is possible. +- You can check LoRA information and add trigger words from the icons displayed on the preview screen. +- Information from the readme file attached to LoRA can be displayed on the preview screen. ({loraname}.txt, {loraname}.description.txt) +- The SD version of LoRA is displayed based on the metadata of LoRA. (If there is metadata text from Civitai, the SD version registered with Civitai will also be displayed.) + +## Known Issues +- The algorithm is deduced from the metadata of LoRA, but it is incomplete. Bug reports and suggestions for the correct algorithm would be appreciated. + +## Configuration Options + +- Scale adjustment in the context menu. +- Use of the `execCommand` function for text replacement (Note: execCommand is a deprecated function, but it allows for "undo" and "redo"). +- Display of UNet, Dyn, Start, and Stop sliders. +- Setting of minimum, maximum, and step values for each weight slider. +- Detailed settings for LBW's Lora and Lyco blocks. +- Option to display or hide previews, and settings for preview size and position. +- Settings for block weight segmentation. + +## Guaranteed Operating Environment + +- Stable Diffusion AUTOMATIC1111 +- Windows +- Google Chrome + +## Acknowledgments + +We would like to express our deep gratitude to **hako-mikan**, the author of **LoRA Block Weight**, for creating this extension. +``` +https://github.com/hako-mikan/sd-webui-lora-block-weight +``` + +## License + +This project is published under the [MIT License](LICENSE). diff --git a/extensions/CHECK/sd-webui-weight-helper/README_JP.md b/extensions/CHECK/sd-webui-weight-helper/README_JP.md new file mode 100644 index 0000000000000000000000000000000000000000..8eed3fda05fbc2ef6341434cdcce6d38a6f705e4 --- /dev/null +++ b/extensions/CHECK/sd-webui-weight-helper/README_JP.md @@ -0,0 +1,69 @@ +[English](README.md) | [日本語](README_JP.md) + + + +# Weight Helper 拡張機能 + +## 概要 + +**Weight Helper** はLoraやLycoのウェイトをマウス操作で視覚的に調整できる拡張機能です。
+コンテキストメニューを通じて各ウェイトブロックの倍率を調整できます。
+注:この拡張機能を利用するにはLoRA Block Weight拡張機能が必要です。 + +## インストール + +1. Stable Diffusion Web UIの[拡張機能]タブを開く +2. [URLからインストール]を選択 +3. 以下のURLを入力し、インストールを実行 +``` +https://github.com/nihedon/sd-webui-weight-helper.git +``` + +## 使い方 + +1. LoraまたはLycoのタグを右クリックしてコンテキストメニューを開く + - `` + - `` +2. LoRA(またはLyCORIS)に合わせて"LoRA"か"LyCORIS"を選択し、SDXLバージョンの場合は"SDXL"を選択します。 +3. ウェイトスライダーを動かして各ウェイトブロックの倍率を調整します。 +4. UNet、Dyn、Start、Stopは非表示となっているため、設定した場合は"show more options"ボタンを押下して表示できます。 +5. お気に入りのウェイト設定はコンテキストメニュー左上の"★"アイコンをクリックし保存します。 + 重要:保存した場合はLocalStrageに保存されますが、保存していない履歴は画面更新時にすべて削除されます。 + +## 特徴 + +- マウス操作でウェイト値を容易に設定できます。 +- Lora Block Weightのプリセット値の利用が可能です。 +- プレビュー画面に表示されるアイコンからLoRA情報の確認、トリガーワードの追加ができます。 +- LoRAに付属するreadmeファイルの情報をプレビュー画面に表示できます。({loraname}.txt, {loraname}.description.txt) +- LoRAのメタ情報からLoRAのSDバージョンが表示されます。(Civitaiのメタテキストが存在する場合、civitaiに登録したSDバージョンも表示します。) + +## 既知の問題 +- LoRAのメタ情報からアルゴリズムを割り出していますが不完全です。不具合報告や正しいアルゴリズムを提示していただけると助かります(_ _) + +## 設定オプション + +- コンテキストメニューのスケール調整 +- テキスト置換に `execCommand` 関数の使用(注:execCommandは非推奨関数ですが、「元に戻す」「やり直し」が使えます) +- UNet、Dyn、Start、およびStopスライダーの表示 +- 各ウェイトスライダーの最小値、最大値、ステップ数の設定 +- LBWのLoraおよびLycoブロックの詳細設定 +- プレビューの表示有無、表示サイズと表示位置の設定 +- ブロックウェイトの区切り設定 + +## 動作保証環境 + +- Stable Difusion AUTOMATIC1111 +- Windows +- Google Chrome + +## 謝辞 + +この拡張機能を作成するにあたり、**LoRA Block Weight** の作者である **hako-mikan** 様に深く感謝申し上げます。 +``` +https://github.com/hako-mikan/sd-webui-lora-block-weight +``` + +## ライセンス + +このプロジェクトは[MITライセンス](LICENSE)の下で公開されています。 diff --git a/extensions/CHECK/sd-webui-weight-helper/html/template.hbs b/extensions/CHECK/sd-webui-weight-helper/html/template.hbs new file mode 100644 index 0000000000000000000000000000000000000000..4ca69959b7511d398dbd473d2d229043fbe50328 --- /dev/null +++ b/extensions/CHECK/sd-webui-weight-helper/html/template.hbs @@ -0,0 +1,154 @@ +
+ + {{#with preview}} + {{#if hasResponse}} +
+ +
+ {{#if modelName}}{{#if definedExtraNetworksRequestMetadata}}{{#if hasMetadata}} + + {{/if}}{{/if}}{{/if}} + {{#if modelName}}{{#if definedExtraNetworksEditUserMetadata}} +
+ {{/if}}{{/if}} + {{#if modelId}} +
+ {{/if}} + {{#if hasTriggerWords}} +
+ {{/if}} +
+ {{#if description}} +
+
+
+ +
+ {{/if}} +
+ {{/if}} + {{/with}} +
+
+ + {{#with lock}} + + + + + + {{/with}} + + +
+ clear +
+ < + + > +
+
+
+ + {{#each weights}} +
+ + + {{#if this.useCheck}} + + {{/if}} + +
+ + +
+
+ {{/each}} + +
+ +
+
+ +
+
+ {{#each sdvers}} + + + {{/each}} +
+
+
+
+ +
+
+ {{#with xyz}} + + + {{/with}} +
+
+
+
+ + {{#each lbwBlocks}} +
+ + {{#each lbws}} +
+ +
+ + +
+
+ {{/each}} +
+
+ {{/each}} +
+
+
+
+
diff --git a/extensions/CHECK/sd-webui-weight-helper/javascript/weight_helper.js b/extensions/CHECK/sd-webui-weight-helper/javascript/weight_helper.js new file mode 100644 index 0000000000000000000000000000000000000000..08f6107882a7303fbbf63d459555cd73a1c37047 --- /dev/null +++ b/extensions/CHECK/sd-webui-weight-helper/javascript/weight_helper.js @@ -0,0 +1,1793 @@ +'use strict'; + +(function() { + const VERSION = "1.2.0" + + var jq; + + var weight_helper_data = {}; + var weight_helper_preview_info = {}; + + var sampling_steps; + + const REGEX = /<([^:]+):([^:]+):([^>]+)>/; + const TAG_TYPES = ["lora", "lyco"]; + + const SPECIAL_KEYWORDS = ["XYZ"]; + + const SPECIAL_PRESETS = { + lora: { + SD: { XYZ: "XYZ(17)" }, + SDXL: { XYZ: "XYZ(12)" } + }, + lycoris: { + SD: { XYZ: "XYZ(26)" }, + SDXL: { XYZ: "XYZ(20)" } + }, + unknown: { XYZ: "XYZ(26)" } + }; + + const LORA_TYPE_PULLDOWN = { + "LoRA(LierLa)": "lora", + "LyCORIS,etc": "lycoris" + }; + + const LBW_WEIGHT_SD_VERSIONS = ["SD", "SDXL"]; + + const LBW_WEIGHT_SETTINGS = { + lora: { + SD: { + masks: [1,0,1,1,0,1,1,0,1,1,0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,1], + block_points: ["BASE", "IN01-IN04", "IN05-IN08", "M00", "OUT03-OUT06", "OUT07-OUT11"] + }, + SDXL: { + masks: [1,0,0,0,0,1,1,0,1,1,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0], + block_points: ["BASE", "IN04-IN08", "M00", "OUT00-OUT05"] + } + }, + lycoris: { + SD: { + masks: [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], + block_points: ["BASE", "IN00-IN05", "IN06-IN11", "M00", "OUT00-OUT05", "OUT06-OUT11"] + }, + SDXL: { + masks: [1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0], + block_points: ["BASE", "IN00-IN03", "IN04-IN08", "M00", "OUT00-OUT03", "OUT04-OUT08"] + } + }, + unknown: { + masks: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + block_points: [] + } + }; + + const WEIGHT_SETTINGS = { + te: { + label: "TEnc", + min: undefined, max: undefined, default: 100, step: undefined + }, + unet: { + label: "UNet", + min: undefined, max: undefined, default: undefined, step: undefined + }, + dyn: { + label: "Dyn", + min: undefined, max: undefined, default: undefined, step: undefined + }, + start: { + label: "Start", + min: 0, max: undefined, default: 0, step: 100 + }, + stop: { + label: "Stop", + min: 0, max: undefined, default: undefined, step: 100 + }, + lbw: { + labels: ["BASE", "IN00", "IN01", "IN02", "IN03", "IN04", "IN05", "IN06", "IN07", "IN08", "IN09", "IN10", "IN11", "M00", "OUT00", "OUT01", "OUT02", "OUT03", "OUT04", "OUT05", "OUT06", "OUT07", "OUT08", "OUT09", "OUT10", "OUT11"], + min: undefined, max: undefined, default: 100, step: undefined + } + }; + + class WeightSet { + map; + constructor() { + this.map = new Map(); + } + add(weightDataHash, weightData) { + return this.map.set(weightDataHash, weightData); + } + has(weightDataHash) { + return this.map.has(weightDataHash); + } + delete(weightDataHash) { + return this.map.delete(weightDataHash); + } + clear() { + return this.map.clear(); + } + getAll() { + return [...this.map.values()]; + } + } + + var last_instance = undefined; + + class WeightData { + lbw_lora_type; + lbw_sd_version; + te; + use_unet; + unet; + use_dyn; + dyn; + start; + stop; + lbw; + lbwe; + special; + + constructor(data) { + if (data) { + Object.assign(this, data); + } + } + + isSpecial() { + return this.special != null && this.special !== ""; + } + + clone() { + return new WeightData(structuredClone(this)); + } + + equals(other) { + if (other == null) { + return false; + } + if (this.lbw_lora_type !== other.lbw_lora_type) { + return false; + } + if (this.lbw_sd_version !== other.lbw_sd_version) { + return false; + } + if (this.te[0] !== other.te[0]) { + return false; + } + if (this.te[0] !== other.te[0]) { + return false; + } + if (this.use_unet !== other.use_unet) { + return false; + } + if (this.use_unet && (this.unet[0] != other.unet[0])) { + return false; + } + if (this.use_dyn !== other.use_dyn) { + return false; + } + if (this.use_dyn && (this.dyn[0] != other.dyn[0])) { + return false; + } + if (this.start[0] !== other.start[0]) { + return false; + } + let stop1 = this.stop[0] == null ? sampling_steps : this.stop[0]; + let stop2 = other.stop[0] == null ? sampling_steps : other.stop[0]; + if (stop1 !== stop2) { + return false; + } + if (this.lbw.length !== other.lbw.length) { + return false; + } + if (this.isSpecial()) { + if (this.special !== other.special) { + return false; + } + } else { + let masks = null; + if (this.lbw_lora_type && this.lbw_sd_version) { + masks = LBW_WEIGHT_SETTINGS[this.lbw_lora_type][this.lbw_sd_version].masks; + } + for (let i = 0; i < this.lbw.length; i++) { + if ((!masks || masks[i] === 1) && this.lbw[i] !== other.lbw[i]) { + return false; + } + } + } + return true; + } + + hashCode() { + let hash = 0; + const calcHash = (v) => ((hash ^ v) << 5) - hash ^ v; + hash = calcHash(strHashCode(this.lbw_lora_type)); + hash = calcHash(strHashCode(this.lbw_sd_version)); + hash = calcHash(this.te[0]); + hash = calcHash(this.use_unet ? 1 : 0); + hash = calcHash(this.use_unet ? this.unet[0] : 0); + hash = calcHash(this.use_dyn ? 1 : 0); + hash = calcHash(this.use_dyn ? this.dyn[0] : 0); + hash = calcHash(this.start[0]/10); + hash = calcHash(this.stop[0] == null ? sampling_steps : this.stop[0]); + if (this.isSpecial()) { + hash = calcHash(strHashCode(this.special)); + } else { + let masks = null; + if (this.lbw_lora_type && this.lbw_sd_version) { + masks = LBW_WEIGHT_SETTINGS[this.lbw_lora_type][this.lbw_sd_version].masks; + } + for (let i = 0; i < this.lbw.length; i++) { + const val = !masks || masks[i] === 1 ? this.lbw[i] : 0; + hash = calcHash(val); + } + } + return hash & 0xffffffff; + } + } + + class WeightHelper { + + static MAIN_TEMPLATE; + static PREVIEW_TEMPLATE; + static PRESETS_TEMPLATE; + static LBWBLOCKS_TEMPLATE; + static LBWS_TEMPLATE; + + mainBody; + previewBody; + lbwDOMs = []; + + weightData = {}; + bindData = { + mainBindData: {}, + lbwPresetBindDatas: [], + lbwBlockBindDatas: [], + lbwBindDatas: [], + previewBindData: null + } + weightElements = {}; + + usingExecCommand = false; + + tabId = null; + offsetX = 0; + offsetY = 0; + isDragging = false; + + lbwPresetsMap = {}; + lbwPresetsValueKeyMap = {}; + + metadata = {}; + previewInfo = {}; + + tagName = null; + name = null; + nameHash = null; + multiplier = null; + currentHistory = null; + currentLockSet = new WeightSet(); + weightData = new WeightData(); + + lastSelectionStart = null; + lastSelectionEnd = null; + lastText = null; + + historyIndex = 0; + + openedExtraOption = false; + + releaseFunctions = []; + + static attach(textarea) { + textarea.addEventListener('contextmenu', (e) => { + if (!opts.weight_helper_enabled) { + return; + } + if (last_instance) { + e.preventDefault(); + last_instance.close(); + return; + } + let selectedText = window.getSelection().toString(); + if (selectedText) { + return; + } + const prompt = e.target.value; + let tmpSelectionStart = e.target.selectionStart; + const lCar = prompt.lastIndexOf("<", tmpSelectionStart - 1); + const rCar = prompt.indexOf(">", tmpSelectionStart); + if (lCar < 0 || rCar < 0) { + return; + } + selectedText = prompt.substring(lCar, rCar + 1); + if ((selectedText.match(//g) || []).length != 1) { + return; + } + tmpSelectionStart = lCar; + const match = REGEX.exec(selectedText); + if (match) { + const loraType = match[1].toLowerCase(); + const name = match[2]; + const multiplier = match[3]; + + if (TAG_TYPES.includes(loraType)) { + e.preventDefault(); + const tabId = e.target.closest("[id^='tab_'][class*='tabitem']").id.split("_")[1]; + const selectionStart = tmpSelectionStart + match.index; + const selectionEnd = selectionStart + match.input.trim().length; + WeightHelper.loadTemplate().then(() => { + const weightHelper = new WeightHelper(tabId, e.target, selectionStart, selectionEnd, loraType, name, multiplier); + weightHelper.setup(); + weightHelper.show(e.pageY + 15, e.pageX); + }); + } + } + }); + } + + static async loadTemplate() { + if (WeightHelper.MAIN_TEMPLATE != null) { + return; + } + async function loadHandlebars() { + if ("Handlebars" in window) { + return Promise.resolve(); + } else { + try { + const res = await fetch("https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"); + const script = await res.text(); + new Function(script).call(window); + + Handlebars.registerHelper("lower", str => str.toLowerCase()); + Handlebars.registerHelper("visible", b => b ? "" : "visibility: hidden;"); + Handlebars.registerHelper("display", b => b ? "" : "display: none;"); + Handlebars.registerHelper("checked", b => b ? "checked" : ""); + Handlebars.registerHelper("selected", b => b ? "selected" : ""); + Handlebars.registerHelper("pos", array => { + let str = ""; + str += array[0] == null ? "" : `top: ${array[0]}px;`; + str += array[1] == null ? "" : `right: ${array[1]}px;`; + str += array[2] == null ? "" : `bottom: ${array[2]}px;`; + str += array[3] == null ? "" : `left: ${array[3]}px;`; + return str; + }) + } catch (error) { + console.error("Failed to load Handlebars:", error); + } + } + } + + await loadHandlebars(); + const response = await fetch("/whapi/v1/get_template", { method: "POST", body: null }); + const hbs = await response.json(); + function partialize(hbs, tag) { + const startTag = tag; + const start = hbs.indexOf(startTag); + const endTag = `${tag.substring(0, 1)}/${tag.substring(1)}`; + const end = hbs.indexOf(endTag, start + startTag.length); + const partial = hbs.substring(start + startTag.length, end); + const main = hbs.substring(0, start) + hbs.substring(end + endTag.length); + return {"partial": partial, "main": main} + } + let main, preview, preset, lbwBlock, lbw; + ({ partial: preview, main: main} = partialize(hbs, "")); + ({ partial: preset, main: main} = partialize(main, "")); + ({ partial: lbwBlock, main: main} = partialize(main, "")); + ({ partial: lbw, main: lbwBlock} = partialize(lbwBlock, "")); + WeightHelper.MAIN_TEMPLATE = Handlebars.compile(main); + WeightHelper.PREVIEW_TEMPLATE = Handlebars.compile(preview); + WeightHelper.PRESETS_TEMPLATE = Handlebars.compile(preset); + WeightHelper.LBWBLOCKS_TEMPLATE = Handlebars.compile(lbwBlock); + WeightHelper.LBWS_TEMPLATE = Handlebars.compile(lbw); + } + + constructor(tabId, textarea, selectionStart, selectionEnd, tagName, name, multiplier) { + document.documentElement.style.setProperty('--weight-helper-slider_size', opts.weight_helper_slider_length); + + this.tabId = tabId; + this.textarea = textarea; + this.lastSelectionStart = selectionStart; + this.lastSelectionEnd = selectionEnd; + this.lastText = this.textarea.value.substring(this.lastSelectionStart, this.lastSelectionEnd); + + this.tagName = tagName; + this.name = name; + this.nameHash = strHashCode(name); + this.multiplier = multiplier; + + const samplingSteps = gradioApp().getElementById(`${this.tabId}_steps`).querySelector("input"); + sampling_steps = Math.round(samplingSteps.value * 100); + + if (opts.weight_helper_using_execCommand) { + if (typeof document.execCommand === 'function') { + this.usingExecCommand = true; + } else { + console.warn("execCommand is not supported."); + } + } + } + + setup() { + this.initSettings(); + this.initWeightData(); + this.initHistory(); + + this.setupMainBindData(); + this.setupLbwBindData(); + + this.buildDOM(); + if (this.weightData.lbw_sd_version) { + this.redrawLbw(); + } + } + + initSettings() { + const optBlockPattern = /((BASE|MID|M00|(IN|OUT)[0-9]{1,2}(-(IN|OUT)[0-9]{1,2})?) *(, *|$))+/; + for (let loraType of Object.values(LORA_TYPE_PULLDOWN)) { + for (let sdVersion of LBW_WEIGHT_SD_VERSIONS) { + try { + let optBlockPoints = opts[`weight_helper_lbw_${loraType}_${sdVersion.toLowerCase()}_block_points`] + optBlockPoints = optBlockPoints.replace("MID", "M00"); + if (optBlockPattern.exec(optBlockPoints)) { + const blockPoints = optBlockPoints.split(',').map(v => { + return v.trim().replace(/\d+/g, match => match.length === 1 ? `0${match}` : match); + }); + this.getLbwWeightSetting(loraType, sdVersion).block_points = blockPoints; + } + } catch (e) { + console.warn(`${loraType}_${sdVersion} block definition format is invalid.`, e); + } + } + } + + for (const k of ["te", "unet", "dyn", "lbw"]) { + WEIGHT_SETTINGS[k].min = Math.round(opts[`weight_helper_${k}_min`] * 100); + WEIGHT_SETTINGS[k].max = Math.round(opts[`weight_helper_${k}_max`] * 100); + WEIGHT_SETTINGS[k].step = Math.round(opts[`weight_helper_${k}_step`] * 100); + } + WEIGHT_SETTINGS.start.max = sampling_steps; + WEIGHT_SETTINGS.stop.max = sampling_steps; + WEIGHT_SETTINGS.stop.default = sampling_steps; + + const lbwPreset = gradioApp().getElementById("lbw_ratiospreset").querySelector("textarea"); + let lbwPresetValue = lbwPreset.value ?? ""; + const lbwPresets = lbwPresetValue.split("\n").filter(e => e.trim() !== ''); + for (const loraType of Object.values(LORA_TYPE_PULLDOWN)) { + this.lbwPresetsMap[loraType] = {}; + this.lbwPresetsValueKeyMap[loraType] = {}; + for (const sdVersion of LBW_WEIGHT_SD_VERSIONS) { + let lbwPreset = {}; + let lbwPresetValueKey = {}; + + this.lbwPresetsMap[loraType][sdVersion] = lbwPreset; + this.lbwPresetsValueKeyMap[loraType][sdVersion] = lbwPresetValueKey; + + const blockLength = this.getLbwWeightSetting(loraType, sdVersion).masks.filter((b) => b == 1).length; + for (const line of lbwPresets) { + const kv = line.split(":"); + if (kv.length == 2 && kv[1].split(",").length == blockLength) { + lbwPreset[kv[0]] = kv[1]; + lbwPresetValueKey[kv[1]] = kv[0]; + } + } + } + } + } + + initWeightData() { + if (!(this.nameHash in weight_helper_data)) { + weight_helper_data[this.nameHash] = { "lock": [], "history": [], "lora_info": {} } + } + + if (!("lora_info" in weight_helper_data[this.nameHash])) { + weight_helper_data[this.nameHash].lora_info = {}; + } + const loraInfo = weight_helper_data[this.nameHash].lora_info; + if ("metadata" in loraInfo) { + this.metadata = loraInfo.metadata; + this.weightData.lbw_sd_version = this.metadata.sd_version; + } + if ("selected_lora_type" in loraInfo) { + this.weightData.lbw_lora_type = loraInfo.selected_lora_type.lbw_lora_type; + this.weightData.lbw_sd_version = loraInfo.selected_lora_type.lbw_sd_version; + } else { + loraInfo.selected_lora_type = {}; + } + if (this.nameHash in weight_helper_preview_info) { + this.previewInfo = weight_helper_preview_info[this.nameHash]; + } + + for (const weightType of Object.keys(WEIGHT_SETTINGS)) { + this.weightData[weightType] = [] + this.weightData[weightType].push(WEIGHT_SETTINGS[weightType].default); + } + + this.weightData.lbw = []; + this.weightData.lbwe = []; + this.weightData.special = ""; + + const multipliers = this.multiplier.split(":"); + + const multiplierMap = {} + for (let i = 0; i < multipliers.length; i++) { + let key; + let value; + if (multipliers[i].indexOf("=") >= 0) { + const keyValue = multipliers[i].split("="); + key = keyValue[0].toLowerCase(); + value = keyValue[1]; + } else { + key = ["te", "unet", "dyn"][i]; + value = multipliers[i]; + } + multiplierMap[key] = value; + } + + let assumedSdVersion = null; + let assumedLoraType = null; + Object.entries(multiplierMap).forEach(kv => { + const group = kv[0]; + const value = kv[1]; + if (group === "lbw") { + let blocks = value.split(','); + if (blocks.length === 1 && SPECIAL_KEYWORDS.includes(value)) { + this.weightData.special = value; + } else { + const loraSdCombination = []; + for (const loraType of Object.values(LORA_TYPE_PULLDOWN)) { + for (const sdVersion of LBW_WEIGHT_SD_VERSIONS) { + loraSdCombination.push({ + loraType: loraType, + sdVersion: sdVersion + }); + } + } + for (const loraSd of loraSdCombination) { + const loraType = loraSd.loraType; + const sdVersion = loraSd.sdVersion; + if (blocks.length === 1) { + const lbwPresets = this.getLbwPresets(loraType, sdVersion); + if (value in lbwPresets) { + blocks = lbwPresets[value].split(','); + } else { + continue; + } + } + const masks = this.getLbwWeightSetting(loraType, sdVersion).masks; + if (blocks.length === masks.filter((b) => b == 1).length) { + assumedSdVersion = sdVersion; + assumedLoraType = loraType; + let refIdx = 0; + for (let enable of masks) { + if (enable) { + this.weightData.lbw.push(Math.round(blocks[refIdx] * 100)); + refIdx++; + } else { + this.weightData.lbw.push(0); + } + } + break; + } + } + } + } else if (group === "step") { + const startStop = value.split('-'); + this.weightData.start[0] = Math.round(startStop[0] * 100); + this.weightData.stop[0] = Math.round(startStop[1] * 100); + } else if (group === "lbwe") { + this.weightData[group][0] = value; + } else { + this.weightData[group][0] = Math.round(value * 100); + } + }); + this.weightData.use_unet = this.weightData.unet[0] != null; + this.weightData.use_dyn = this.weightData.dyn[0] != null; + + if (assumedLoraType) { + this.weightData.lbw_lora_type = assumedLoraType; + } + if (!this.weightData.lbw_lora_type) { + if (this.tagName === "lora") { + this.weightData.lbw_lora_type = "lora"; + } else { + this.weightData.lbw_lora_type = "lycoris"; + } + } + if (assumedSdVersion) { + this.weightData.lbw_sd_version = assumedSdVersion; + } + if (!this.weightData.lbw_sd_version) { + this.weightData.lbw_sd_version = this.metadata.sd_version; + } + + const masks = this.getLbwWeightSetting(this.weightData.lbw_lora_type, this.weightData.lbw_sd_version).masks; + this.weightData.masks = masks; + if (this.weightData.lbw.length === 0) { + this.weightData.lbw = new Array(masks.length).fill(100); + } + } + + initHistory() { + if (!("lock" in weight_helper_data[this.nameHash])) { + weight_helper_data[this.nameHash].lock = []; + } + weight_helper_data[this.nameHash].lock.forEach(lock => { + this.currentLockSet.add(lock.hashCode(), lock); + }); + + if (!("history" in weight_helper_data[this.nameHash])) { + weight_helper_data[this.nameHash].history = []; + } + + this.currentHistory = weight_helper_data[this.nameHash].history; + if (this.currentHistory.length == 0) { + this.currentHistory.push(this.weightData.clone()); + } else { + const historyLen = this.currentHistory.length; + const latestHistory = this.currentHistory[historyLen - 1]; + if (this.weightData.isSpecial()) { + this.weightData.lbw = structuredClone(latestHistory.lbw); + } + if (!this.weightData.equals(latestHistory)) { + this.currentHistory.push(this.weightData.clone()); + } + } + this.historyIndex = this.currentHistory.length - 1; + } + + initWeightForm(weight, weightSetting, weightData, idx = 0) { + const fVal = weightData[idx]; + weight.sliderMin = fVal < weightSetting.min ? fVal : weightSetting.min; + weight.sliderMax = fVal > weightSetting.max ? fVal : weightSetting.max; + weight.sliderStep = weightSetting.step; + weight.sliderValue = fVal; + weight.updownStep = weightSetting.step / 100; + weight.updownValue = weightData[idx] / 100; + } + + setupMainBindData() { + const mainBindData = []; + this.bindData.mainBindData = mainBindData; + + mainBindData.title = this.name; + mainBindData.page = `${this.historyIndex + 1}/${this.currentHistory.length}`; + let scale = opts.weight_helper_context_menu_scale; + if (scale <= 0) { + scale = 1; + } + mainBindData.scale = scale; + + const lock = {} + mainBindData.lock = lock; + const weightDataHash = this.weightData.hashCode(); + const isLocked = this.currentLockSet.has(weightDataHash); + lock.flag = isLocked ? "like" : "unlike"; + lock.visible = !this.weightData.isSpecial() && this.weightData.lbw_sd_version; + + const weights = [] + mainBindData.weights = weights; + for (const group of Object.keys(WEIGHT_SETTINGS)) { + if (group === "lbw") continue; + + const weightSetting = WEIGHT_SETTINGS[group]; + const weightData = this.weightData[group]; + + const weight = {} + const useCheck = weightSetting.default === undefined; + if (useCheck) { + if (weightData[0] != null) { + weight.checked = true; + } else { + weightData[0] = 0; + } + } + + this.initWeightForm(weight, weightSetting, weightData); + weight.group = group; + weight.label = weightSetting.label; + weight.useCheck = useCheck; + weight.visible = true; + if (group !== "te") { + if (useCheck) { + if (!weight.checked) { + weight.visible = false; + } + } else if (weightData[0] == weightSetting.default) { + weight.visible = false; + } + } + weights.push(weight); + } + + const extraButton = {} + mainBindData.extraButton = extraButton; + if (weights.some(v => !v.visible)) { + extraButton.visible = true; + } else { + this.openedExtraOption = true; + } + + const loraTypes = []; + mainBindData.loraTypes = loraTypes; + for (const entry of Object.entries(LORA_TYPE_PULLDOWN)) { + const loraType = {} + loraType.name = entry[0]; + loraType.value = entry[1]; + if (loraType.value === this.weightData.lbw_lora_type) { + loraType.selected = true; + } + loraTypes.push(loraType); + } + + const sdvers = []; + mainBindData.sdvers = sdvers; + for (const sdVersion of LBW_WEIGHT_SD_VERSIONS) { + const sdver = {} + sdver.name = sdVersion; + sdver.value = sdVersion; + sdver.checked = sdVersion == this.weightData.lbw_sd_version; + sdvers.push(sdver); + } + + const xyz = {}; + mainBindData.xyz = xyz; + + const specialPresets = this.getLbwSpecialPreset(this.weightData.lbw_lora_type, this.weightData.lbw_sd_version); + xyz.checked = this.weightData.special === "XYZ"; + xyz.label = specialPresets.XYZ; + } + + setupLbwBindData() { + const lbwBindDatas = []; + this.bindData.lbwBindDatas = lbwBindDatas; + + const group = "lbw" + const lbwSetting = WEIGHT_SETTINGS[group]; + const lbwData = this.weightData[group]; + for (let idx = 0; idx < lbwData.length; idx++) { + const lbwWeight = {} + this.initWeightForm(lbwWeight, lbwSetting, lbwData, idx); + lbwWeight.group = group; + lbwWeight.label = lbwSetting.labels[idx]; + lbwBindDatas.push(lbwWeight); + } + } + + buildDOM() { + const parser = new DOMParser(); + const mainHtml = WeightHelper.MAIN_TEMPLATE(this.bindData.mainBindData); + const mainDoc = parser.parseFromString(mainHtml, 'text/html'); + this.mainBody = mainDoc.body.firstChild; + + const lbwsHtml = WeightHelper.LBWS_TEMPLATE({ lbws: this.bindData.lbwBindDatas }); + const lbwsDoc = parser.parseFromString(lbwsHtml, 'text/html'); + this.lbwDOMs = [...lbwsDoc.body.children]; + + [...mainDoc.getElementsByClassName("wh:weight")].forEach(elem => { + const data = {} + const group = elem.dataset.group; + const check = elem.getElementsByClassName("wh:check"); + data.check = check.length > 0 ? check[0] : undefined; + data.slider = elem.getElementsByClassName("wh:slider")[0]; + data.updown = elem.getElementsByClassName("wh:updown")[0]; + this.weightElements[group] = [data]; + }); + + this.weightElements["lbw"] = []; + [...lbwsDoc.getElementsByClassName("wh:lbw")].forEach(elem => { + const data = {} + data.check = undefined; + data.slider = elem.getElementsByClassName("wh:slider")[0]; + data.updown = elem.getElementsByClassName("wh:updown")[0]; + this.weightElements["lbw"].push(data); + }); + + this.attachEvent(this.mainBody, "click", (e) => e.stopPropagation()); + + this.attachEvent(mainDoc.getElementById("wh:header"), "mousedown", (e) => { + this.isDragging = true; + this.offsetX = e.clientX - this.mainBody.getBoundingClientRect().left; + this.offsetY = e.clientY - this.mainBody.getBoundingClientRect().top; + }); + this.attachEvent(document.body, "mousemove", (e) => { + if (!this.isDragging) return; + + const x = e.clientX - this.offsetX + window.scrollX; + const y = e.clientY - this.offsetY + window.scrollY; + + this.mainBody.style.left = x + 'px'; + this.mainBody.style.top = y + 'px'; + }); + this.attachEvent(document.body, "mouseup", () => { + this.isDragging = false; + }); + + this.attachEvent(mainDoc.getElementById("wh:lock"), "click", () => { + const weightDataHash = this.weightData.hashCode(); + const isLocked = this.currentLockSet.has(weightDataHash); + + this.updateLockedIcon(!isLocked); + if (isLocked) { + this.currentLockSet.delete(weightDataHash); + } else { + const weightDataClone = this.weightData.clone(); + if (weightDataClone.stop[0] == WEIGHT_SETTINGS.stop.default) { + weightDataClone.stop[0] = null; + } + this.currentLockSet.add(weightDataHash, weightDataClone); + } + }); + + this.attachEvent(mainDoc.getElementById("wh:clear"), "click", () => { + const newHistory = this.currentLockSet.getAll(); + if (newHistory.length == 0 || !newHistory[newHistory.length - 1].equals(this.weightData)) { + newHistory.push(this.weightData); + } + + weight_helper_data[this.nameHash].history = newHistory; + this.currentHistory = newHistory; + + document.getElementById("wh:page__label").textContent = `${newHistory.length}/${newHistory.length}`; + this.historyIndex = newHistory.length - 1; + }); + + this.attachEvent(mainDoc.getElementById("wh:page__prev"), "click", () => { + if (this.historyIndex <= 0) { + return; + } + this.historyIndex--; + this.applyFromHistory(); + }); + this.attachEvent(mainDoc.getElementById("wh:page__next"), "click", () => { + if (this.historyIndex >= this.currentHistory.length - 1) { + return; + } + this.historyIndex++; + this.applyFromHistory(); + }); + + this.attachEvent(mainDoc.getElementById("wh:reload"), "click", () => { + this.loadMetadata(true); + }); + + const changedEvent = (group, useCheck) => { + if (useCheck && !useCheck.checked) { + useCheck.checked = true; + this.weightData[`use_${group}`] = true; + } + + const weightDataHash = this.weightData.hashCode(); + const isLocked = this.currentLockSet.has(weightDataHash); + this.updateLockedIcon(isLocked); + + if (!this.usingExecCommand) { + const updatedText = this.makeUpdatedText(); + this.update(updatedText); + } + } + Object.entries(this.weightElements).forEach(entry => { + const group = entry[0]; + if (group === "lbw") { + return; + } + const weights = entry[1]; + weights.forEach((weight, i) => { + if (weight.check) { + this.attachEvent(weight.check, "change", (e) => { + this.weightData[`use_${group}`] = e.target.checked; + + const weightDataHash = this.weightData.hashCode(); + const isLocked = this.currentLockSet.has(weightDataHash); + this.updateLockedIcon(isLocked); + + if (!this.usingExecCommand) { + const updatedText = this.makeUpdatedText(); + this.update(updatedText); + } + }); + } + this.attachEvent(weight.slider, "input", (e) => { + const fVal = parseFloat(e.target.value); + this.weightData[group][i] = fVal; + weight.updown.value = Math.round(fVal) / 100; + changedEvent(group, weight.check); + }); + this.attachEvent(weight.updown, "input", (e) => { + const fVal = Math.round(e.target.value * 100); + this.weightData[group][i] = fVal; + if (fVal < weight.slider.min) { + weight.slider.min = fVal; + } + if (fVal > weight.slider.max) { + weight.slider.max = fVal; + } + weight.slider.value = Math.round(fVal); + changedEvent(group, weight.check); + }); + }) + }); + + this.attachEvent(mainDoc.getElementById("wh:extra_button"), "click", (e) => { + e.target.remove(); + [...this.mainBody.getElementsByClassName("wh:weight")].forEach(f => { + f.style.display = ''; + }) + this.openedExtraOption = true; + }); + + this.attachEvent(mainDoc.getElementById("wh:lora_type"), "change", (e) => { + this.weightData.lbw_lora_type = e.target.value; + + const xyzLabel = document.getElementById("wh:xyz_label"); + const specialPreset = this.getLbwSpecialPreset(this.weightData.lbw_lora_type, this.weightData.lbw_sd_version); + xyzLabel.textContent = specialPreset.XYZ; + + this.redrawLbw(); + if (!this.usingExecCommand) { + const updatedText = this.makeUpdatedText(); + this.update(updatedText); + } + const masks = this.getLbwWeightSetting(e.target.value, this.weightData.lbw_sd_version).masks; + this.weightData.masks = masks; + }); + + this.attachEvent(mainDoc.getElementsByClassName("wh:sdver"), "change", (e) => { + this.weightData.lbw_sd_version = e.target.value; + + const xyzLabel = document.getElementById("wh:xyz_label"); + const specialPreset = this.getLbwSpecialPreset(this.weightData.lbw_lora_type, this.weightData.lbw_sd_version); + xyzLabel.textContent = specialPreset.XYZ; + + this.redrawLbw(); + if (!this.usingExecCommand) { + const updatedText = this.makeUpdatedText(); + this.update(updatedText); + } + const masks = this.getLbwWeightSetting(this.weightData.lbw_lora_type, e.target.value).masks; + this.weightData.masks = masks; + if (!this.weightData.special) { + const lockIcon = document.getElementById("wh:lock"); + lockIcon.style.visibility = ""; + const isLocked = this.currentLockSet.has(this.weightData.hashCode()); + this.updateLockedIcon(isLocked); + } + }); + + this.attachEvent(mainDoc.getElementsByClassName("wh:preset_select"), "change", (e) => { + const selectVal = e.target.value; + if (!this.weightData.lbw_sd_version) { + return; + } + + if (selectVal !== "") { + this.weightData.special = ""; + const xyz = document.getElementById("wh:xyz"); + xyz.checked = false; + + const values = selectVal.split(",").map(v => Math.round(v * 100)); + const masks = this.getLbwWeightSetting(this.weightData.lbw_lora_type, this.weightData.lbw_sd_version).masks; + let refIdx = 0; + for (let idx = 0; idx < masks.length; idx++) { + let val = 0; + if (masks[idx] === 1) { + val = values[refIdx]; + refIdx++; + } + this.weightData.lbw[idx] = val; + this.weightElements.lbw[idx].slider.value = val; + this.weightElements.lbw[idx].updown.value = val / 100; + } + + const lockIcon = document.getElementById("wh:lock"); + lockIcon.style.visibility = ""; + + const weightDataHash = this.weightData.hashCode(); + const isLocked = this.currentLockSet.has(weightDataHash); + this.updateLockedIcon(isLocked); + + if (!this.usingExecCommand) { + const updatedText = this.makeUpdatedText(); + this.update(updatedText); + } + } + }); + + this.attachEvent(mainDoc.getElementById("wh:xyz"), "change", (e) => { + const lockIcon = document.getElementById("wh:lock"); + if (e.target.checked) { + this.weightData.special = e.target.value; + lockIcon.style.visibility = "hidden"; + } else { + this.weightData.special = ""; + lockIcon.style.visibility = this.weightData.lbw_sd_version ? "" : "hidden"; + + const weightDataHash = this.weightData.hashCode(); + const isLocked = this.currentLockSet.has(weightDataHash); + this.updateLockedIcon(isLocked); + } + + if (!this.usingExecCommand) { + const updatedText = this.makeUpdatedText(); + this.update(updatedText); + } + }); + + const lbwChangedEvent = () => { + this.weightData.special = ""; + const xyz = document.getElementById("wh:xyz"); + xyz.checked = false; + + const lbwValues = this.getLbwWeightData().join(","); + const select = this.mainBody.getElementsByClassName("wh:preset_select")[0]; + if (lbwValues in this.lbwPresetsValueKeyMap[this.weightData.lbw_lora_type][this.weightData.lbw_sd_version]) { + select.value = lbwValues; + } else { + select.selectedIndex = 0; + } + + const lockIcon = document.getElementById("wh:lock"); + lockIcon.style.visibility = ""; + + const weightDataHash = this.weightData.hashCode(); + const isLocked = this.currentLockSet.has(weightDataHash); + this.updateLockedIcon(isLocked); + + if (!this.usingExecCommand) { + const updatedText = this.makeUpdatedText(); + this.update(updatedText); + } + } + this.weightElements.lbw.forEach(lbw => { + this.attachEvent(lbw.slider, "input", (e) => { + const fVal = parseFloat(e.target.value); + const idx = e.target.dataset.index; + this.weightData["lbw"][idx] = fVal; + lbw.updown.value = Math.round(fVal) / 100; + lbwChangedEvent(); + }); + this.attachEvent(lbw.updown, "input", (e) => { + const fVal = Math.round(e.target.value * 100); + const idx = e.target.dataset.index; + this.weightData["lbw"][idx] = fVal; + if (fVal < lbw.slider.min) { + lbw.slider.min = fVal; + } + if (fVal > lbw.slider.max) { + lbw.slider.max = fVal; + } + lbw.slider.value = fVal; + lbwChangedEvent(); + }); + }); + } + + async loadMetadata(force = false) { + const domMetadata = document.getElementById("wh:metadata"); + const typeVal = document.getElementById("wh:metadata__alg"); + const sdVerVal = document.getElementById("wh:metadata__sdver"); + if (force) { + domMetadata.classList.remove("error"); + } + if (force || Object.keys(this.metadata).length == 0) { + const startLoading = (elem) => { + if (elem.dataset.interval_id == null) { + elem.classList.add("loading"); + const frames = ["-", "--", "---", "----", "-----", "------", "-------"]; + let currentFrame = 0; + elem.dataset.interval_id = setInterval(() => { + elem.textContent = frames[currentFrame]; + currentFrame = (currentFrame + 1) % frames.length; + }, 100); + } + } + const stopLoading = (elem) => { + if (elem.dataset.interval_id != null) { + elem.classList.remove("loading"); + clearInterval(elem.dataset.interval_id); + } + } + startLoading(typeVal); + startLoading(sdVerVal); + const key = encodeURIComponent(this.name); + this.metadata = await postAPI(`/whapi/v1/get_metadata?key=${key}`, null) ?? {}; + stopLoading(typeVal); + stopLoading(sdVerVal); + } + if (Object.keys(this.metadata).length > 0) { + if (this.metadata.sd_version && !this.weightData.lbw_sd_version) { + this.weightData.lbw_sd_version = this.metadata.sd_version === "SDXL" ? "SDXL" : "SD"; + if (this.currentHistory.length == 1) { + this.currentHistory[0].lbw_sd_version = this.weightData.lbw_sd_version; + } + let selectedRadio = undefined; + [...this.mainBody.getElementsByClassName("wh:sdver")].forEach((radio) => { + if (radio.value === this.weightData.lbw_sd_version) { + radio.checked = true; + selectedRadio = radio; + } + }); + if (selectedRadio) { + selectedRadio.dispatchEvent(new Event('change', { bubbles: true })); + } + } + typeVal.textContent = this.metadata.algorithm ?? "Unknown"; + let sdVersion = this.metadata.sd_version ?? "Unknown"; + if (this.metadata.base_model) { + sdVersion += `(${this.metadata.base_model})`; + } + sdVerVal.textContent = sdVersion; + } else { + metadata.classList.add("error"); + typeVal.textContent = "TIMEOUT"; + sdVerVal.textContent = "TIMEOUT"; + } + } + + async loadPreviewBindData() { + if (Object.keys(this.previewInfo).length == 0) { + const key = encodeURIComponent(this.name); + this.previewInfo = await postAPI(`/whapi/v1/get_preview_info?key=${key}`, null); + } + + const preview = {} + this.bindData.previewBindData = preview; + if (Object.keys(this.previewInfo).length > 0) { + preview.hasResponse = true; + preview.previewUrl = this.previewInfo.preview_url; + preview.hasMetadata = this.previewInfo.has_metadata; + preview.modelName = this.previewInfo.model_name; + if (typeof extraNetworksRequestMetadata === "function") { + preview.definedExtraNetworksRequestMetadata = true; + } + if (typeof extraNetworksEditUserMetadata === "function") { + preview.definedExtraNetworksEditUserMetadata = true; + } + preview.modelId = this.previewInfo.model_id; + preview.description = this.previewInfo.description; + + preview.trgWords = this.previewInfo.trigger_words ?? []; + preview.negTrgWords = this.previewInfo.negative_trigger_words ?? []; + if (preview.trgWords.length > 0 || preview.negTrgWords.length > 0) { + preview.hasTriggerWords = true; + } + preview.height = opts.weight_helper_preview_height; + switch (opts.weight_helper_preview_position) { + case "Bottom Right": + preview.pos = [null, null, 0, this.mainBody.clientWidth + 6]; + break; + case "Top Left": + preview.pos = [0, this.mainBody.clientWidth + 6, null, null]; + break; + case "Bottom Left": + preview.pos = [null, this.mainBody.clientWidth + 6, 0, null]; + break; + default: + preview.pos = [0, null, null, this.mainBody.clientWidth + 6]; + break; + } + } + } + + buildPreviewDOM() { + const parser = new DOMParser(); + const previewHtml = WeightHelper.PREVIEW_TEMPLATE({ preview: this.bindData.previewBindData }); + const previewDoc = parser.parseFromString(previewHtml, 'text/html'); + this.previewBody = previewDoc.body.firstChild; + + const preview = this.bindData.previewBindData; + this.attachEvent(previewDoc.getElementById("wh:preview__metadata"), "click", (e) => { + extraNetworksRequestMetadata(e, 'lora', preview.modelName); + }); + this.attachEvent(previewDoc.getElementById("wh:preview__edit"), "click", (e) => { + extraNetworksEditUserMetadata(e, this.tabId, 'lora', preview.modelName); + }); + this.attachEvent(previewDoc.getElementById("wh:preview__civitai"), "click", () => { + window.open(`https://civitai.com/models/${preview.modelId}`, '_blank'); + }); + this.attachEvent(previewDoc.getElementById("wh:preview__add-trigger"), "click", (e) => { + let promptTextarea = document.querySelector(`#${this.tabId}_prompt textarea`); + let negativeTextarea = document.querySelector(`#${this.tabId}_neg_prompt textarea`); + if (this.textarea === negativeTextarea) { + } else if (this.textarea !== promptTextarea) { + promptTextarea = this.textarea; + negativeTextarea = null; + } + const negTrgWords = preview.negTrgWords; + const trgWords = preview.trgWords; + if (!this.usingExecCommand) { + const insert = (wordArray, textarea) => { + if (wordArray.length > 0 && textarea) { + let words = wordArray.join(", "); + if (textarea.value) words = ", " + words; + textarea.value += words; + } + } + insert(negTrgWords, negativeTextarea); + insert(trgWords, promptTextarea); + } else { + const insert = (wordArray, textarea) => { + if (wordArray.length > 0 && textarea) { + let words = wordArray.join(", "); + if (textarea.value) words = ", " + words; + textarea.focus(); + const eolIndex = textarea.value.length; + textarea.setSelectionRange(eolIndex, eolIndex); + document.execCommand("insertText", false, words); + } + } + withoutTAC(() => { + insert(negTrgWords, negativeTextarea); + insert(trgWords, promptTextarea); + }); + } + e.target.remove(); + }); + + const topRow = previewDoc.getElementById("wh:preview__top-row"); + const bottomRow = previewDoc.getElementById("wh:preview__bottom-row"); + const desc = previewDoc.getElementById("wh:preview__desc"); + const open = previewDoc.getElementById("wh:preview__note-open"); + const close = previewDoc.getElementById("wh:preview__note-close"); + this.attachEvent(open, "click", () => { + topRow.style.visibility = "hidden"; + bottomRow.style.visibility = "hidden"; + desc.style.visibility = "visible"; + close.style.visibility = "visible"; + }); + this.attachEvent(close, "click", () => { + topRow.style.visibility = ""; + bottomRow.style.visibility = ""; + desc.style.visibility = ""; + close.style.visibility = ""; + }); + } + + redrawLbw() { + const parser = new DOMParser(); + + const presetBindDatas = []; + this.bindData.lbwPresetBindDatas = presetBindDatas; + + const lbwSdVersion = this.weightData.lbw_sd_version; + if (lbwSdVersion) { + const lbwLoraType = this.weightData.lbw_lora_type; + if (Object.keys(this.getLbwPresets(lbwLoraType, lbwSdVersion)).length) { + const lbwWeightData = this.getLbwWeightData(); + const strLbwWeightData = Array.isArray(lbwWeightData) ? lbwWeightData.join(",") : lbwWeightData; + const lbwPresets = this.getLbwPresets(lbwLoraType, lbwSdVersion); + for (const key of Object.keys(lbwPresets)) { + const preset = {} + preset.name = key; + preset.value = lbwPresets[key]; + if (preset.value === strLbwWeightData) { + preset.selected = true; + } + presetBindDatas.push(preset); + } + } + } + + const presetSelect = this.mainBody.getElementsByClassName("wh:preset_select")[0]; + const presetHtml = WeightHelper.PRESETS_TEMPLATE({ presets: this.bindData.lbwPresetBindDatas }); + const previewDoc = parser.parseFromString(presetHtml, 'text/html'); + while (presetSelect.firstChild) { + presetSelect.firstChild.remove(); + } + const presetFragment = document.createDocumentFragment(); + [...previewDoc.body.children].forEach(element => { + presetFragment.appendChild(element); + }); + presetSelect.appendChild(presetFragment); + + const lbwBlockBindDatas = []; + this.bindData.lbwBlockBindDatas = lbwBlockBindDatas; + + const lbwWeightSetting = this.getLbwWeightSetting(this.weightData.lbw_lora_type, this.weightData.lbw_sd_version); + for (const blockPoint of lbwWeightSetting.block_points) { + lbwBlockBindDatas.push({label: blockPoint}); + } + + const lbwblocks = this.mainBody.getElementsByClassName("wh:lbwblocks")[0]; + const lbwblocksHtml = WeightHelper.LBWBLOCKS_TEMPLATE({ lbwBlocks: this.bindData.lbwBlockBindDatas }); + const lbwblocksDoc = parser.parseFromString(lbwblocksHtml, 'text/html'); + while (lbwblocks.firstChild) { + lbwblocks.firstChild.remove(); + } + const lbwblockFragment = document.createDocumentFragment(); + [...lbwblocksDoc.body.children].forEach(element => { + lbwblockFragment.appendChild(element); + }); + + const labelMap = {}; + for (let idx = 0; idx < WEIGHT_SETTINGS.lbw.labels.length; idx++) { + labelMap[WEIGHT_SETTINGS.lbw.labels[idx]] = idx; + } + for (const blockPoint of lbwWeightSetting.block_points) { + const lbwblock = lbwblockFragment.getElementById(`wh:lbwblock_${blockPoint.toLowerCase()}`); + const points = blockPoint.split("-"); + let pointStart = labelMap[points[0]]; + let pointEnd = pointStart; + if (points.length > 1) { + pointEnd = labelMap[points[1]]; + } + for (let idx = pointStart; idx <= pointEnd; idx++) { + if (lbwWeightSetting.masks[idx] == 1) { + lbwblock.appendChild(this.lbwDOMs[idx]); + } + } + } + lbwblocks.appendChild(lbwblockFragment); + } + + applyFromHistory() { + document.getElementById("wh:page__label").textContent = `${this.historyIndex + 1}/${this.currentHistory.length}`; + const oldSdVersion = this.weightData.lbw_sd_version; + this.weightData = this.currentHistory[this.historyIndex].clone(); + + const lockIcon = document.getElementById("wh:lock"); + if (this.weightData.isSpecial() || !this.weightData.lbw_sd_version) { + lockIcon.style.visibility = "hidden"; + } else { + lockIcon.style.visibility = ""; + } + + Object.entries(this.weightData).map(entry => { + const group = entry[0]; + const vals = entry[1]; + if (group === "stop" && vals[0] == null) { + vals[0] = WEIGHT_SETTINGS[group].default; + } + if (Array.isArray(vals) && group in this.weightElements) { + for (const idx in vals) { + let fVal = vals[idx]; + let isExtraType = false; + let show = false; + if (["unet", "dyn", "start", "stop"].includes(group)) { + isExtraType = true; + if (this.weightElements[group][0].check) { + const useCheck = this.weightData[`use_${group}`]; + this.weightElements[group][0].check.checked = useCheck; + show = useCheck; + } + if (["start", "stop"].includes(group)) { + if (fVal != null && fVal != WEIGHT_SETTINGS[group].default) { + show = true; + } + } + } + if (!this.openedExtraOption && isExtraType) { + const parent = document.getElementById(`wh:weight_${group}`); + parent.style.display = show ? "flex" : "none"; + } + if (fVal == null) { + fVal = 0; + } + this.weightElements[group][idx].slider.value = fVal; + this.weightElements[group][idx].updown.value = fVal / 100; + } + } + }); + + if (oldSdVersion !== this.weightData.lbw_sd_version) { + const loraTypeSelect = document.getElementById("wh:lora_type"); + loraTypeSelect.value = this.weightData.lbw_lora_type; + [...this.mainBody.getElementsByClassName("wh:sdver")].forEach((radio) => { + radio.checked = radio.value === this.weightData.lbw_sd_version; + }); + this.redrawLbw(); + } else { + const presetSelect = this.mainBody.getElementsByClassName("wh:preset_select")[0]; + if (this.weightData.special) { + presetSelect.value = this.weightData.special; + } else { + const lbwValues = this.getLbwWeightData().join(","); + const presetValues = this.lbwPresetsValueKeyMap[this.weightData.lbw_lora_type][this.weightData.lbw_sd_version]; + try { + if (lbwValues in presetValues) { + presetSelect.value = lbwValues; + } else { + presetSelect.selectedIndex = 0; + } + } catch (error) { + console.error(error, presetValues, lbwValues); + } + } + } + const lbwBlocks = this.mainBody.getElementsByClassName("wh:lbwblocks")[0]; + lbwBlocks.style.display = this.weightData.lbw_sd_version ? "flex" : "none"; + + if (!this.usingExecCommand) { + const updatedText = this.makeUpdatedText(); + this.update(updatedText); + } + + const weightDataHash = this.weightData.hashCode(); + const isLocked = this.currentLockSet.has(weightDataHash); + this.updateLockedIcon(isLocked); + } + + getLbwWeightData() { + if (this.weightData.isSpecial()) { + return this.weightData.special; + } + const masks = this.getLbwWeightSetting(this.weightData.lbw_lora_type, this.weightData.lbw_sd_version).masks; + return this.weightData.lbw.filter((_, i) => masks[i] === 1).map(v => v / 100); + } + + getLbwPresets(lbwLoraType, lbwSdVersion) { + if (lbwSdVersion) { + if (lbwLoraType in this.lbwPresetsMap && lbwSdVersion in this.lbwPresetsMap[lbwLoraType]) { + return this.lbwPresetsMap[lbwLoraType][lbwSdVersion]; + } + } + return {}; + } + + getLbwWeightSetting(lbwLoraType, lbwSdVersion) { + if (lbwSdVersion) { + if (lbwLoraType in LBW_WEIGHT_SETTINGS && lbwSdVersion in LBW_WEIGHT_SETTINGS[lbwLoraType]) { + return LBW_WEIGHT_SETTINGS[lbwLoraType][lbwSdVersion]; + } + } + return LBW_WEIGHT_SETTINGS.unknown; + } + + getLbwSpecialPreset(loraType, sdVersion) { + if (sdVersion) { + if (loraType in SPECIAL_PRESETS && sdVersion in SPECIAL_PRESETS[loraType]) { + return SPECIAL_PRESETS[loraType][sdVersion]; + } + } + return SPECIAL_PRESETS.unknown; + } + + updateLockedIcon(isLocked) { + const flag = isLocked ? "like" : "unlike"; + const lockIcon = document.getElementById("wh:lock"); + lockIcon.className = `lock ${flag}`; + } + + makeUpdatedText() { + let updatedText = `<${this.tagName}:${this.name}`; + const optionalTypes = ["te", "unet", "dyn"]; + let refIdx = 0; + for (let idx = 0; idx < optionalTypes.length; idx++) { + const keyType = optionalTypes[idx]; + if (keyType in this.weightData) { + const defVal = WEIGHT_SETTINGS[keyType].default; + const val = this.weightData[keyType]; + let output = false; + if (keyType === "te") { + output = true; + } else if (this.weightElements[keyType][0].check) { + if (this.weightElements[keyType][0].check.checked) { + output = true; + } + } else if (val != defVal) { + output = true; + } + if (output) { + let rateValue = val / 100; + if (idx === refIdx) { + updatedText += `:${rateValue}`; + } else { + updatedText += `:${keyType}=${rateValue}`; + } + refIdx++; + } + } + } + const startDefVal = WEIGHT_SETTINGS.start.default; + const startVal = this.weightData.start; + const stopDefVal = WEIGHT_SETTINGS.stop.default; + const stopVal = this.weightData.stop; + if (startVal != startDefVal && stopVal != stopDefVal) { + updatedText += `:step=${startVal / 100}-${stopVal / 100}`; + } else if (startVal != startDefVal) { + updatedText += `:start=${startVal / 100}`; + } else if (stopVal != stopDefVal) { + updatedText += `:stop=${stopVal / 100}`; + } + + let lbwWeights = []; + const masks = this.getLbwWeightSetting(this.weightData.lbw_lora_type, this.weightData.lbw_sd_version).masks; + for (let idx = 0; idx < masks.length; idx++) { + if (masks[idx]) { + lbwWeights.push(this.weightData.lbw[idx]); + } + } + if (!this.weightData.special) { + if (!lbwWeights.every(val => val === WEIGHT_SETTINGS.lbw.default)) { + let rateValues = lbwWeights.map(v => v / 100).join(","); + const lbwValues = this.getLbwWeightData().join(","); + + let loraType = this.weightData.lbw_lora_type; + let sdVersion = this.weightData.lbw_sd_version; + if (loraType && sdVersion) { + if (lbwValues in this.lbwPresetsValueKeyMap[loraType][sdVersion]) { + rateValues = this.lbwPresetsValueKeyMap[loraType][sdVersion][lbwValues]; + } + } + updatedText += `:lbw=${rateValues}`; + } + } else { + updatedText += `:lbw=${this.weightData.special}`; + } + if (this.weightData.lbwe.length > 0) { + updatedText += `:lbwe=${this.weightData.lbwe[0]}`; + } + updatedText += ">"; + return updatedText; + } + + update(updatedText) { + this.textarea.value = this.textarea.value.substring(0, this.lastSelectionStart) + updatedText + this.textarea.value.substring(this.lastSelectionEnd); + this.lastSelectionEnd = this.lastSelectionStart + updatedText.length; + } + + updateWithExecCommand(updatedText) { + withoutTAC(() => { + this.textarea.focus(); + this.textarea.setSelectionRange(this.lastSelectionStart, this.lastSelectionEnd); + document.execCommand("insertText", false, updatedText); + }); + } + + save() { + const loraInfo = weight_helper_data[this.nameHash].lora_info; + loraInfo.metadata = this.metadata; + loraInfo.selected_lora_type.lbw_lora_type = this.weightData.lbw_lora_type; + loraInfo.selected_lora_type.lbw_sd_version = this.weightData.lbw_sd_version; + + const lbwDefault = WEIGHT_SETTINGS.lbw.default; + const masks = this.getLbwWeightSetting(this.weightData.lbw_lora_type, this.weightData.lbw_sd_version).masks; + for (let idx = 0; idx < masks.length; idx++) { + if (masks[idx] !== 1) { + this.weightData.lbw[idx] = lbwDefault; + } + } + + if (this.weightData.lbw_sd_version) { + const historyLen = this.currentHistory.length; + let lastWeightData = this.currentHistory.at(-1); + let historyChanged = false; + if (this.historyIndex == historyLen - 1) { + historyChanged = !this.weightData.equals(lastWeightData); + if (historyChanged) { + this.currentHistory.push(this.weightData); + } + } else { + this.currentHistory.splice(this.historyIndex, 1); + this.currentHistory.push(this.weightData); + } + if (this.weightData.stop[0] == WEIGHT_SETTINGS.stop.default) { + this.weightData.stop[0] = null; + } + weight_helper_data[this.nameHash].lock = this.currentLockSet.getAll(); + } + weight_helper_data.VERSION = VERSION; + localStorage.setItem("weight_helper_data", JSON.stringify(weight_helper_data)); + } + + attachEvent(doms, eventName, func) { + if (doms == null) return; + if (doms instanceof HTMLCollection) { + if (doms.length === 0) return; + } else { + doms = [doms]; + } + for (const dom of doms) { + dom.addEventListener(eventName, func); + this.releaseFunctions.push(() => dom.removeEventListener(eventName, func)); + } + } + + show(top, left) { + this.mainBody.style.top = top + 'px'; + this.mainBody.style.left = left + 'px'; + document.body.appendChild(this.mainBody); + + const diffBottom = window.innerHeight - this.mainBody.getBoundingClientRect().bottom; + if (diffBottom < 0) { + this.mainBody.style.top = (top + diffBottom) + 'px'; + const diffTop = this.mainBody.getBoundingClientRect().top; + if (diffTop < 0) { + this.mainBody.style.top = window.scrollY + 'px'; + } + } + this.attachEvent(document.body, "click", this.close); + this.attachEvent(document.body, "keyup", this.cancel); + last_instance = this; + + this.loadMetadata(); + + if (opts.weight_helper_show_preview) { + this.loadPreviewBindData().then(() => { + this.buildPreviewDOM(); + this.mainBody.prepend(this.previewBody); + }); + } + } + + close = (e) => { + if (!this.mainBody) return; + if (e) { + if (this.mainBody.contains(e.target)) return; + if (e.target.id === `${this.tabId}_token_button`) return; + if (e.target.id === `${this.tabId}_lora_edit_user_metadata_button`) return; + if (e.target.className === "global-popup-close") return; + if (e.target.id.indexOf("_interrupt") > 0) { + this.finally(); + return; + } + } + + const updatedText = this.makeUpdatedText(); + const changed = this.lastText != updatedText; + if (changed) { + if (!this.usingExecCommand) { + this.textarea.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true })); + } else { + this.updateWithExecCommand(updatedText); + } + } + this.save(); + this.finally(); + }; + + cancel = (e) => { + if (e.key === 'Escape') { + if (!this.usingExecCommand) { + this.update(this.lastText); + } + this.finally(); + } + }; + + finally() { + weight_helper_preview_info[this.nameHash] = this.previewInfo; + + last_instance = undefined; + this.releaseFunctions.forEach((f) => f()); + this.mainBody.remove(); + } + } + + async function postAPI(url, body) { + let response = await fetch(url, { method: "POST", body: body }); + if (response.status != 200) { + console.error(`Error posting to API endpoint "${url}": ` + response.status, response.statusText); + return null; + } + return await response.json(); + } + + function withoutTAC(func) { + let tacActiveInOrg = undefined; + const tacEnabled = typeof TAC_CFG !== 'undefined' && TAC_CFG; + try { + if (tacEnabled) { + tacActiveInOrg = TAC_CFG.activeIn.global + TAC_CFG.activeIn.global = false; + } + func(); + } finally { + if (tacEnabled) { + TAC_CFG.activeIn.global = tacActiveInOrg; + } + } + } + + function strHashCode(s) { + let hash = 0; + if (!s) return hash; + for (let i = 0; i < s.length; i++) { + const char = s.charCodeAt(i); + hash = hash ^ char; + hash = (hash << 5) - hash; + } + return hash & 0xffffffff; + } + + async function onPageLoaded() { + let tab = null; + while (!tab) { + tab = gradioApp().getElementById("tab_txt2img"); + if (!tab) { + await new Promise((resolve) => setTimeout(resolve, 200)); + } + } + return tab; + } + + document.addEventListener('DOMContentLoaded', function() { + const dataTemp = JSON.parse(localStorage.getItem("weight_helper_data")) ?? {}; + + const oldData = JSON.parse(localStorage.getItem("weight_helper")); + const oldDataType = JSON.parse(localStorage.getItem("weight_helper_type")); + try { + if (oldData && Object.keys(oldData).length > 0) { + Object.entries(oldData).forEach(kv => { + const key = kv[0]; + const datas = kv[1]; + + delete dataTemp[key] + const sdType = oldDataType[key]; + + let lbwSdVersion = sdType == "sdxl" ? "SDXL" : "SD"; + datas.forEach(data => { + delete data.VERSION; + delete data.DATE; + if (data.unet[0] != null) { + data.use_unet = true; + } else { + data.use_unet = false; + data.unet[0] = 0; + } + if (data.dyn[0] != null) { + data.use_dyn = true; + } else { + data.use_dyn = false; + data.dyn[0] = 0; + } + data.stop = [null]; + data.special = ""; + data.lbw_lora_type = "lora"; + data.lbw_sd_version = lbwSdVersion; + }); + dataTemp[key] = { lock: [] } + dataTemp[key].history = datas; + dataTemp[key].lora_info = { + "selected_lora_type": { + "lbw_lora_type": "lora", + "lbw_sd_version": lbwSdVersion + } + } + }); + } + localStorage.removeItem("weight_helper"); + localStorage.removeItem("weight_helper_type"); + } catch (error) { + console.error("An error occurred:", error); + } + + weight_helper_data = dataTemp; + Object.entries(dataTemp).forEach(kv => { + const key = kv[0]; + if (key === "VERSION") { + return; + } + const val = kv[1]; + if (val.lock) { + weight_helper_data[key].lock = val.lock.map(v => new WeightData(v)); + } + if (val.history) { + weight_helper_data[key].history = val.history.map(v => new WeightData(v)); + } + }); + + onPageLoaded().then(() => { + let textColor = getComputedStyle(document.documentElement).getPropertyValue('--body-text-color').trim(); + let textColorRgb = textColor.slice(1).match(/.{1,2}/g).map(hex => parseInt(hex, 16)); + let textColorRgba = [...textColorRgb, 0.3]; + document.documentElement.style.setProperty('--weight-helper-shadow', `rgba(${textColorRgba.join(",")})`); + + if (!gradioApp().getElementById("lbw_ratiospreset")) { + return; + } + + const genButtons = gradioApp().querySelectorAll("button:is([id*='_generate'])"); + genButtons.forEach((genBtn) => { + genBtn.addEventListener('click', () => { + if (last_instance) { + last_instance.close(); + } + }, true); + }); + const textareas = gradioApp().querySelectorAll("*:is([id*='_toprow'] [id*='_prompt'], .prompt) textarea"); + textareas.forEach((textarea) => { + WeightHelper.attach(textarea); + }); + }); + }); +})(); diff --git a/extensions/CHECK/sd-webui-weight-helper/scripts/__pycache__/weight_helper.cpython-310.pyc b/extensions/CHECK/sd-webui-weight-helper/scripts/__pycache__/weight_helper.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdd902232b7764abbc7be9e02f0742012a593160 Binary files /dev/null and b/extensions/CHECK/sd-webui-weight-helper/scripts/__pycache__/weight_helper.cpython-310.pyc differ diff --git a/extensions/CHECK/sd-webui-weight-helper/scripts/weight_helper.py b/extensions/CHECK/sd-webui-weight-helper/scripts/weight_helper.py new file mode 100644 index 0000000000000000000000000000000000000000..ba1268316aabe58fb9a4d67730747567a1cd83dd --- /dev/null +++ b/extensions/CHECK/sd-webui-weight-helper/scripts/weight_helper.py @@ -0,0 +1,363 @@ +import ast +import enum +import os +import sys +from typing import Any, Optional +import gradio as gr +import importlib +import json +import urllib.parse +from fastapi import FastAPI +from modules import script_callbacks, shared + +prefix = "/whapi/v1" + +allowed_preview_extensions = ["png", "jpg", "jpeg", "webp", "gif"] + +class UI_TYPE(enum.Enum): + Unknown = 1 + A1111 = 2 + NEW_FORGE = 3 + +class WeightHelperAPI: + + instance = None + + network = None + network_lora = None + ui_type = None + + @staticmethod + def init_endpoints(_: gr.Blocks, app: FastAPI): + if not WeightHelperAPI.instance: + WeightHelperAPI.instance = WeightHelperAPI() + + instance = WeightHelperAPI.instance + @app.post(prefix + "/get_template") + async def _(): + return instance.get_template() + @app.post(prefix + "/get_metadata") + async def _(key: str): + instance.__initialize() + return instance.get_metadata(key) + @app.post(prefix + "/get_preview_info") + async def _(key: str): + instance.__initialize() + return instance.get_preview_info(key) + + def __new__(cls, *args, **kwargs): + if not cls.instance: + cls.instance = super(WeightHelperAPI, cls).__new__(cls, *args, **kwargs) + return cls.instance + + def __initialize(self): + if self.network_lora is not None or self.ui_type is not None: + return + + try: + self.network = importlib.import_module("extensions-builtin.Lora.network") + self.network_lora = importlib.import_module("extensions-builtin.Lora.ui_extra_networks_lora").networks + self.ui_type = UI_TYPE.A1111 + except: + try: + self.network = importlib.import_module("packages_3rdparty.webui_lora_collection.network") + # TODO Perhaps 'packages_3rdparty.webui_lora_collection.network_lora' is correct, but 'new forge' doesn't support it yet. + self.network_lora = importlib.import_module("extensions-builtin.sd_forge_lora.networks") + self.ui_type = UI_TYPE.NEW_FORGE + except Exception as e: + self.ui_type = UI_TYPE.Unknown + print(e) + + def __get_lora_on_disk(self, key): + if self.network_lora: + lora_on_disk = self.network_lora.available_network_aliases.get(key) + if not lora_on_disk: + # sdnext + lora_on_disk = next((v for v in self.network_lora.available_network_aliases.values() if v.alias == key), None) + return lora_on_disk + return None + + def get_template(self): + template_path = os.path.join(__file__, "../../html/template.hbs") + try: + with open(template_path, "r", encoding="utf-8", errors="replace") as f: + return f.read() + except OSError: + pass + + def get_metadata(self, key) -> dict[str, Optional[str]]: + ret: dict[str, Optional[str]] = { + "sd_version": None, + "algorithm": None, + "base_model": None + } + + lora_on_disk = self.__get_lora_on_disk(key) + if lora_on_disk: + ret["sd_version"] = self.__get_sd_version(lora_on_disk) + + metadata = lora_on_disk.metadata + ss_network_args = self.__get_network_args(lora_on_disk) + ret["algorithm"] = self.__get_algorithm(metadata, ss_network_args) + + model_path, _ = os.path.splitext(lora_on_disk.filename) + lora_json = self.__get_lora_json(model_path) + json_sd_version = lora_json.get("sd version", None) + if json_sd_version: + ret["sd_version"] = json_sd_version + + civitai_info = self.__find_civitai_info(model_path) + ret["base_model"] = civitai_info.get("baseModel", None) + + return ret + + def get_preview_info(self, key): + ret: dict[str, Optional[bool | str | list[str]]] = { + "model_id": None, + "trigger_words": None, + "negative_trigger_words": [], + "model_name": None, + "preview_url": None, + "has_metadata": False, + "description": None, + } + + lora_on_disk = self.__get_lora_on_disk(key) + if lora_on_disk: + model_path, _ = os.path.splitext(lora_on_disk.filename) + ret["model_name"] = lora_on_disk.name + ret["preview_url"] = self.__find_preview(model_path) + ret["has_metadata"] = self.__find_metadata(lora_on_disk) + ret["description"] = self.__find_description(model_path) + + trigger_words = [] + civitai_info = self.__find_civitai_info(model_path) + ret["model_id"] = civitai_info.get("modelId", None) + trigger_words = civitai_info.get("trainedWords", []) + trigger_words = ",".join(trigger_words).split(",") + ret["trigger_words"] = [w.strip() for w in trigger_words if w] + + lora_json = self.__get_lora_json(model_path) + + activation_text = lora_json.get("activation text", "") + if activation_text: + activation_text = activation_text.split(",") + activation_text = [w.strip() for w in activation_text if w] + ret["trigger_words"] = activation_text + + negative_text = lora_json.get("negative text", "") + if negative_text: + negative_text = negative_text.split(",") + negative_text = [w.strip() for w in negative_text if w] + ret["negative_trigger_words"] = negative_text + + json_description = lora_json.get("description", None) + if json_description: + ret["description"] = json_description + + return ret + + def __get_sd_version(self, lora) -> Optional[str]: + if self.ui_type == UI_TYPE.A1111: + sd_version: Optional[str] = lora.sd_version.name + if sd_version and self.network: + if sd_version == self.network.SdVersion.SD1.name or sd_version == self.network.SdVersion.SD2.name: + return "SD" + elif sd_version == self.network.SdVersion.SDXL.name: + return "SDXL" + return sd_version + elif self.ui_type == UI_TYPE.NEW_FORGE: + base_model_version: Optional[str] = lora.metadata.get("ss_base_model_version") + if base_model_version: + base_model_version = base_model_version.lower() + if "sd_" in base_model_version: + return "SD" + if "sd3" in base_model_version: + return "SD" + elif "sdxl" in base_model_version: + return "SDXL" + elif "flux" in base_model_version: + return "FLUX" + return None + return None + + def __get_algorithm(self, metadata, ss_network_args) -> Optional[str]: + ss_network_module = metadata.get("ss_network_module") + if not ss_network_module or ss_network_module == "Unknown": + return None + + conv_dim = float(ss_network_args.get("conv_dim", "-1")) + conv_alpha = float(ss_network_args.get("conv_alpha", "-1")) + algo = ss_network_args.get("algo", "").lower() + unit = ss_network_args.get("unit", "").lower() + dora_wd = ss_network_args.get("dora_wd", False) + + if ss_network_module.find("locon.locon_kohya") >= 0: + return "LoCon" + + elif ss_network_module.find("lycoris.kohya") >= 0: + algoName = "" + if dora_wd: + algoName = "(DoRA)" + elif algo == "lora": + algoName += "(LoCon)" + elif algo == "locon": + if unit: + algoName += "(DyLoRA)" + else: + algoName += "(LoCon)" + elif algo == "loha": + algoName += "(LoHa)" + elif algo == "lokr": + algoName += "(Lokr)" + elif algo == "ia3": + algoName += "(IA3)" + elif algo == "full": + algoName += "(Full)" + elif algo == "glora": + algoName += "(GLoRA)" + + return f"LyCORIS{algoName}" + + elif ss_network_module.find("networks.dylora") >= 0: + if algo == "dylora" and unit: + if conv_dim > 0 or conv_alpha > 0: + return "DyLoRA(C3Lier)" + return "DyLoRA(LierLa)" + + elif conv_dim > 0 or conv_alpha > 0: + return "LoRA(C3Lier)" + + else: + return "LoRA(LierLa)" + + def __get_network_args(self, lora) -> dict[str, Any]: + metadata = lora.metadata + ss_network_args = metadata.get("ss_network_args", {}) + if type(ss_network_args) is str: + try: + ss_network_args = ast.literal_eval(ss_network_args) + except Exception: + ss_network_args = {} + return ss_network_args + + def __get_lora_json(self, path) -> dict[str, Any]: + if path: + lora_json = f"{path}.json" + if os.path.exists(lora_json): + try: + with open(lora_json, "r", encoding="utf-8", errors="replace") as f: + return json.load(f) + except OSError: + pass + return {} + + def __link_preview(self, filename) -> str: + quoted_filename = urllib.parse.quote(filename.replace('\\', '/')) + #use browser cache + #mtime, _ = self.lister.mctime(filename) + #return f"./sd_extra_networks/thumb?filename={quoted_filename}&mtime={mtime}" + return f"./sd_extra_networks/thumb?filename={quoted_filename}" + + def __find_preview(self, path) -> str: + if path: + potential_files = sum([[f"{path}.{ext}", f"{path}.preview.{ext}"] for ext in allowed_preview_extensions], []) + for file in potential_files: + if os.path.exists(file): + return self.__link_preview(file) + return "./file=html/card-no-preview.png" + + def __find_metadata(self, lora) -> bool: + return len(lora.metadata) > 0 if lora else False + + #@functools.cache + def __find_description(self, path) -> Optional[str]: + if path: + for file in [f"{path}.txt", f"{path}.description.txt"]: + if os.path.exists(file): + try: + with open(file, "r", encoding="utf-8", errors="replace") as f: + return f.read() + except OSError: + pass + return None + + #@functools.cache + def __find_civitai_info(self, path) -> dict[str, Any]: + if path: + civitai_info = f"{path}.civitai.info" + if os.path.exists(civitai_info): + try: + with open(civitai_info, "r", encoding="utf-8", errors="replace") as f: + return json.load(f) + except OSError: + pass + return {} + +def on_ui_settings(): + """ + Set up the UI settings for the weight helper by adding options to the shared configuration. + """ + section = ('weight_helper', 'Weight Helper') + + shared.options_templates.update(shared.options_section(section, { + 'weight_helper_enabled': shared.OptionInfo(True, 'Enabled'), + 'weight_helper_context_menu_scale': shared.OptionInfo(0.8, "Context Menu Scale", gr.Number), + 'weight_helper_using_execCommand': shared.OptionInfo(True, "Use the deprecated execCommand function to replace text." + ).info( + "You can use 'undo' to revert the text to its previous state, " + "but it will no longer be updated in real-time." + ), + + "weight_helper_slider_length": shared.OptionInfo(160, "Slider Length (px)", gr.Number), + + "weight_helper_te_min": shared.OptionInfo(0, "TEnc Min Value", gr.Number), + "weight_helper_te_max": shared.OptionInfo(1, "TEnc Max Value", gr.Number), + "weight_helper_te_step": shared.OptionInfo(0.05, "TEnc Step", gr.Number), + + "weight_helper_unet_min": shared.OptionInfo(0, "UNet Min Value", gr.Number), + "weight_helper_unet_max": shared.OptionInfo(1, "UNet Max Value", gr.Number), + "weight_helper_unet_step": shared.OptionInfo(0.05, "UNet Step", gr.Number), + + "weight_helper_dyn_min": shared.OptionInfo(0, "Dyn Min Value", gr.Number), + "weight_helper_dyn_max": shared.OptionInfo(128, "Dyn Max Value", gr.Number), + "weight_helper_dyn_step": shared.OptionInfo(8, "Dyn Step", gr.Number), + + "weight_helper_lbw_min": shared.OptionInfo(0, "LBW (LoRA Block Weight) Min Value", gr.Number), + "weight_helper_lbw_max": shared.OptionInfo(1, "LBW (LoRA Block Weight) Max Value", gr.Number), + "weight_helper_lbw_step": shared.OptionInfo(0.05, "LBW (LoRA Block Weight) Step", gr.Number), + + 'weight_helper_show_preview': shared.OptionInfo(True, 'Show Preview'), + "weight_helper_preview_height": shared.OptionInfo(400, "Preview Height (px)", gr.Number), + "weight_helper_preview_position": shared.OptionInfo("Top Right", "Preview Position", gr.Radio, { + "choices": ["Top Right", "Bottom Right", "Top Left", "Bottom Left"] + }), + + "weight_helper_lbw_lora_sd_block_points": shared.OptionInfo( + "BASE, IN01-IN04, IN05-IN08, M00, OUT03-OUT06, OUT07-OUT11", + "Advanced option - LoRA-LierLa block points" + ).info( + "default: BASE, IN01-IN04, IN05-IN08, M00, OUT03-OUT06, OUT07-OUT11" + ), + "weight_helper_lbw_lycoris_sd_block_points": shared.OptionInfo( + "BASE, IN00-IN05, IN06-IN11, M00, OUT00-OUT05, OUT06-OUT11", + "Advanced option - LyCORIS or LoRA-C3Lier block points" + ).info( + "default: BASE, IN00-IN05, IN06-IN11, M00, OUT00-OUT05, OUT06-OUT11" + ), + "weight_helper_lbw_lora_sdxl_block_points": shared.OptionInfo( + "BASE, IN04-IN08, M00, OUT00-OUT05", + "Advanced option - SDXL LoRA-LierLa block points" + ).info( + "default: BASE, IN04-IN08, M00, OUT00-OUT05" + ), + "weight_helper_lbw_lycoris_sdxl_block_points": shared.OptionInfo( + "BASE, IN00-IN03, IN04-IN08, M00, OUT00-OUT03, OUT04-OUT08", + "Advanced option - SDXL LyCORIS or LoRA-C3Lier block points" + ).info( + "default: BASE, IN00-IN03, IN04-IN08, M00, OUT00-OUT03, OUT04-OUT08" + ) + })) + +script_callbacks.on_ui_settings(on_ui_settings) +script_callbacks.on_app_started(WeightHelperAPI.init_endpoints) diff --git a/extensions/CHECK/sd-webui-weight-helper/style.css b/extensions/CHECK/sd-webui-weight-helper/style.css new file mode 100644 index 0000000000000000000000000000000000000000..b20ebc265ff7ac8c40df900b759ec7812e3b0fa6 --- /dev/null +++ b/extensions/CHECK/sd-webui-weight-helper/style.css @@ -0,0 +1,343 @@ +#weight-helper { + text-align: center; + max-width: 330px; + transform-origin: left top; + color: var(--body-text-color); + background: var(--body-background-fill); + position: absolute; + z-index: 1000; + border: var(--input-border-width) solid var(--border-color-primary); + box-shadow: 0px 0px 7px 0 var(--weight-helper-shadow); +} +#weight-helper > header { + height: 24px; + background-color: var(--border-color-primary); + margin-bottom: 4px; + color: var(--block-label-text-color); + user-select: none; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0px 3px; + cursor: move; +} +#weight-helper > header > span { + display: flex; + align-items: center; +} +#weight-helper > header .name { + max-width: 146px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + pointer-events: none; +} +#weight-helper > header .lock { + width: 16px; + height: 20px; + cursor: pointer; +} +#weight-helper > header .lock.unlike > svg { + fill: var(--checkbox-background-color); +} +#weight-helper > header .lock.like > svg { + fill: var(--checkbox-border-color-selected); +} +#weight-helper > header .page { + display: flex; + align-items: center; + padding-right: 4px; +} +#weight-helper > header .page > label { + pointer-events: none; +} +#weight-helper .icon { + background-color: var(--block-info-text-color); + color: var(--border-color-primary); + border-radius: 10px; + padding: 0 7px; + text-align: center; + font-size: var(--text-xs); + display: flex; + justify-content: center; +} +#weight-helper .icon.svg { + background-color: initial; + width: 13px; +} +#weight-helper .icon.svg > svg { + stroke: var(--block-label-text-color); + fill: var(--block-label-text-color); +} +#weight-helper .icon.mini { + padding: 0 4px; +} +#weight-helper > header .history { + display: flex; + align-items: center; + gap: 10px; +} +#weight-helper > header .history label { + width: 40px; +} +#weight-helper .metadata { + color: var(--block-label-text-color); + font-size: 10px; + margin: 0 4px 4px; + display: flex; + column-gap: 4px; + justify-content: space-between; + user-select: none; + align-items: stretch; +} +#weight-helper .metadata > span { + font-weight: bold; + border-radius: 4px; + border: 1px solid var(--block-info-text-color); + text-align: left; + flex: 1 1 50%; + display: flex; + align-items: stretch; +} +#weight-helper .metadata > span > span:nth-child(1) { + color: var(--block-label-text-color); + background-color: var(--border-color-primary); + border-right: 1.5px solid var(--block-info-text-color); + border-radius: 3px 0 0 3px; + padding: 0px 6px 0px 5px; +} +#weight-helper .metadata > span > span:nth-child(2) { + margin: 0 4px; + white-space: nowrap; +} +#weight-helper .metadata > span > span:nth-child(2).loading { + display: block; + width: 100%; + text-align: center; + margin-left: 0; +} +#weight-helper .metadata.error > span > span:nth-child(2) { + color: var(--error-text-color); +} +#weight-helper section { + display: flex; + justify-content: space-between; + align-items: center; + gap: 6px; +} +#weight-helper section > span { + width: 108px; + display: flex; + justify-content: space-between; +} +#weight-helper button { + cursor: pointer; + border: var(--button-border-width) solid var(--button-secondary-border-color); + background: var(--button-secondary-background-fill); + color: var(--button-secondary-text-color); + border-radius: 4px; + + display: inline-flex; + justify-content: center; + align-items: center; + transition: var(--button-transition); + box-shadow: var(--button-shadow); + padding: var(--size-0-5) var(--size-2); + text-align: center; +} +#weight-helper button:hover { + border-color: var(--button-secondary-border-color-hover); + background: var(--button-secondary-background-fill-hover); + color: var(--button-secondary-text-color-hover); + box-shadow: var(--button-shadow-hover); +} +#weight-helper button:active { + box-shadow: var(--button-shadow-active); +} +#weight-helper .p { + padding: 3px; +} +#weight-helper .border { + border: 1px solid var(--block-border-color) !important; + border-radius: 8px !important; +} +#weight-helper > section, +#weight-helper > button { + margin: 0 4px 4px; +} +#weight-helper label { + user-select: none; + font-family: var(--font); + font-size: var(--section-header-text-size); + padding: 0 5px 0 5px; +} +#weight-helper input.slider { + width: var(--weight-helper-slider_size); + min-width: initial; + margin: 0; + accent-color: var(--slider-color); +} +#weight-helper input.value { + width: 4.5em; + height: 1.4em !important; + padding: 0; + margin: 0; + display: block; + position: relative; + outline: none!important; + box-shadow: var(--input-shadow); + border: var(--input-border-width) solid var(--input-border-color); + border-radius: var(--input-radius); + background: var(--input-background-fill); + color: var(--body-text-color); + font-size: var(--input-text-size); + line-height: var(--line-sm); + text-align: center; +} +#weight-helper select { + color: var(--body-text-color); + outline: none; + padding: calc(var(--input-padding) - 5px); + border: var(--input-border-width) solid var(--border-color-primary); + border-radius: var(--input-radius); + background: var(--input-background-fill); +} +#weight-helper select:focus { + box-shadow: var(--input-shadow-focus); + border-color: var(--input-border-color-focus); +} +#weight-helper label[for] { + cursor: pointer; +} +#weight-helper input[type="radio"] { + margin: 3px 0px 3px 4px; +} +#weight-helper label.radio-label { + min-width: 22px; + padding-left: 0px; +} +#weight-helper .preview-pane { + position: absolute; + overflow: hidden; + border-radius: 6px; + box-shadow: 0px 0px 7px 0 var(--weight-helper-shadow); +} +#weight-helper .preview-pane:hover .action-row { + visibility: visible; +} +#weight-helper .preview-pane .action-row { + visibility: hidden; + display: flex; + width: 100%; + position: absolute; + height: 30px; + background-color: rgb(0 0 0 / 40%); +} +#weight-helper .preview-pane .button-top { + top: 0; +} +#weight-helper .preview-pane .button-bottom { + bottom: 0; + justify-content: center; +} +#weight-helper .preview-pane .card-btn { + font-size: 1.5rem; + cursor: pointer; + width: 40px; + padding: 0; + margin: 0; +} +#weight-helper .preview-pane .card-btn::before, +#weight-helper .preview-pane .card-btn::after { + position: absolute; + top: 50%; + transform: translate(-50%, -50%); +} +#weight-helper .preview-pane .edit-btn::before, +#weight-helper .preview-pane .edit-btn::after { + content: "🛠"; +} +#weight-helper .preview-pane .metadata-btn::before, +#weight-helper .preview-pane .metadata-btn::after { + content: "🛈"; +} +#weight-helper .preview-pane .civitai-btn::before, +#weight-helper .preview-pane .civitai-btn::after { + content: "🌐"; + font-size: calc(75%); +} +#weight-helper .preview-pane .add-trigger-btn::before, +#weight-helper .preview-pane .add-trigger-btn::after { + content: "🔫"; + font-size: calc(75%); +} +#weight-helper .preview-pane .description-close-btn::before, +#weight-helper .preview-pane .description-close-btn::after { + content: "❌"; + font-size: calc(75%); +} +#weight-helper .preview-pane .note-btn::before, +#weight-helper .preview-pane .note-btn::after { + content: "🖹"; +} +#weight-helper .preview-pane .card-btn::before { + text-shadow: 0px 3px 2px #000000B0; +} +#weight-helper .preview-pane .card-btn::after { + color: transparent; + text-shadow: 0 0 0 white; +} +#weight-helper .preview-pane .card-btn:hover::after { + text-shadow: 0 0 0 red; +} +#weight-helper .preview-pane .preview { + vertical-align: middle; +} +#weight-helper .preview-pane .description { + visibility: hidden; + color: white; + font-size: 0.9rem; + background-color: rgb(0 0 0 / 70%); + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + padding: 5px 5px 35px 5px; + margin: 0; + box-sizing: border-box; + border: none; + resize: none; + outline: none; + -ms-overflow-style: none; + scrollbar-width: none; +} +#weight-helper .preview-pane .description-close-btn { + visibility: hidden; + position: absolute; + bottom: 0; + width: 100%; + height: 30px; +} +#weight-helper .f { + display: flex; + justify-content: space-between; +} +#weight-helper .f-c { + align-items: center; +} +#weight-helper .f-end { + justify-content: flex-end; +} +#weight-helper .col { + flex-direction: column; +} +#weight-helper .g-2 { + gap: 2px; +} +#weight-helper .g-4 { + gap: 4px; +} +#weight-helper .w-fill { + width: 100%; +} diff --git a/extensions/CHECK/stable-diffusion-NPW/LICENSE b/extensions/CHECK/stable-diffusion-NPW/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..fdddb29aa445bf3d6a5d843d6dd77e10a9f99657 --- /dev/null +++ b/extensions/CHECK/stable-diffusion-NPW/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/extensions/CHECK/stable-diffusion-NPW/README.md b/extensions/CHECK/stable-diffusion-NPW/README.md new file mode 100644 index 0000000000000000000000000000000000000000..75e157c44d85d108dbc1b78b81abfefa1fc3ec93 --- /dev/null +++ b/extensions/CHECK/stable-diffusion-NPW/README.md @@ -0,0 +1,68 @@ + +# Negative Prompt Weight + +This is a simple extension for the [Stable Diffusion Web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui), which allows users to adjust the overall weight of the negative prompt, allowing you to increase or decrease its effect in a new way. + +## What Does It Do? + +Here's a demonstration of how it can continously reduce the effect of the negative prompt from what you normally get (on the right, with weight 1.0) to nothing, as if the negative prompt was empty (on the left, with weight 0.0): + +![Another example plot showing the effect of different weights](/assets/example1.jpg) +*Prompt: portrait of zimby anton fadeev cyborg propaganda poster*
+*Params: Steps: 30, Sampler: DPM++ SDE Karras, CFG scale: 7.5, Seed: 918, Size: 512x640, Model: deliberate_v2*
+*Negative Prompt: Male* + +![Another example plot showing the effect of different weights](/assets/example2.jpg) +*Prompt: portrait of zimby anton fadeev cyborg propaganda poster*
+*Params: Steps: 30, Sampler: DPM++ SDE Karras, CFG scale: 7.5, Seed: 918, Size: 512x640, Model: deliberate_v2*
+*Negative Prompt: Female* + +### Why Use This? + +This method was originally intended for decreasing the effect of the negative prompt, which is very hard or at times impossible to do with the currently available methods like Better Prompting™, Attention/Emphasis (using the '(prompt:weight)' syntax), Prompt Editing (using the [prompt1:prompt2:when] syntax), etc. But you can also use it with values higher than 1 and it will boost your negative prompt in its own style (you might need to lower your CFG scale a bit if you do that). + +Here is the first example compared to using the '(negative prompts: weight)' syntax (i.e. bottom row is (negative prompt:0),(negative prompt:0.25),etc.: + +![portrait of zimby anton fadeev cyborg propaganda poster-24-male](https://user-images.githubusercontent.com/48160881/229344713-81793753-d9ae-4927-b5e9-03a7749dfc95.jpg) + +Please have a look at the examples in the [comparisons](https://github.com/muerrilla/stable-diffusion-NPW#more-comparisons-and-stuff) section if you want to know how it's different from using '(prompt:weight)' and check out the discussion [here](https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/9220) if you need more context. + +## Installation + +Open SD WebUI > Go to Extensions tab > Go to Available > Press the big button > Find 'Negative Prompt Weight' in the list > Click Install + +Or manually clone this repo into your extensions folder: + +`git clone "https://github.com/muerrilla/stable-diffusion-NPW" extensions/stable-diffusion-NPW` + +## Usage + +![Screenshot of the slider provided by the extension in UI](/assets/screenshot.png "Does what it says on the box.") + +After installing, you can find the new parameter "Negative Prompt Weight" in the extentions area of txt2img and img2img tabs. + +## More Comparisons and Stuff + +Here are some comparisons between NPW and Attention/Emphasis. So, top row is using NPW and bottom row is using the (Negative Prompt: weight) syntax with the same weights. + +```Prompts: a close up portrait of a cyberpunk [knight|lobster], [lobster| ] armour, cyberpunk!, fantasy, elegant, digital painting, artstation, concept art, matte, sharp focus, art by josan gonzalez``` + +```Params: Steps: 30, Sampler: DPM++ 2M Karras, CFG scale: 10, Seed: 6, Size: 512x640, Model: deliberate_v2``` + + +![a close up portrait of a cyberpunk knight-2-red](https://user-images.githubusercontent.com/48160881/229320416-c805642e-168d-4d35-a4c8-a1f0b066a982.jpg) +*Negative Prompt: red* + + + +![a close up portrait of a cyberpunk knight-25-samurai pink cg](https://user-images.githubusercontent.com/48160881/229320590-1beaf1ac-5ede-49ad-b2bd-7e761fdd49df.jpg) +*Negative Prompt: samurai pink cg* + + + +![a close up portrait of a cyberpunk knight-42](https://user-images.githubusercontent.com/48160881/229321419-055bd6ad-2931-4ad1-96d2-69b047ea1c97.jpg) +*Negative Prompt: *custom TI embedding** + +## How It's Done + +At runtime a new learned conditioning tensor `empty_uncond` is made from an empty prompt. Then at every step, inside the denoiser callback, the scheduled `uncond` tensor of the denoiser (which is based on whatever prompt hijinks were passed to the parser) is lerped with the `empty_uncond` to weaken it's effect. The lerp function can instead be given a parameter bigger than 1, and it will boost the effect of the negative prompt like the CFG scale does for the positive. diff --git a/extensions/CHECK/stable-diffusion-NPW/assets/example1.jpg b/extensions/CHECK/stable-diffusion-NPW/assets/example1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..05021c7f0c702560a901d55c77c88773f404939e Binary files /dev/null and b/extensions/CHECK/stable-diffusion-NPW/assets/example1.jpg differ diff --git a/extensions/CHECK/stable-diffusion-NPW/assets/example2.jpg b/extensions/CHECK/stable-diffusion-NPW/assets/example2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..78e6272a70d76754dd3a5a37d37e35dcffb0d53e Binary files /dev/null and b/extensions/CHECK/stable-diffusion-NPW/assets/example2.jpg differ diff --git a/extensions/CHECK/stable-diffusion-NPW/assets/screenshot.png b/extensions/CHECK/stable-diffusion-NPW/assets/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..85f10558030299d588b38905ceceb213d1f9784a Binary files /dev/null and b/extensions/CHECK/stable-diffusion-NPW/assets/screenshot.png differ diff --git a/extensions/CHECK/stable-diffusion-NPW/javascript/setup.js b/extensions/CHECK/stable-diffusion-NPW/javascript/setup.js new file mode 100644 index 0000000000000000000000000000000000000000..89d43c409a922d8df276ecb9e42b951a4fc17030 --- /dev/null +++ b/extensions/CHECK/stable-diffusion-NPW/javascript/setup.js @@ -0,0 +1,26 @@ +function setupNPW() { + fixAccordion('tab_txt2img'); + fixAccordion('tab_img2img'); + fixInputs('tab_txt2img'); + fixInputs('tab_img2img'); +} + +function fixInputs(tab) { + const npwSlider = document.querySelector(`#${tab} #npw-slider`); + + npwSlider.querySelector('.head').remove(); + + const newSpan = document.createElement("span"); + newSpan.innerHTML = "Negative Prompt Weight"; + const ancestor = npwSlider.parentNode.parentNode.parentNode; + ancestor.insertBefore(newSpan, ancestor.firstChild); + + document.querySelector(`#${tab} #npw-number input[type="number"]`).setAttribute("step", "0.01"); +} + +function fixAccordion(tab) { + document.querySelector(`#${tab} #npw .icon`).remove(); + document.querySelector(`#${tab} #npw .open`).remove(); +} + +onUiLoaded(setupNPW); diff --git a/extensions/CHECK/stable-diffusion-NPW/scripts/npw.py b/extensions/CHECK/stable-diffusion-NPW/scripts/npw.py new file mode 100644 index 0000000000000000000000000000000000000000..67066c2dd435250231001226e0af8772af7a0fd3 --- /dev/null +++ b/extensions/CHECK/stable-diffusion-NPW/scripts/npw.py @@ -0,0 +1,147 @@ +import os +import torch +import gradio as gr + +import modules.scripts as scripts +import modules.shared as shared +from modules.script_callbacks import on_cfg_denoiser, remove_current_script_callbacks +from modules.prompt_parser import SdConditioning + + +class Script(scripts.Script): + + def title(self): + return "Negative Prompt Weight Extention" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with gr.Accordion("Negative Prompt Weight", open=True, elem_id="npw"): + with gr.Row(equal_height=True): + with gr.Column(scale=100): + weight_input_slider = gr.Slider(minimum=0.00, maximum=2.00, step=.05, value=1.00, label="Weight", interactive=True, elem_id="npw-slider") + with gr.Column(scale=1, min_width=120): + with gr.Row(): + weight_input = gr.Number(value=1.00, precision=4, label="Negative Prompt Weight", show_label=False, elem_id="npw-number") + reset_but = gr.Button(value='✕', elem_id='npw-x', size='sm') + + js = """(v) => { + ['#tab_txt2img #npw-x', '#tab_img2img #npw-x'].forEach((selector, index) => { + const element = document.querySelector(selector); + if (document.querySelector(`#tab_${index ? 'img2img' : 'txt2img'}`).style.display === 'block') { + element.style.cssText += `outline:4px solid rgba(255,186,0,${Math.sqrt(Math.abs(v-1))}); border-radius: 0.4em !important;`; + } + }); + return v; + }""" + + weight_input.change(None, [weight_input], weight_input_slider, _js=js) + weight_input_slider.change(None, weight_input_slider, weight_input, _js="(x) => x") + reset_but.click(None, [], [weight_input,weight_input_slider], _js="(x) => [1,1]") + + + self.infotext_fields = [] + self.infotext_fields.extend([ + (weight_input, "NPW_weight"), + ]) + self.paste_field_names = [] + for _, field_name in self.infotext_fields: + self.paste_field_names.append(field_name) + + return [weight_input] + + + def process(self, p, weight): + + weight = getattr(p, 'NPW_weight', weight) + if weight != 1 : self.print_warning(weight) + self.width = p.width + self.height = p.height + self.weight = weight + self.empty_uncond = None + + + if hasattr(self, 'callbacks_added'): + remove_current_script_callbacks() + delattr(self, 'callbacks_added') + # print('NPW callback removed') + + if self.weight != 1.0: + self.empty_uncond = self.make_empty_uncond(self.width, self.height) + on_cfg_denoiser(self.denoiser_callback) + # print('NPW callback added') + self.callbacks_added = True + + p.extra_generation_params.update({ + "NPW_weight": self.weight, + }) + + return + + def postprocess(self, p, processed, *args): + if hasattr(self, 'callbacks_added'): + remove_current_script_callbacks() + delattr(self, 'callbacks_added') + # print('NPW callback removed in post') + + def denoiser_callback(self, params): + def concat_and_lerp(empty, tensor, weight): + if empty.shape[0] != tensor.shape[0]: + empty = empty.expand(tensor.shape[0], *empty.shape[1:]) + if tensor.shape[1] > empty.shape[1]: + num_concatenations = tensor.shape[1] // empty.shape[1] + empty_concat = torch.cat([empty] * num_concatenations, dim=1) + if tensor.shape[1] == empty_concat.shape[1] + 1: + # assuming it's controlnet's marks(?) + empty_concat = torch.cat([tensor[:, :1, :], empty_concat], dim=1) + new_tensor = torch.lerp(empty_concat, tensor, weight) + else: + new_tensor = torch.lerp(empty, tensor, weight) + return new_tensor + + uncond = params.text_uncond + is_dict = isinstance(uncond, dict) + if type(self.empty_uncond) != type(uncond): + self.empty_uncond = self.make_empty_uncond(self.width, self.height) + empty_uncond = self.empty_uncond + + if is_dict: + uncond, cross = uncond['vector'], uncond['crossattn'] + empty_uncond, empty_cross = empty_uncond['vector'], empty_uncond['crossattn'] + params.text_uncond['vector'] = concat_and_lerp(empty_uncond, uncond, self.weight) + params.text_uncond['crossattn'] = concat_and_lerp(empty_cross, cross, self.weight) + else: + params.text_uncond = concat_and_lerp(empty_uncond, uncond, self.weight) + + def make_empty_uncond(self, w, h): + prompt = SdConditioning([""], is_negative_prompt=True, width=w, height=h) + empty_uncond = shared.sd_model.get_learned_conditioning(prompt) + return empty_uncond + + def print_warning(self, value): + if value == 1: + return + color_code = '\033[33m' + if value < 0.5 or value > 1.5: + color_code = '\033[93m' + print(f"\n{color_code}ATTENTION: Negative prompt weight is set to {value}\033[0m") + + +def xyz_support(): + for scriptDataTuple in scripts.scripts_data: + if os.path.basename(scriptDataTuple.path) == 'xyz_grid.py': + xy_grid = scriptDataTuple.module + + npw_weight = xy_grid.AxisOption( + '[NPW] Weight', + float, + xy_grid.apply_field('NPW_weight') + ) + xy_grid.axis_options.extend([ + npw_weight + ]) +try: + xyz_support() +except Exception as e: + print(f'Error trying to add XYZ plot options for NPW', e) diff --git a/extensions/CHECK/stable-diffusion-NPW/style.css b/extensions/CHECK/stable-diffusion-NPW/style.css new file mode 100644 index 0000000000000000000000000000000000000000..0d35932a6d6c3c199160f7e7c90e845f0d6a0d69 --- /dev/null +++ b/extensions/CHECK/stable-diffusion-NPW/style.css @@ -0,0 +1,35 @@ +#npw span { + margin-bottom: 0 !important; + font-size: var(--section-header-text-size); + min-width: fit-content; +} + +#npw input[type=number] { + padding: 4px 12px !important; +} + +#npw button { + min-width: 30px; + max-width: 30px; + min-height: fit-content; + font-weight: var(--button-large-text-weight); + border-radius: 0.5em; + border-width: 1px; +} + +#npw .gradio-row { + align-items: center; + gap: 0.5em; +} + +#npw .gradio-checkbox { + margin: 0; +} + +#npw .form { + min-width: unset !important; +} + +#npw-slider { + padding: 0 10px !important; +} diff --git a/extensions/DriftCorrection/LICENSE b/extensions/DriftCorrection/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..fdddb29aa445bf3d6a5d843d6dd77e10a9f99657 --- /dev/null +++ b/extensions/DriftCorrection/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/extensions/DriftCorrection/README.md b/extensions/DriftCorrection/README.md new file mode 100644 index 0000000000000000000000000000000000000000..075f93979e0ee0828b90e8fe2897841b80d07d2f --- /dev/null +++ b/extensions/DriftCorrection/README.md @@ -0,0 +1,55 @@ +# Latent Drift Correction # +### extension for Forge webui for Stable Diffusion ### + +--- +## Install ## +Go to the **Extensions** tab, then **Install from URL**, use the URL for this repository. + +--- +![](screenshot.png "image of extension UI") + +--- +## Basic usage ## +Pick methods. + +--- +## Advanced / Details ## +Delaying the start can be beneficial, as can early ending. +This sort of correction has a tendency to prevent extremes of lighting. +custom functions: +* M: mean +* m: median +* q(n): quantile. 0.5 is same as median; using high values will darken the image appropriately for sunsets, etc. +* rM(n, m): mean of range, rM(0, 0.5) gives mean of lowest 50% +* c: channel index, will be 0, 1, 2 or 3. Example uses: q([0.9, 0.5, 0.5, 0.7][c]); [m, 0.0, 0.0, 0.1][c] + + +--- +#### 25/06/2024 #### +* generation parameters now also saved to *params.txt* + +#### 06/06/2024 #### +larger update: +* added local average to overall, can use as a local contrast adjustment, adjustable blur radius +* added strength sliders for per channel and overall (removes previously hidden multipliers, defaults match those previous values) +* minor UI reshuffle + +#### 05/11/2024 #### +fixed bug with centered mean and topK of 0.5. (0.5-0.5)=0, who knew? + +#### 25/04/2024 #### +added saving/loading of custom functions + +--- +## License ## +Public domain. Unlicense. Free to a good home. +All terrible code is my own. Use at your own risk, read the code. + +--- +## Credits ## +General idea from (Birch Labs)[https://birchlabs.co.uk/machine-learning#combating-mean-drift-in-cfg] but this is after CFG + +SoftClamp method by (Timothy Alexis Vass)[https://huggingface.co/blog/TimothyAlexisVass/explaining-the-sdxl-latent-space] + + +--- diff --git a/extensions/DriftCorrection/screenshot.png b/extensions/DriftCorrection/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ffbf72a938da9221b6f25fbf875e1666804a3520 Binary files /dev/null and b/extensions/DriftCorrection/screenshot.png differ diff --git a/extensions/DriftCorrection/scripts/__pycache__/forge_driftr.cpython-310.pyc b/extensions/DriftCorrection/scripts/__pycache__/forge_driftr.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6ff1dd995cc2160e8ad475d61233ab105b18771 Binary files /dev/null and b/extensions/DriftCorrection/scripts/__pycache__/forge_driftr.cpython-310.pyc differ diff --git a/extensions/DriftCorrection/scripts/forge_driftr.py b/extensions/DriftCorrection/scripts/forge_driftr.py new file mode 100644 index 0000000000000000000000000000000000000000..1e41cd89626590494243480254463afe2bcbe511 --- /dev/null +++ b/extensions/DriftCorrection/scripts/forge_driftr.py @@ -0,0 +1,291 @@ +import gradio as gr + +from modules import scripts +import modules.shared as shared +import torch, math +import torchvision.transforms.functional as TF + +#effect seems better when aplied to denoised result after CFG, rather than to cond/uncond before CFG + +class driftrForge(scripts.Script): + def __init__(self): + self.method1 = "None" + self.method2 = "None" + + def title(self): + return "Latent Drift Correction" + + def show(self, is_img2img): + # make this extension visible in both txt2img and img2img tab. + return scripts.AlwaysVisible + + def ui(self, *args, **kwargs): + with gr.Accordion(open=False, label=self.title()): + with gr.Row(): + method1 = gr.Dropdown(["None", "custom", "mean", "median", "mean/median average", "centered mean", "average of extremes", "average of quantiles"], value="None", type="value", label='Correction method (per channel)') + method2 = gr.Dropdown(["None", "mean", "median", "mean/median average", "center to quantile", "local average"], value="None", type="value", label='Correction method (overall)') + with gr.Row(): + strengthC = gr.Slider(minimum=-1.0, maximum=1.0, step=0.01, value=1.0, label='strength (per channel)') + strengthO = gr.Slider(minimum=-1.0, maximum=1.0, step=0.01, value=0.8, label='strength (overall)') + with gr.Row(equalHeight=True): + custom = gr.Textbox(value='0.5 * (M + m)', max_lines=1, label='custom function', visible=True) + topK = gr.Slider(minimum=0.01, maximum=1.0, step=0.01, value=0.5, label='quantiles', visible=False, scale=0) + blur = gr.Slider(minimum=0, maximum=128, step=1, value=0, label='blur radius (x8)', visible=False, scale=0) + sigmaWeight = gr.Dropdown(["Hard", "Soft", "None"], value="Hard", type="value", label='Limit effect by sigma', scale=0) + with gr.Row(): + stepS = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.0, label='Start step') + stepE = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=1.0, label='End step') + with gr.Row(): + softClampS = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=1.0, label='Soft clamp start step') + softClampE = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=1.0, label='Soft clamp end step') + + def show_topK(m1, m2): + if m1 == "centered mean" or m1 == "average of extremes" or m1 == "average of quantiles": + return gr.update(visible=True), gr.update(visible=False) + elif m2 == "center to quantile": + return gr.update(visible=True), gr.update(visible=False) + elif m2 == "local average": + return gr.update(visible=False), gr.update(visible=True) + else: + return gr.update(visible=False), gr.update(visible=False) + + method1.change( + fn=show_topK, + inputs=[method1, method2], + outputs=[topK, blur], + show_progress=False + ) + method2.change( + fn=show_topK, + inputs=[method1, method2], + outputs=[topK, blur], + show_progress=False + ) + + self.infotext_fields = [ + (method1, "ldc_method1"), + (method2, "ldc_method2"), + (topK, "ldc_topK"), + (blur, "ldc_blur"), + (strengthC, "ldc_strengthC"), + (strengthO, "ldc_strengthO"), + (stepS, "ldc_stepS"), + (stepE, "ldc_stepE"), + (sigmaWeight, "ldc_sigW"), + (softClampS, "ldc_softClampS"), + (softClampE, "ldc_softClampE"), + (custom, "ldc_custom"), + ] + + return method1, method2, topK, blur, strengthC, strengthO, stepS, stepE, sigmaWeight, softClampS, softClampE, custom + + + def patch(self, model): + model_sampling = model.model.model_sampling + sigmin = model_sampling.sigma(model_sampling.timestep(model_sampling.sigma_min)) + sigmax = model_sampling.sigma(model_sampling.timestep(model_sampling.sigma_max)) + + +## https://huggingface.co/blog/TimothyAlexisVass/explaining-the-sdxl-latent-space + def soft_clamp_tensor(input_tensor, threshold=3.5, boundary=4): + if max(abs(input_tensor.max()), abs(input_tensor.min())) < 4: + return input_tensor + channel_dim = 1 + + max_vals = input_tensor.max(channel_dim, keepdim=True)[0] + max_replace = ((input_tensor - threshold) / (max_vals - threshold)) * (boundary - threshold) + threshold + over_mask = (input_tensor > threshold) + + min_vals = input_tensor.min(channel_dim, keepdim=True)[0] + min_replace = ((input_tensor + threshold) / (min_vals + threshold)) * (-boundary + threshold) - threshold + under_mask = (input_tensor < -threshold) + + return torch.where(over_mask, max_replace, torch.where(under_mask, min_replace, input_tensor)) + + def center_latent_mean_values(latent, multiplier): + thisStep = shared.state.sampling_step + lastStep = shared.state.sampling_steps + + channelMultiplier = multiplier * self.strengthC + fullMultiplier = multiplier * self.strengthO + + if thisStep >= self.stepS * lastStep and thisStep <= self.stepE * lastStep: + for b in range(len(latent)): + for c in range(4): + custom = None + channel = latent[b][c] + + if self.method1 == "mean": + custom = "M" + #averageMid = torch.mean(channel) + #latent[b][c] -= averageMid * channelMultiplier + + elif self.method1 == "median": + custom = "m" + #averageMid = torch.quantile(channel, 0.5) + #latent[b][c] -= averageMid * channelMultiplier + + elif self.method1 == "mean/median average": + custom = "0.5 * (M+m)" + #averageMid = 0.5 * (torch.mean(channel) + torch.quantile(channel, 0.5)) + #latent[b][c] -= averageMid * channelMultiplier + + + elif self.method1 == "centered mean": + custom="rM(self.topK, 1.0-self.topK)" +## valuesHi = torch.topk(channel, int(len(channel)*self.topK), largest=True).values +## valuesLo = torch.topk(channel, int(len(channel)*self.topK), largest=False).values +## averageMid = torch.mean(channel).item() * len(channel) +## averageMid -= torch.mean(valuesHi).item() * len(channel)*self.topK +## averageMid -= torch.mean(valuesLo).item() * len(channel)*self.topK +## averageMid /= len(channel)*(1.0 - 2*self.topK) +## latent[b][c] -= averageMid * channelMultiplier + + elif self.method1 == "average of extremes": + custom="0.5 * (inner_rL(self.topK) + inner_rH(self.topK))" +## valuesHi = torch.topk(channel, int(len(channel)*self.topK), largest=True).values +## valuesLo = torch.topk(channel, int(len(channel)*self.topK), largest=False).values +## averageMid = 0.5 * (torch.mean(valuesHi).item() + torch.mean(valuesLo).item()) +## latent[b][c] -= averageMid * channelMultiplier + + elif self.method1 == "average of quantiles": + custom="0.5 * (q(self.topK) + q(1.0-self.topK))" +## averageMid = 0.5 * (torch.quantile(channel, self.topK) + torch.quantile(channel, 1.0 - self.topK)) +## latent[b][c] -= averageMid * channelMultiplier + + elif self.method1 == "custom": + custom = self.custom + + if custom != None: + M = torch.mean(channel) + m = torch.quantile(channel, 0.5) + def q(quant): + return torch.quantile(channel, quant) + def qa(quant): + return torch.quantile(abs(channel), quant) + def inner_rL(lo): # mean of values from lowest to input(proportional) + valuesLo = torch.topk(channel, int(len(channel)*lo), largest=False).values + return torch.mean(valuesLo).item() + def inner_rH(hi): # mean of values from input(proportional) to highest + valuesHi = torch.topk(channel, int(len(channel)*hi), largest=True).values + return torch.mean(valuesHi).item() + + def rM(rangelo, rangehi): # mean of range + if rangelo == rangehi: + return M + else: + averageHi = inner_rH(1.0-rangehi) + averageLo = inner_rL(rangelo) + + average = torch.mean(channel).item() * len(channel) + average -= averageLo * len(channel) * rangelo + average -= averageHi * len(channel) * (1.0-rangehi) + average /= len(channel)*(rangehi - rangelo) + return average + + averageMid = eval(custom) + latent[b][c] -= averageMid * channelMultiplier + + if self.method2 == "mean": + latent[b] -= latent[b].mean() * fullMultiplier + elif self.method2 == "median": + latent[b] -= latent[b].median() * fullMultiplier + elif self.method2 == "mean/median average": + mm = latent[b].mean() + latent[b].median() + latent[b] -= 0.5 * fullMultiplier * mm + elif self.method2 == "center to quantile": + quantile = torch.quantile(latent[b].flatten(), self.topK) # 0.5 is same as median + latent[b] -= quantile * fullMultiplier + elif self.method2 == "local average" and fullMultiplier != 0.0 and self.blur != 0: + minDim = min(latent.size(2), latent.size(3)) + if minDim % 2 == 0: # blur kernel size must be odd + minDim -= 1 + blurSize = min (minDim, 1+self.blur+self.blur) + + blurred = TF.gaussian_blur(latent[b], blurSize) + torch.lerp(latent[b], blurred, fullMultiplier, out=latent[b]) + del blurred + + + if thisStep >= self.softClampS * lastStep and thisStep <= self.softClampE * lastStep: + for b in range(len(latent)): + latent[b] = soft_clamp_tensor (latent[b]) + + + return latent + + + def map_sigma(sigma, sigmax, sigmin): + return (sigma - sigmin) / (sigmax - sigmin) + + def center_mean_latent_post_cfg(args): + denoised = args["denoised"] + sigma = args["sigma"][0] + + if self.sigmaWeight == "None": # range 1 - always full correction + mult = 1 + else: + mult = map_sigma(sigma, sigmax, sigmin) # range 0.0 to 1.0 + if self.sigmaWeight == "Soft": # range 0.5 to 1.0 + mult += 1.0 + mult /= 2.0 + + denoised = center_latent_mean_values(denoised, mult) + return denoised + + m = model.clone() + m.set_model_sampler_post_cfg_function(center_mean_latent_post_cfg) + + return (m, ) + + + def process(self, params, *script_args, **kwargs): + method1, method2, topK, blur, strengthC, strengthO, stepS, stepE, sigmaWeight, softClampS, softClampE, custom = script_args + + if method1 == "None" and method2 == "None": + return + + self.method1 = method1 + self.method2 = method2 + self.topK = topK + self.blur = blur + self.strengthC = strengthC + self.strengthO = strengthO + self.stepS = stepS + self.stepE = stepE + self.sigmaWeight = sigmaWeight + self.softClampS = softClampS + self.softClampE = softClampE + self.custom = custom + + # Below codes will add some logs to the texts below the image outputs on UI. + # The extra_generation_params does not influence results. + params.extra_generation_params.update(dict( + ldc_method1 = method1, + ldc_method2 = method2, + ldc_strengthC = strengthC, + ldc_strengthO = strengthO, + ldc_stepS = stepS, + ldc_stepE = stepE, + ldc_sigW = sigmaWeight, + ldc_softClampS = softClampS, + ldc_softClampE = softClampE, + )) + if method1 == "custom": + params.extra_generation_params.update(dict(ldc_custom = custom, )) + if method1 == "centered mean" or method1 == "average of extremes" or method1 == "average of quantiles" or method2 == "center to quantile": + params.extra_generation_params.update(dict(ldc_topK = topK, )) + if method2 == "local average": + params.extra_generation_params.update(dict(ldc_blur = blur, )) + + return + + + def process_before_every_sampling(self, params, *script_args, **kwargs): + method1, method2 = script_args[0], script_args[1] + if method1 != "None" or method2 != "None": + unet = params.sd_model.forge_objects.unet + unet = driftrForge.patch(self, unet)[0] + params.sd_model.forge_objects.unet = unet + + return diff --git a/extensions/Euler-Smea-Dyn-Sampler/.gitignore b/extensions/Euler-Smea-Dyn-Sampler/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ed8ebf583f771da9150c35db3955987b7d757904 --- /dev/null +++ b/extensions/Euler-Smea-Dyn-Sampler/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/extensions/Euler-Smea-Dyn-Sampler/LICENSE b/extensions/Euler-Smea-Dyn-Sampler/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2d63b1a0a2584fe392ed316913115d1a8678140d --- /dev/null +++ b/extensions/Euler-Smea-Dyn-Sampler/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 KBlueLeaf + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/extensions/Euler-Smea-Dyn-Sampler/README.md b/extensions/Euler-Smea-Dyn-Sampler/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4eb8125dc017a37ef54bf1c537e8c7bdb7c2761c --- /dev/null +++ b/extensions/Euler-Smea-Dyn-Sampler/README.md @@ -0,0 +1,271 @@ +| Catalog | +| ---- | +| [2024.05.09 Add Euler Negative And Euler dy Negtive 添加两个新采样器](#section1) | +| [2024.04.24](#section2) | +| [2024.04.18 Stage Technical Report 阶段性技术报告](#section3) | +| [2024.04.15 Compatible with Stable Cascade Models 适配Sc模型](#section4) | +| [2024.04.13 Fix Bug In ComfyUI 修复ComfyUI中的Bug](#section5) | +| [2024.04.11 Important! This repo can be use as a extension! 重大!现在此仓库可作为插件使用](#section6) | +| [2024.04.10](#section7) | +| [2024.04.09](#section8) | +| [Euler Smea Dyn Sampler](#section9) | +| [Effect 效果](#section10) | +| [how to use(This has become outdated, but it will be retained) 如何使用(已经过期,依然保留,仅做参考)](#section11) | +| [The technical principles 技术报告](#section12) | +| [Contact the author 联系作者](#section13) | + + + +## 2024.05.09 Add Euler Negative And Euler dy Negtive + +新增两个采样器,Euler Negative 和 Euler dy Negtive。我不会说它们效果比别的好,因为没有理论依据。不过在实践中我很喜欢它们。 + +在SDXL表现更好一些,但在SD1.5使用效果也不差 + +我得去稍微进修一下关于AI的知识,目前这种纯粹依靠灵感和实践的方案过于自由。 + +以下是关于它们的测试: + +Two new samplers have been added, Euler Negative and Euler dy Negative. I won't claim they perform better than others because there's no theoretical basis for it. However, in practice, I quite like them. + +They perform slightly better in SDXL, but their performance in SD1.5 is also decent. + +I need to brush up on my professional knowledge of AI. Currently, relying solely on intuition and practice feels too unrestricted. + +Below are the test results for them: + +**768x768, model meinaMixV11** +![xyz_grid-0005-1234-1girl,heart hands,river,cherry blossoms,hair flower,hair ribbon,cat ears,animal ear fluff,blue eyes,grey hair,short hair,bangs,h](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/04131413-0173-4afc-91dc-d5ac42d0560c) + +**832x1216,model kohaku-xl-epsilon** +![xyz_grid-0007-4286407380-1girl,_(midori _(blue archive_)_1 1),blue archive,_ciloranko,lobelia _(saclia_),(konya karasue_0 9),wanke,(jiu ye sang_1 1),(rum](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/c237d930-cb00-4142-9b46-faeb59c4faba) +![xyz_grid-0008-3096426698-1girl,_mayano top gun _(umamusume_),umamusume,_shiro9jira,ciloranko,ask _(askzy_),(tianliang duohe fangdongye_0 8),_(solo_1 2),(](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/6d47de71-ceca-4091-8dac-3c59bb1aee1e) +![xyz_grid-0016-789654357-1girl,_gotoh hitori,bocchi the rock!,_ciloranko,maccha _(mochancc_),lobelia _(saclia_),migolu,ask _(askzy_),wanke,(jiu ye sang_1](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/f46be012-2b07-4408-9449-b5a20769ebb4) + +**832x1216,model animegineV30** +![xyz_grid-0014-3532334-1girl,_vivlos _(umamusume_),umamusume,_ciloranko,maccha _(mochancc_),lobelia _(saclia_),migolu,ask _(askzy_),wanke,(jiu ye sang_](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/92bd48bf-fe30-45b6-9014-5f98bd3622d7) + +**Please Note** + +目前插件有一些小bug,会使得人物在画面中的占比变小,就像这样: + +The current plugin has a few minor bugs that cause the characters to shrink in the frame, like this: + +![xyz_grid-0011-3096426698-1girl,_mayano top gun _(umamusume_),umamusume,_shiro9jira,ciloranko,ask _(askzy_),(tianliang duohe fangdongye_0 8),_(solo_1 2),(](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/41d067ec-7e49-413c-bc4a-9e8e8b173140) +![xyz_grid-0013-3096426698-1girl,_mayano top gun _(umamusume_),umamusume,_shiro9jira,ciloranko,ask _(askzy_),(tianliang duohe fangdongye_0 8),_(solo_1 2),(](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/80c1481a-d5a6-40c1-96a6-665f215c7b39) + +所以你可以采用其他方案,例如修改源码的方案去添加这两个采样器,请参考:[How to use](#section11) + +So, you can consider alternative solutions, such as modifying the source code to add these two samplers. Please refer to:[How to use](#section11) + + +## 2024.04.24 + +简单分析Dy Step的原理 + +最近我尝试在https://civitai.com/models/399873/kohaku-xl-epsilon上测试了Euler Dy,效果不尽人意(但在ang3以及pony系列效果不错)。因此我咨询了作者,得到的回复是“模型没有使用任何低分辨率的图片进行训练”。我想这就是原因所在,Euler Dy将图片放在一个小的尺度上,让去噪工作来到模型的舒适区,并给予一个参考。尤其在SD1.5,Euler Dy确保图像始终处于模型的舒适区域。 + +而在本次的SDXL模型中,它几乎遗忘了如何在小尺度上生成图片。所以,Dy Step的改进方向已经变得很明显:寻找到SDXL模型的舒适区域,并让采样器在SDXL的舒适区工作。 + +同时我也写了几个其他的采样器,效果平平,达不到Dy Step的效果。如果有人想试试它们,请在评论区留言。 + +Recently, I attempted to analyze the principle of Dy Step on https://civitai.com/models/399873/kohaku-xl-epsilon using Euler Dy. The results were unsatisfactory (although they performed well on ang3 and pony series). Therefore, I consulted the author and received the response that "the model did not use any low-resolution images for training." I believe this is the reason why. Euler Dy places images on a small scale, allowing denoising to operate within the model's comfort zone and providing a reference. Especially in SD1.5, Euler Dy ensures that the image always remains within the model's comfort zone. + +However, in the current SDXL model, it has almost forgotten how to generate images on a small scale. Therefore, the direction for improving Dy Step has become apparent: to find the comfort zone of the SDXL model and enable the sampler to work within the comfort zone of SDXL. + +I've also written a few other samplers, but their performance is mediocre and doesn't match up to Dy Step's effectiveness. If anyone wants to try them out, please leave a comment in the discussions. + + +## 2024.04.18 Stage Technical Report + +阶段性技术报告报告。 + +这些日子里,我尝试了超过二十种策略,但采样器的质量总是优于euler a却差于euler dy,所以暂时还不能发布euler dy a。我必须承认这和nai3的dyn是不同的东西。我依旧会长期维护这个项目,并为了新的采样方法努力,同时尽可能降低ai的算力需求。 + +Stage Technical Report + +In these days, I have attempted over twenty strategies, but the quality of the sampler is always better than Euler A yet worse than Euler DY. Therefore, I cannot release Euler DY A for the time being. I must acknowledge that this is different from NAI3's DYN. I will continue to maintain this project in the long term and work towards developing new sampling methods while trying to minimize the AI's computing power requirements as much as possible. + +![xyz_grid-0036-1234-1girl,heart hands,river,cherry blossoms,hair flower,hair ribbon,cat ears,animal ear fluff,blue eyes,grey hair,short hair,bangs,h](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/32618333-0228-472e-bfbe-6c1a2e84ae96) + + + +## 2024.04.15 Compatible with Stable Cascade Models + +Makes dy_step respect original channel count, making it compatible with Stable Cascade models. + +使dy_step遵循原本的通道数,使其与Stable Cascade模型相适应。 + + +## 2024.04.13 Fix Bug In ComfyUI + +Change code for ComfyUI import. This will fix the overwrite error that occurs in ComfyUI when other extensions use `scripts` as the import folder (I really hope ComfyUI will standardize its interfaces and version dependencies). + +P.S.You may find some commits with no means, that because I am not familiar with Github, and try times. So don't care. + +更改代码,用于ComfyUI导入。这将修复在ComfyUI中存在其他插件时,若其他插件将`scripts`作为导入文件夹时引起的覆盖错误。(真希望ComfyUI能规范一下它的接口和版本依赖)。 + +P.S.你可能会发现一些无意义的提交,这是因为我不熟悉Github的使用,并且尝试了几次。别在意。 + + +## 2024.04.11 Important! This repo can be use as a extension! + +Thanks for @pamparamm, his selfless work has been a great help. + +Now this sampler can be use as a extension for **ComfyUI** and **WebUI from Automatic1111**. + +The inpainting bug will be fixed.(**At least doesn't throw any exceptions.**) + +Thanks again. + +Another extension from @licyk , in repo: https://github.com/licyk/advanced_euler_sampler_extension **suitable for 1.8 version** + +It's also useful, and thanks hard efforts from licky, too. + +In the future, I will work on making dy step compatible with more samplers (such as the DPM series). + +感谢 @pamparamm,他的无私工作帮助很大。 + +现在,这个采样器可以作为 **ComfyUI** 和 **Automatic1111 的 WebUI** 的扩展来使用。 + +修复了inpainting的bug。(**至少不再抛出异常。**) + +再次感谢。 + +另一个拓展来自@licyk,位于: https://github.com/licyk/advanced_euler_sampler_extension **适用于1.8** + +也同样很好用, 同样感谢licyk的辛勤努力。 + +之后我会想办法让dy step适配更多采样器(例如dpm系列)。 + + +## 2024.04.10 + +![image](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/aa3dd88a-5760-4589-857c-5717a3253ea0) + +Find a way to avoid errors during inpaint and extensions. + +**Please note that this is just a temporary solution and doesn't actually resolve the issue.It will try to use Euler method if error occurs.** + +P.S.I trying to fix it……but all methods seems doesn't work.I've working for it over 36 hours. + +**Suggestions from anyone are welcome**. + +I need to take a short break and prepare for my other project.*<== A mobile phone app base on flutter, using for TRPG.(No worries, I don't mean I will give up this project,also not about diverting traffic either. LOL.)* + +想了个办法避免在局部重绘以及拓展中的报错。 + +**请注意,这只是一个临时解决方案,实际上并没有解决问题。如果出现错误,它将尝试使用欧拉方法。** + +我努力尝试修复……但所有方案都不起作用。我已经连续工作了36小时以上。**欢迎任何人提出建议。** +, +我需要稍微休息一下,并且为我的其他项目做准备。*<== 一个基于Flutter的手机应用,用于TRPG。(别担心,我不是说我要放弃这个项目,也不是引流。)* + + +## 2024.04.09 + +Add `__init__.py` for ComfyUI. Thanks for CapsAdmin. I don't use ComfyUI so I can't tell you how to add it, sorry. + +为ComfyUI增加`__init__.py` 感谢CapsAdmin 我不用ComfyUI所以我没法告诉你怎么添加它,抱歉 + + +## Euler Smea Dyn Sampler + +A sampling method based on Euler's approach, designed to generate superior imagery. + +The SMEA sampler can significantly mitigate the structural and limb collapse that occurs when generating large images, and to a great extent, it can produce superior hand depictions (not perfect, but better than existing sampling methods). + +The SMEA sampler is designed to accommodate the majority of image sizes, with particularly outstanding performance on larger images. It also supports the generation of images in unconventional sizes that lack sufficient training data (for example, running 512x512 in SDXL, 823x1216 in SD1.5, as well as 640x960, etc.). + +The SMEA sampler performs very well in SD1.5, but the effects are not as pronounced in SDXL. + +In terms of computational resource consumption, the Euler dy is approximately equivalent to the Euler a, while the Euler SMEA Dy sampler will consume more computational resources, approximately 1.25 times more. + +一种基于Euler的采样方法,旨在生成更好的图片 + +Dyn采样器可以很大程度上避免出大图时的结构、肢体崩坏,能很大程度得到更优秀的手部(不完美但比已有采样方法更好) + +Smea采样器理论上将增加图片的细节(**无法达到Nai3让图片闪闪发光的效果**) + +适配绝大多数图片尺寸,在大图的效果尤其优秀,支持缺乏训练的异种尺寸(例如在sdxl跑512x512,在sd1.5跑823x1216,以及640x960等) + +在SD1.5效果很好,在SDXL效果不明显。 + +计算资源消耗:Euler dy将约等于euler a, 而euler smea dy将消耗更多计算资源(约1.25倍) + + +## Effect +**SD1.5,测试模型AnythingV5-Prt-RE,测试姿势Heart Hand,一个容易出坏手的姿势** + +**SD1.5: Testing the AnythingV5-Prt-RE model with the Heart Hand pose often results in distorted hand positions.** + +768x768,without Lora: +![xyz_grid-0049-1234-masterpiece,best quality,highres,1girl,heart hands,river,cherry blossoms,hair flower,hair ribbon,cat ears,animal ear fluff,blue](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/c13dcf8b-b9da-4624-9a04-a1488e850647) +768x768,with Lora: +![xyz_grid-0048-1234-_lora_manhattan_cafe_loha-000008_0 75_,manhattan cafe _(umamusume_),black choker,long sleeves,collared shirt,yellow necktie,blac](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/a60d7395-9ebd-4505-9ad7-3f0de0944bb8) +832x1216,without lora: +![xyz_grid-0046-1234-1girl,heart hands,river,cherry blossoms,hair flower,hair ribbon,cat ears,animal ear fluff,blue eyes,grey hair,short hair,bangs,h](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/9d6024f1-e9fa-42a0-841b-5a5b18089fd8) +832x1216,with Lora: +![xyz_grid-0047-1234-_lora_manhattan_cafe_loha-000008_0 75_,manhattan cafe _(umamusume_),black choker,long sleeves,collared shirt,yellow necktie,blac](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/3432dafb-4ebb-43ae-8c62-a4c4c168ce63) + +**SDXL,测试模型animagineXLV31,测试姿势也是手部姿势** + +**SDXL: Testing animagineXLV31 model with hand poses.** + +768x768: +![xyz_grid-0019-114-1girl,manhattan cafe _(umamusume_),umamusume,heart hands,masterpiece,best quality,highres,](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/6883ace1-3712-4cd1-b974-4f0d1ed41bc2) +832x1216: +![xyz_grid-0018-114514-1girl,manhattan cafe _(umamusume_),umamusume,heart hands,masterpiece,best quality,highres,](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/9424ef70-a54f-454c-bf03-e543562367fc) +![xyz_grid-0019-114515-1girl,manhattan cafe _(umamusume_),umamusume,finger_on_trigger,upper body,masterpiece,best quality,highres,](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/2f9d1a68-3a28-47e5-a1a8-ce14e2ad4563) + + +## how to use(This has become outdated, but it will be retained) + +**step.1:** 打开`sd-webui-aki-v4.6\repositories\k-diffusion\k_diffusion`文件夹,打开其中的`sampling.py`文件(可以用记事本打开,称为文件1) + +**Step 1:** Navigate to the `k_diffusion` folder within the `sd-webui-aki-v4.6\repositories\k-diffusion` directory and open the `sampling.py` file within it (this can be done using a text editor like Notepad, which will be referred to as File 1). +![QQ截图20240408193751](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/482d9647-f6d7-458e-a680-3c5a0c5fad9c) + +**step.2:** 复制本仓库中的`sampling.py`中的所有内容并粘贴到文件1末尾 + +**Step 2:** Copy the entire content from the `sampling.py` file in the current repository and paste it at the end of File 1. +![image](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/2b48db77-2d44-410e-afe7-81f04ede34ce) +(To present the complete picture, I have utilized PyTorch's abbreviation feature.) + +**Step 3:** Open the `sd_samplers_kdiffusion.py` file located in the `sd-webui-aki-v4.6\modules` directory (refer to this as File 2). +![image](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/e014a51f-7d8a-49ee-ba3d-ec5d338f51d1) + +**Step 4:** Copy the following two lines from this repository: +![QQ截图20240408192923](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/440f522a-1acb-4d78-bebe-b75c7b969adb) + +Paste them into File 2: +![image](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/782722bf-713c-41dc-a7ec-669419423ae5) + +**Step 5:** Restart the webui, and you will see: +![image](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler/assets/66173435/15abbf9e-4d4d-4325-9223-1506f08f25cc) + +现在你就可以使用它们了。在图生图中可能有一些bug,欢迎向我汇报(请带上截图/报错声明) + +Now you can start using them. There may be some bugs in the image generation process, and I welcome you to report any issues to me (please provide screenshots or error statements). + + +## The technical principles + +简单地讲,dyn方法有规律地取出图片中的一部分,去噪后加回原图。在理论上这应当等同于euler a,但其加噪环节被替代为有引导的噪声。 + +而smea方法将图片潜空间放大再压缩回原本的大小,这增加了图片的可能性。很抱歉我没能实现Nai3中smea让图片微微发光的效果。 + +一点忠告:不要相信pytorch的插值放大和缩小方法,不会对改善图像带来任何帮助。同时用有条件引导取代随机噪声也是有希望的道路。 + +In simple terms, the dyn method regularly extracts a portion of the image, denoises it, and then adds it back to the original image. Theoretically, this should be equivalent to the Euler A method, but its noise addition step is replaced with guided noise. + +The SMEA method enlarges the image's latent space and then compresses it back to its original dimensions, thereby increasing the range of possible image variations. I apologize that I was unable to achieve the subtle glowing effect in Nai3 with the SMEA method. + +A piece of advice: Do not trust PyTorch's interpolation methods for enlarging and shrinking images; they will not contribute to improving image quality. Additionally, replacing random noise with conditional guidance is also a promising path forward. + + +## Contact the author + +Email:872324454@qq.com + +Bilibili:星河主炮发射 diff --git a/extensions/Euler-Smea-Dyn-Sampler/__init__.py b/extensions/Euler-Smea-Dyn-Sampler/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f9fa8440679fe0ef2a5df56d32f1bd8d1cd943ba --- /dev/null +++ b/extensions/Euler-Smea-Dyn-Sampler/__init__.py @@ -0,0 +1,21 @@ +from . import smea_sampling +from .smea_sampling import sample_euler_dy, sample_euler_smea_dy, sample_euler_negative, sample_euler_dy_negative + +if smea_sampling.BACKEND == "ComfyUI": + if not smea_sampling.INITIALIZED: + from comfy.k_diffusion import sampling as k_diffusion_sampling + from comfy.samplers import SAMPLER_NAMES + + setattr(k_diffusion_sampling, "sample_euler_dy", sample_euler_dy) + setattr(k_diffusion_sampling, "sample_euler_smea_dy", sample_euler_smea_dy) + setattr(k_diffusion_sampling, "sample_euler_negative", sample_euler_negative) + setattr(k_diffusion_sampling, "sample_euler_dy_negative", sample_euler_dy_negative) + + SAMPLER_NAMES.append("euler_dy") + SAMPLER_NAMES.append("euler_smea_dy") + SAMPLER_NAMES.append("euler_negative") + SAMPLER_NAMES.append("euler_dy_negative") + + smea_sampling.INITIALIZED = True + +NODE_CLASS_MAPPINGS = {} diff --git a/extensions/Euler-Smea-Dyn-Sampler/__pycache__/smea_sampling.cpython-310.pyc b/extensions/Euler-Smea-Dyn-Sampler/__pycache__/smea_sampling.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48f96110a8b555fc164a4dce7ee4e6d8d8962165 Binary files /dev/null and b/extensions/Euler-Smea-Dyn-Sampler/__pycache__/smea_sampling.cpython-310.pyc differ diff --git a/extensions/Euler-Smea-Dyn-Sampler/pyproject.toml b/extensions/Euler-Smea-Dyn-Sampler/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..b2630cd72c579b001d4b8aafffc46ba86d4ed8a5 --- /dev/null +++ b/extensions/Euler-Smea-Dyn-Sampler/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "euler-smea-dyn-sampler" +description = "СomfyUI version of [a/Euler Smea Dyn Sampler](https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler). It adds samplers directly to KSampler nodes." +version = "1.0.0" +license = "LICENSE" + +[project.urls] +Repository = "https://github.com/Koishi-Star/Euler-Smea-Dyn-Sampler" +# Used by Comfy Registry https://comfyregistry.org + +[tool.comfy] +PublisherId = "" +DisplayName = "Euler-Smea-Dyn-Sampler" +Icon = "" diff --git a/extensions/Euler-Smea-Dyn-Sampler/scripts/__pycache__/smea.cpython-310.pyc b/extensions/Euler-Smea-Dyn-Sampler/scripts/__pycache__/smea.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..daacc36c4aca978b3f79e1541156e7a03ac9767b Binary files /dev/null and b/extensions/Euler-Smea-Dyn-Sampler/scripts/__pycache__/smea.cpython-310.pyc differ diff --git a/extensions/Euler-Smea-Dyn-Sampler/scripts/smea.py b/extensions/Euler-Smea-Dyn-Sampler/scripts/smea.py new file mode 100644 index 0000000000000000000000000000000000000000..82524651be673f3939caf7f40fe1f012b0a17483 --- /dev/null +++ b/extensions/Euler-Smea-Dyn-Sampler/scripts/smea.py @@ -0,0 +1,39 @@ +try: + import smea_sampling + from smea_sampling import sample_euler_dy, sample_euler_smea_dy, sample_euler_negative, sample_euler_dy_negative + + if smea_sampling.BACKEND == "WebUI": + from modules import scripts, sd_samplers_common, sd_samplers + from modules.sd_samplers_kdiffusion import sampler_extra_params, KDiffusionSampler + + class SMEA(scripts.Script): + def title(self): + "SMEA Samplers" + + def show(self, is_img2img): + return False + + def __init__(self): + if not smea_sampling.INITIALIZED: + samplers_smea = [ + ("Euler Dy", sample_euler_dy, ["k_euler_dy"], {}), + ("Euler SMEA Dy", sample_euler_smea_dy, ["k_euler_smea_dy"], {}), + ("Euler Negative", sample_euler_negative, ["k_euler_negative"], {}), + ("Euler Negative Dy", sample_euler_dy_negative, ["k_euler_negative_dy"], {}), + ] + samplers_data_smea = [ + sd_samplers_common.SamplerData(label, lambda model, funcname=funcname: KDiffusionSampler(funcname, model), aliases, options) + for label, funcname, aliases, options in samplers_smea + if callable(funcname) + ] + sampler_extra_params["sample_euler_dy"] = ["s_churn", "s_tmin", "s_tmax", "s_noise"] + sampler_extra_params["sample_euler_smea_dy"] = ["s_churn", "s_tmin", "s_tmax", "s_noise"] + sampler_extra_params["sample_euler_negative"] = ["s_churn", "s_tmin", "s_tmax", "s_noise"] + sampler_extra_params["sample_euler_dy_negative"] = ["s_churn", "s_tmin", "s_tmax", "s_noise"] + sd_samplers.all_samplers.extend(samplers_data_smea) + sd_samplers.all_samplers_map = {x.name: x for x in sd_samplers.all_samplers} + sd_samplers.set_samplers() + smea_sampling.INITIALIZED = True + +except ImportError as _: + pass diff --git a/extensions/Euler-Smea-Dyn-Sampler/smea_sampling.py b/extensions/Euler-Smea-Dyn-Sampler/smea_sampling.py new file mode 100644 index 0000000000000000000000000000000000000000..d18bd443b20a59173aed1149c936a3518b86e504 --- /dev/null +++ b/extensions/Euler-Smea-Dyn-Sampler/smea_sampling.py @@ -0,0 +1,220 @@ +from importlib import import_module +from tqdm.auto import trange +import torch + +sampling = None +BACKEND = None +INITIALIZED = False + +if not BACKEND: + try: + _ = import_module("modules.sd_samplers_kdiffusion") + sampling = import_module("k_diffusion.sampling") + BACKEND = "WebUI" + except ImportError as _: + pass + +if not BACKEND: + try: + sampling = import_module("comfy.k_diffusion.sampling") + BACKEND = "ComfyUI" + except ImportError as _: + pass + + +class _Rescaler: + def __init__(self, model, x, mode, **extra_args): + self.model = model + self.x = x + self.mode = mode + self.extra_args = extra_args + if BACKEND == "WebUI": + self.init_latent, self.mask, self.nmask = model.init_latent, model.mask, model.nmask + if BACKEND == "ComfyUI": + self.latent_image, self.noise = model.latent_image, model.noise + self.denoise_mask = self.extra_args.get("denoise_mask", None) + + def __enter__(self): + if BACKEND == "WebUI": + if self.init_latent is not None: + self.model.init_latent = torch.nn.functional.interpolate(input=self.init_latent, size=self.x.shape[2:4], mode=self.mode) + if self.mask is not None: + self.model.mask = torch.nn.functional.interpolate(input=self.mask.unsqueeze(0), size=self.x.shape[2:4], mode=self.mode).squeeze(0) + if self.nmask is not None: + self.model.nmask = torch.nn.functional.interpolate(input=self.nmask.unsqueeze(0), size=self.x.shape[2:4], mode=self.mode).squeeze(0) + if BACKEND == "ComfyUI": + if self.latent_image is not None: + self.model.latent_image = torch.nn.functional.interpolate(input=self.latent_image, size=self.x.shape[2:4], mode=self.mode) + if self.noise is not None: + self.model.noise = torch.nn.functional.interpolate(input=self.latent_image, size=self.x.shape[2:4], mode=self.mode) + if self.denoise_mask is not None: + self.extra_args["denoise_mask"] = torch.nn.functional.interpolate(input=self.denoise_mask, size=self.x.shape[2:4], mode=self.mode) + + return self + + def __exit__(self, type, value, traceback): + if BACKEND == "WebUI": + del self.model.init_latent, self.model.mask, self.model.nmask + self.model.init_latent, self.model.mask, self.model.nmask = self.init_latent, self.mask, self.nmask + if BACKEND == "ComfyUI": + del self.model.latent_image, self.model.noise + self.model.latent_image, self.model.noise = self.latent_image, self.noise + + +@torch.no_grad() +def dy_sampling_step(x, model, dt, sigma_hat, **extra_args): + original_shape = x.shape + batch_size, channels, m, n = original_shape[0], original_shape[1], original_shape[2] // 2, original_shape[3] // 2 + extra_row = x.shape[2] % 2 == 1 + extra_col = x.shape[3] % 2 == 1 + + if extra_row: + extra_row_content = x[:, :, -1:, :] + x = x[:, :, :-1, :] + if extra_col: + extra_col_content = x[:, :, :, -1:] + x = x[:, :, :, :-1] + + a_list = x.unfold(2, 2, 2).unfold(3, 2, 2).contiguous().view(batch_size, channels, m * n, 2, 2) + c = a_list[:, :, :, 1, 1].view(batch_size, channels, m, n) + + with _Rescaler(model, c, 'nearest-exact', **extra_args) as rescaler: + denoised = model(c, sigma_hat * c.new_ones([c.shape[0]]), **rescaler.extra_args) + d = sampling.to_d(c, sigma_hat, denoised) + c = c + d * dt + + d_list = c.view(batch_size, channels, m * n, 1, 1) + a_list[:, :, :, 1, 1] = d_list[:, :, :, 0, 0] + x = a_list.view(batch_size, channels, m, n, 2, 2).permute(0, 1, 2, 4, 3, 5).reshape(batch_size, channels, 2 * m, 2 * n) + + if extra_row or extra_col: + x_expanded = torch.zeros(original_shape, dtype=x.dtype, device=x.device) + x_expanded[:, :, :2 * m, :2 * n] = x + if extra_row: + x_expanded[:, :, -1:, :2 * n + 1] = extra_row_content + if extra_col: + x_expanded[:, :, :2 * m, -1:] = extra_col_content + if extra_row and extra_col: + x_expanded[:, :, -1:, -1:] = extra_col_content[:, :, -1:, :] + x = x_expanded + + return x + + +@torch.no_grad() +def sample_euler_dy(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0., + s_tmax=float('inf'), s_noise=1.): + extra_args = {} if extra_args is None else extra_args + s_in = x.new_ones([x.shape[0]]) + for i in trange(len(sigmas) - 1, disable=disable): + # print(i) + # i第一步为0 + gamma = max(s_churn / (len(sigmas) - 1), 2 ** 0.5 - 1) if s_tmin <= sigmas[i] <= s_tmax else 0. + eps = torch.randn_like(x) * s_noise + sigma_hat = sigmas[i] * (gamma + 1) + # print(sigma_hat) + dt = sigmas[i + 1] - sigma_hat + if gamma > 0: + x = x - eps * (sigma_hat ** 2 - sigmas[i] ** 2) ** 0.5 + denoised = model(x, sigma_hat * s_in, **extra_args) + d = sampling.to_d(x, sigma_hat, denoised) + if sigmas[i + 1] > 0: + if i // 2 == 1: + x = dy_sampling_step(x, model, dt, sigma_hat, **extra_args) + if callback is not None: + callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised}) + # Euler method + x = x + d * dt + return x + + +@torch.no_grad() +def smea_sampling_step(x, model, dt, sigma_hat, **extra_args): + m, n = x.shape[2], x.shape[3] + x = torch.nn.functional.interpolate(input=x, scale_factor=(1.25, 1.25), mode='nearest-exact') + with _Rescaler(model, x, 'nearest-exact', **extra_args) as rescaler: + denoised = model(x, sigma_hat * x.new_ones([x.shape[0]]), **rescaler.extra_args) + d = sampling.to_d(x, sigma_hat, denoised) + x = x + d * dt + x = torch.nn.functional.interpolate(input=x, size=(m,n), mode='nearest-exact') + return x + + +@torch.no_grad() +def sample_euler_smea_dy(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0., + s_tmax=float('inf'), s_noise=1.): + extra_args = {} if extra_args is None else extra_args + s_in = x.new_ones([x.shape[0]]) + for i in trange(len(sigmas) - 1, disable=disable): + gamma = max(s_churn / (len(sigmas) - 1), 2 ** 0.5 - 1) if s_tmin <= sigmas[i] <= s_tmax else 0. + eps = torch.randn_like(x) * s_noise + sigma_hat = sigmas[i] * (gamma + 1) + dt = sigmas[i + 1] - sigma_hat + if gamma > 0: + x = x - eps * (sigma_hat ** 2 - sigmas[i] ** 2) ** 0.5 + denoised = model(x, sigma_hat * s_in, **extra_args) + d = sampling.to_d(x, sigma_hat, denoised) + # Euler method + x = x + d * dt + if sigmas[i + 1] > 0: + if i + 1 // 2 == 1: + x = dy_sampling_step(x, model, dt, sigma_hat, **extra_args) + if i + 1 // 2 == 0: + x = smea_sampling_step(x, model, dt, sigma_hat, **extra_args) + if callback is not None: + callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised}) + return x + +@torch.no_grad() +def sample_euler_negative(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0., + s_tmax=float('inf'), s_noise=1.): + extra_args = {} if extra_args is None else extra_args + s_in = x.new_ones([x.shape[0]]) + for i in trange(len(sigmas) - 1, disable=disable): + # print(i) + # i第一步为0 + gamma = max(s_churn / (len(sigmas) - 1), 2 ** 0.5 - 1) if s_tmin <= sigmas[i] <= s_tmax else 0. + eps = torch.randn_like(x) * s_noise + sigma_hat = sigmas[i] * (gamma + 1) + # print(sigma_hat) + dt = sigmas[i + 1] - sigma_hat + if gamma > 0: + x = x - eps * (sigma_hat ** 2 - sigmas[i] ** 2) ** 0.5 + denoised = model(x, sigma_hat * s_in, **extra_args) + d = sampling.to_d(x, sigma_hat, denoised) + if callback is not None: + callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised}) + # Euler method + if sigmas[i + 1] > 0 and i // 2 == 1: + x = - x - d * dt + else: + x = x + d * dt + return x + + +@torch.no_grad() +def sample_euler_dy_negative(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0., + s_tmax=float('inf'), s_noise=1.): + extra_args = {} if extra_args is None else extra_args + s_in = x.new_ones([x.shape[0]]) + for i in trange(len(sigmas) - 1, disable=disable): + # print(i) + # i第一步为0 + gamma = max(s_churn / (len(sigmas) - 1), 2 ** 0.5 - 1) if s_tmin <= sigmas[i] <= s_tmax else 0. + eps = torch.randn_like(x) * s_noise + sigma_hat = sigmas[i] * (gamma + 1) + # print(sigma_hat) + dt = sigmas[i + 1] - sigma_hat + if gamma > 0: + x = x - eps * (sigma_hat ** 2 - sigmas[i] ** 2) ** 0.5 + denoised = model(x, sigma_hat * s_in, **extra_args) + d = sampling.to_d(x, sigma_hat, denoised) + if callback is not None: + callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised}) + # Euler method + if sigmas[i + 1] > 0 and i // 2 == 1: + x = dy_sampling_step(x, model, dt, sigma_hat, **extra_args) + x = - x - d * dt + else: + x = x + d * dt + return x diff --git a/extensions/SD-latent-mirroring/.gitignore b/extensions/SD-latent-mirroring/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c18dd8d83ceed1806b50b0aaa46beb7e335fff13 --- /dev/null +++ b/extensions/SD-latent-mirroring/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/extensions/SD-latent-mirroring/README.md b/extensions/SD-latent-mirroring/README.md new file mode 100644 index 0000000000000000000000000000000000000000..157aba9d6704c5bf58e321c3d7e27dc453ba6dda --- /dev/null +++ b/extensions/SD-latent-mirroring/README.md @@ -0,0 +1,42 @@ +# SD-latent-mirroring +Applies mirroring and flips to the latent images mid-generation to produce anything from subtle balanced compositions to perfect reflections + +## UI + +![image](https://user-images.githubusercontent.com/35278260/201234705-1e8f6b36-29b0-4c5c-8773-4ee6734c1748.png) +- Mirror Application Mode + - **None** - Do not mirror + - **Alternate Steps** - flip or rotate the latents on each step + - **Blend Average** - take the average of the original latents and their flipped or rotated version +- Mirror Style + - **Vertical Mirroring** - Mirror vertically flipping left for right. + - **Horizontal Mirroring** - Mirror horizontally flipping up for down. + - **Horizontal+Vertical Mirroring** - flip alternately horizontally and vertically. + - **90 Degree Rotation** - Rotate 90 degrees clockwise. + - **180 Degree Rotation** - Rotate 180 degrees + - **Roll Channels** - Sequentially switch the 'channels' of the latent image for colour variations. + - **None** - No mirroring. +- **X/Y Panning** - shift the latents in the specfied direction by this percentage of the total size every step. +- **Maximum steps fraction to mirror at** - a decimal percentage representing the maximum step to apply the mirroring on, 0.5 = 50%, stopping at the 10th step out of 20 when 20 steps are used. + +## Outputs + +Vertical Mirroring: + +![image](https://user-images.githubusercontent.com/35278260/199627861-07b2c1a6-0271-4505-814d-01ad31a68f79.png) + +Horizontal Mirroring: + +![image](https://user-images.githubusercontent.com/35278260/199627881-6f62a227-3a6c-4470-9c18-2ed8bc57194c.png) + +90 Degree Rotation: + +![image](https://user-images.githubusercontent.com/35278260/199627897-bdef0e03-3230-4b1d-ba21-0e2f15bf14e7.png) + +180 Degree Rotation: + +![image](https://user-images.githubusercontent.com/35278260/199627888-8b778a8a-d053-456f-8651-323b01126d87.png) + +Higher `Maximum steps fraction to mirror at` values producer stronger symetries: + +![image](https://user-images.githubusercontent.com/35278260/199627949-0529921f-8c82-4d01-b3cb-23b91d68bc9c.png) diff --git a/extensions/SD-latent-mirroring/scripts/__pycache__/latent_mirroring.cpython-310.pyc b/extensions/SD-latent-mirroring/scripts/__pycache__/latent_mirroring.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ddbfac1ad4457de2b25ce076c96a7ce34b8fd43a Binary files /dev/null and b/extensions/SD-latent-mirroring/scripts/__pycache__/latent_mirroring.cpython-310.pyc differ diff --git a/extensions/SD-latent-mirroring/scripts/latent_mirroring.py b/extensions/SD-latent-mirroring/scripts/latent_mirroring.py new file mode 100644 index 0000000000000000000000000000000000000000..abf9b4b8cab45cc12314d0b442b4a8ccb29cca2d --- /dev/null +++ b/extensions/SD-latent-mirroring/scripts/latent_mirroring.py @@ -0,0 +1,115 @@ +import torch +import modules.scripts as scripts +import gradio as gr +from modules.script_callbacks import on_cfg_denoiser +from modules import processing +from torchvision import transforms + +class Script(scripts.Script): + + def title(self): + return "Latent Mirroring extension" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with gr.Group(): + with gr.Accordion("Latent Mirroring", open=False): + mirror_mode = gr.Radio(label='Latent Mirror mode', choices=['None', 'Alternate Steps', 'Blend Average'], value='None', type="index") + mirror_style = gr.Radio(label='Latent Mirror style', choices=['Horizontal Mirroring', 'Vertical Mirroring', 'Horizontal+Vertical Mirroring', '90 Degree Rotation', '180 Degree Rotation', 'Roll Channels', 'None'], value='Horizontal Mirroring', type="index") + + with gr.Row(): + x_pan = gr.Slider(minimum=-1.0, maximum=1.0, step=0.01, label='X panning', value=0.0) + y_pan = gr.Slider(minimum=-1.0, maximum=1.0, step=0.01, label='Y panning', value=0.0) + + mirroring_max_step_fraction = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Maximum steps fraction to mirror at', value=0.25) + + if not is_img2img: + disable_hr = gr.Checkbox(label='Disable during hires pass', value=False) + else: + disable_hr = gr.State(False) + + self.run_callback = False + return [mirror_mode, mirror_style, x_pan, y_pan, mirroring_max_step_fraction, disable_hr] + + def denoise_callback(self, params): + is_hires = self.is_hires + + # indices start at -1 + # params.sampling_step = max(0, real_sampling_step) + if params.sampling_step >= params.total_sampling_steps - 2: + self.is_hires = not is_hires and self.enable_hr + + if not self.run_callback or is_hires: + return + + if params.sampling_step >= params.total_sampling_steps * self.mirroring_max_step_fraction: + return + + try: + if self.mirror_mode == 1: + if self.mirror_style == 0: + params.x[:, :, :, :] = torch.flip(params.x, [3]) + elif self.mirror_style == 1: + params.x[:, :, :, :] = torch.flip(params.x, [2]) + elif self.mirror_style == 2: + params.x[:, :, :, :] = torch.flip(params.x, [3, 2]) + elif self.mirror_style == 3: + params.x[:, :, :, :] = torch.rot90(params.x, dims=[2, 3]) + elif self.mirror_style == 4: + params.x[:, :, :, :] = torch.rot90(torch.rot90(params.x, dims=[2, 3]), dims=[2, 3]) + elif self.mirror_style == 5: + params.x[:, :, :, :] = torch.roll(params.x, shifts=1, dims=[1]) + + elif self.mirror_mode == 2: + if self.mirror_style == 0: + params.x[:, :, :, :] = (torch.flip(params.x, [3]) + params.x)/2 + elif self.mirror_style == 1: + params.x[:, :, :, :] = (torch.flip(params.x, [2]) + params.x)/2 + elif self.mirror_style == 2: + params.x[:, :, :, :] = (torch.flip(params.x, [2, 3]) + params.x)/2 + elif self.mirror_style == 3: + params.x[:, :, :, :] = (torch.rot90(params.x, dims=[2, 3]) + params.x)/2 + elif self.mirror_style == 4: + params.x[:, :, :, :] = (torch.rot90(torch.rot90(params.x, dims=[2, 3]), dims=[2, 3]) + params.x)/2 + elif self.mirror_style == 5: + params.x[:, :, :, :] = (torch.roll(params.x, shifts=1, dims=[1]) + params.x)/2 + except RuntimeError as e: + if self.mirror_style in (3, 4): + raise RuntimeError('90 Degree Rotation requires a square image.') from e + else: + raise RuntimeError('Error transforming image for latent mirroring.') from e + + if self.x_pan != 0: + params.x[:, :, :, :] = torch.roll(params.x, shifts=int(params.x.size()[3]*self.x_pan), dims=[3]) + if self.y_pan != 0: + params.x[:, :, :, :] = torch.roll(params.x, shifts=int(params.x.size()[2]*self.y_pan), dims=[2]) + + + def process(self, p, mirror_mode, mirror_style, x_pan, y_pan, mirroring_max_step_fraction, disable_hr): + self.mirror_mode = mirror_mode + self.mirror_style = mirror_style + self.mirroring_max_step_fraction = mirroring_max_step_fraction + self.x_pan = x_pan + self.y_pan = y_pan + + if mirror_mode != 0: + p.extra_generation_params["Mirror Mode"] = mirror_mode + p.extra_generation_params["Mirror Style"] = mirror_style + p.extra_generation_params["Mirroring Max Step Fraction"] = mirroring_max_step_fraction + if x_pan != 0: + p.extra_generation_params["X Pan"] = x_pan + if y_pan != 0: + p.extra_generation_params["Y Pan"] = y_pan + + if not hasattr(self, 'callbacks_added'): + on_cfg_denoiser(self.denoise_callback) + self.callbacks_added = True + self.run_callback = True + self.enable_hr = getattr(p, 'enable_hr', False) and not disable_hr + self.is_hires = False + + def postprocess(self, *args): + self.run_callback = False + return diff --git a/extensions/Stable-Diffusion-WebUI-Image-Filters/.gitignore b/extensions/Stable-Diffusion-WebUI-Image-Filters/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b6e47617de110dea7ca47e087ff1347cc2646eda --- /dev/null +++ b/extensions/Stable-Diffusion-WebUI-Image-Filters/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/extensions/Stable-Diffusion-WebUI-Image-Filters/LICENSE b/extensions/Stable-Diffusion-WebUI-Image-Filters/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..8cd4d8c68233475b67158fc27cbec1248b829ac2 --- /dev/null +++ b/extensions/Stable-Diffusion-WebUI-Image-Filters/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Zuellni + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/Stable-Diffusion-WebUI-Image-Filters/README.md b/extensions/Stable-Diffusion-WebUI-Image-Filters/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a16b557bd72831c2bcebc57b0f702d6e60df3d3c --- /dev/null +++ b/extensions/Stable-Diffusion-WebUI-Image-Filters/README.md @@ -0,0 +1,23 @@ +# Image Filters +A simple image postprocessing extension for [stable diffusion webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui). +Applies various effects to generated images in pixel space just before they're saved. +Works with txt2img, img2img, and in the extras tab. +Pilgram filters courtesy of [akiomik](https://github.com/akiomik/pilgram). + +## Notes +If, after updating, certain values are not displaying correctly in the webui, you should remove all lines containing `customscript/image_filters.py` from your `ui-config.json` file in the root directory. +You can also edit the maximum values in the same file to your liking. + +To move this script up or down in hierarchy you should rename the `stable-diffusion-webui-image-filters` directory in `extensions`. The scripts are loaded in alphabetical order. + +For detailed information on how each filter functions I recommend reading the Pillow documentation: [ImageEnhance](https://pillow.readthedocs.io/en/stable/reference/ImageEnhance.html), [ImageFilter](https://pillow.readthedocs.io/en/stable/reference/ImageFilter.html), [ImageOps](https://pillow.readthedocs.io/en/stable/reference/ImageOps.html). + +There seems to be an issue with the `aden` filter and non-square images so I didn't include it. + +## Preview +![preview](https://user-images.githubusercontent.com/123005779/224801664-661471c6-b06d-427d-b4c4-9c12b2b238a8.jpg) + +## Comparison +| Base | Filtered | +| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| ![base](https://user-images.githubusercontent.com/123005779/224670233-00e09bbb-b889-4b34-8e94-9e5c16fe7ec6.jpg) | ![filtered](https://user-images.githubusercontent.com/123005779/224571916-4e669118-a78c-4abb-b0a5-b45c2d6927ed.jpg) | diff --git a/extensions/Stable-Diffusion-WebUI-Image-Filters/install.py b/extensions/Stable-Diffusion-WebUI-Image-Filters/install.py new file mode 100644 index 0000000000000000000000000000000000000000..1f7853f0ee8313ca781dbf96835493d8170395f6 --- /dev/null +++ b/extensions/Stable-Diffusion-WebUI-Image-Filters/install.py @@ -0,0 +1,4 @@ +import launch + +if not launch.is_installed("pilgram"): + launch.run_pip("install pilgram==1.2.1", "pilgram") diff --git a/extensions/Stable-Diffusion-WebUI-Image-Filters/scripts/__pycache__/image_filters.cpython-310.pyc b/extensions/Stable-Diffusion-WebUI-Image-Filters/scripts/__pycache__/image_filters.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b767f4502592f06c8d6e21a75158faa0e00c63e1 Binary files /dev/null and b/extensions/Stable-Diffusion-WebUI-Image-Filters/scripts/__pycache__/image_filters.cpython-310.pyc differ diff --git a/extensions/Stable-Diffusion-WebUI-Image-Filters/scripts/image_filters.py b/extensions/Stable-Diffusion-WebUI-Image-Filters/scripts/image_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..27b5c7ec1dba04040f43089a55ba6036baeb745c --- /dev/null +++ b/extensions/Stable-Diffusion-WebUI-Image-Filters/scripts/image_filters.py @@ -0,0 +1,148 @@ +import gradio as gr +import pilgram +from modules import scripts, scripts_postprocessing +from PIL import ImageEnhance, ImageFilter, ImageOps + +script_name = "Image Filters" +script_order = 100000 +val_min_enh = -10 +val_min = 0 +val_def = 0 +val_step = 1 +val_max = 10 +val_max_ops = 50 + + +def ui(is_extra): + args_main = {} + args = {} + + with gr.Group(): + with gr.Accordion(label=script_name, open=False): + with gr.Row(): + args_main["enable"] = gr.Checkbox(label="Enable", value=False) + reset_button = gr.Button(value="Reset", variant="secondary") + with gr.Row(): + args_main["effects"] = gr.Dropdown(label="Effects", multiselect=True, choices=[ + "_1977", "brannan", "brooklyn", "clarendon", "earlybird", + "gingham", "hudson", "inkwell", "kelvin", "lark", + "lofi", "maven", "mayfair", "moon", "nashville", + "perpetua", "reyes", "rise", "slumber", "stinson", + "toaster", "valencia", "walden", "willow", "xpro2", + ]) + args_main["filters"] = gr.Dropdown(label="Filters", multiselect=True, choices=[ + "blur", "contour", "detail", "edge_enhance", "edge_enhance_more", + "emboss", "find_edges", "sharpen", "smooth", "smooth_more", + ]) + args_main["operations"] = gr.Dropdown(label="Operations", multiselect=True, choices=[ + "equalize", "flip", "grayscale", "invert", "mirror", + ]) + with gr.Row(): + args["ops_cutoff_low"] = gr.Slider(label="Auto Contrast Low", minimum=val_min, step=val_step, maximum=val_max_ops, value=val_def) + args["ops_cutoff_high"] = gr.Slider(label="Auto Contrast High", minimum=val_min, step=val_step, maximum=val_max_ops, value=val_def) + args["filter_box_blur"] = gr.Slider(label="Box Blur", minimum=val_min, step=val_step, maximum=val_max, value=val_def) + args["filter_gaussian_blur"] = gr.Slider(label="Gaussian Blur", minimum=val_min, step=val_step, maximum=val_max, value=val_def) + with gr.Row(): + args["filter_min"] = gr.Slider(label="Min Filter", minimum=val_min, step=val_step, maximum=val_max, value=val_def) + args["filter_median"] = gr.Slider(label="Median Filter", minimum=val_min, step=val_step, maximum=val_max, value=val_def) + args["filter_max"] = gr.Slider(label="Max Filter", minimum=val_min, step=val_step, maximum=val_max, value=val_def) + args["filter_mode"] = gr.Slider(label="Mode Filter", minimum=val_min, step=val_step, maximum=val_max, value=val_def) + with gr.Row(): + args["enhance_brightness"] = gr.Slider(label="Brightness", minimum=val_min_enh, step=val_step, maximum=val_max, value=val_def) + args["enhance_color"] = gr.Slider(label="Color", minimum=val_min_enh, step=val_step, maximum=val_max, value=val_def) + args["enhance_contrast"] = gr.Slider(label="Contrast", minimum=val_min_enh, step=val_step, maximum=val_max, value=val_def) + args["enhance_sharpness"] = gr.Slider(label="Sharpness", minimum=val_min_enh, step=val_step, maximum=val_max, value=val_def) + + list_main = list(args_main.values()) + list_args = list(args.values()) + reset_button.click(reset, inputs=list_args, outputs=list_args) + return args_main | args if is_extra else list_main + list_args + + +def reset(*args): + return [val_def for arg in args] + + +def map_filter(value): + return 1 + 2 * (value - 1) + + +def map_enhance(value): + return 0.1 * value + 1 + + +def process( + pp, enable, effects, filters, operations, + ops_cutoff_low, ops_cutoff_high, filter_box_blur, filter_gaussian_blur, + filter_min, filter_median, filter_max, filter_mode, + enhance_brightness, enhance_color, enhance_contrast, enhance_sharpness, +): + if not enable: + return + + for effect in effects: + pp.image = getattr(pilgram, effect)(pp.image) + + for filter in filters: + pp.image = pp.image.filter(getattr(ImageFilter, filter.upper())) + + for operation in operations: + pp.image = getattr(ImageOps, operation)(pp.image) + + if ops_cutoff_low != val_def or ops_cutoff_high != val_def: + pp.image = ImageOps.autocontrast(pp.image, cutoff=(ops_cutoff_low, ops_cutoff_high), preserve_tone=True) + + if enhance_brightness != val_def: + pp.image = ImageEnhance.Brightness(pp.image).enhance(map_enhance(enhance_brightness)) + + if enhance_color != val_def: + pp.image = ImageEnhance.Color(pp.image).enhance(map_enhance(enhance_color)) + + if enhance_contrast != val_def: + pp.image = ImageEnhance.Contrast(pp.image).enhance(map_enhance(enhance_contrast)) + + if enhance_sharpness != val_def: + pp.image = ImageEnhance.Sharpness(pp.image).enhance(map_enhance(enhance_sharpness)) + + if filter_box_blur != val_def: + pp.image = pp.image.filter(ImageFilter.BoxBlur(map_filter(filter_box_blur))) + + if filter_gaussian_blur != val_def: + pp.image = pp.image.filter(ImageFilter.GaussianBlur(map_filter(filter_gaussian_blur))) + + if filter_min != val_def: + pp.image = pp.image.filter(ImageFilter.MinFilter(map_filter(filter_min))) + + if filter_max != val_def: + pp.image = pp.image.filter(ImageFilter.MaxFilter(map_filter(filter_max))) + + if filter_median != val_def: + pp.image = pp.image.filter(ImageFilter.MedianFilter(map_filter(filter_median))) + + if filter_mode != val_def: + pp.image = pp.image.filter(ImageFilter.ModeFilter(map_filter(filter_mode))) + + +class Script(scripts.Script): + def title(self): + return script_name + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + return ui(False) + + def postprocess_image(self, p, pp, *args): + return process(pp, *args) + + +class ScriptPostprocessing(scripts_postprocessing.ScriptPostprocessing): + name = script_name + order = script_order + + def ui(self): + return ui(True) + + def process(self, pp, **args): + return process(pp, **args) diff --git a/extensions/a1111-Some-Image-Effects/LICENSE b/extensions/a1111-Some-Image-Effects/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/extensions/a1111-Some-Image-Effects/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/extensions/a1111-Some-Image-Effects/README.md b/extensions/a1111-Some-Image-Effects/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0444104c211050aa7a11c48117505c9dff9f8ec4 --- /dev/null +++ b/extensions/a1111-Some-Image-Effects/README.md @@ -0,0 +1,92 @@ +# a1111-Some-Image-Effects +Adds post-processing image effects to Stable Diffusion outputs, creating a vintage aesthetic. Grain, vignette, random blur, color offset, and customizable overlays like light leaks. + +# Some Image Effects for Stable Diffusion Web UI + +This script adds some image effects to the Stable Diffusion Web UI, allowing users to apply various post-processing effects to generated images. + +## Features + +- Grain effect +- Vignette +- Random blur +- Color offset +- Image overlay +- Option to save original images + +## Installation +Main installation method should soon be the extension panel of automatic1111. + +But for now: +1. Clone this repository or download the script. +2. Place the script in the `extension` folder of your Stable Diffusion Web UI installation. +3. (Optional) Add overlays to the `overlays` folder in the same directory as the script and add any overlay images you want to use. + +## Usage + +1. Run the Stable Diffusion Web UI. +2. In the "Script" dropdown menu, select "Some Image Effects". +3. Adjust the settings for the desired effects. +4. Generate your image as usual. + +## Effect Examples + +Here are some general examples of how the Some Image Effects can transform your generated images: + +Before | After +-------|------ +![Alt text](example/image3.png) | ![Alt text](example/image4.png) +![Alt text](example/image5.png) | ![Alt text](example/image6.png) + +## Settings + +- **Save Original Image**: When enabled, saves the original image before applying effects. +- **Enable Grain**: Adds film grain to the image. +- **Enable Vignette**: Applies a vignette effect to the edges of the image. +- **Enable Random Blur**: Adds a random blur to a portion of the image. +- **Enable Color Offset**: Shifts color channels for a chromatic aberration effect. +- **Enable Overlay**: Applies an image overlay to the generated image. + +Each effect has additional parameters for fine-tuning: + +- Grain Intensity +- Vignette Intensity, Feather, and Roundness +- Blur Max Size and Strength +- Color Offset X and Y +- Overlay File, Fit, Opacity, and Blend Mode + +## Troubleshooting + +If you encounter issues with overlay images not being found: + +1. Check that your overlay images are in the correct directory. +2. Ensure overlay filenames are correctly entered in the UI. +3. Check the console output for any error messages related to file paths. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +This project is licensed under the Apache License 2.0. + +The Apache 2.0 license is a permissive open source license that allows you to: + +- Use the software for any purpose +- Modify and distribute the software +- Distribute modified versions under any license of your choice + +It also provides an express grant of patent rights from contributors to users. + +Key requirements include: + +- Including a copy of the license in any redistribution +- Clearly stating any significant changes made to the software +- Retaining copyright notices + +For the full license text, see the [LICENSE](LICENSE) file in this repository or visit [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). + +## Acknowledgements + +This script is designed for use with the [Stable Diffusion Web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) by AUTOMATIC1111. diff --git a/extensions/a1111-Some-Image-Effects/example/image1.png b/extensions/a1111-Some-Image-Effects/example/image1.png new file mode 100644 index 0000000000000000000000000000000000000000..780d4ac3fe94331cb33d2eba039a44f5010b037a Binary files /dev/null and b/extensions/a1111-Some-Image-Effects/example/image1.png differ diff --git a/extensions/a1111-Some-Image-Effects/example/image2.png b/extensions/a1111-Some-Image-Effects/example/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..4da7395c5185e5dd3cbe45b3b16c1e31cce5f90b Binary files /dev/null and b/extensions/a1111-Some-Image-Effects/example/image2.png differ diff --git a/extensions/a1111-Some-Image-Effects/example/image3.png b/extensions/a1111-Some-Image-Effects/example/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..77f448db884daf71713f7a2a349488fbfca1927b Binary files /dev/null and b/extensions/a1111-Some-Image-Effects/example/image3.png differ diff --git a/extensions/a1111-Some-Image-Effects/example/image4.png b/extensions/a1111-Some-Image-Effects/example/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..a9b7604323cbe660209d6ca019fe4d82aa2894df Binary files /dev/null and b/extensions/a1111-Some-Image-Effects/example/image4.png differ diff --git a/extensions/a1111-Some-Image-Effects/example/image5.png b/extensions/a1111-Some-Image-Effects/example/image5.png new file mode 100644 index 0000000000000000000000000000000000000000..8caa2f2ef431d74874a4db7e54a584d76581a497 Binary files /dev/null and b/extensions/a1111-Some-Image-Effects/example/image5.png differ diff --git a/extensions/a1111-Some-Image-Effects/example/image6.png b/extensions/a1111-Some-Image-Effects/example/image6.png new file mode 100644 index 0000000000000000000000000000000000000000..a202e2745838ceff6fea93c90bbcf4616dd81f51 Binary files /dev/null and b/extensions/a1111-Some-Image-Effects/example/image6.png differ diff --git a/extensions/a1111-Some-Image-Effects/install.py b/extensions/a1111-Some-Image-Effects/install.py new file mode 100644 index 0000000000000000000000000000000000000000..5943c6c33a875699a922ef0c646017bc13283dc0 --- /dev/null +++ b/extensions/a1111-Some-Image-Effects/install.py @@ -0,0 +1,4 @@ +import launch + +if not launch.is_installed("pillow"): + launch.run_pip("install pillow", "requirement for Gaussian Blur and Vignette") \ No newline at end of file diff --git a/extensions/a1111-Some-Image-Effects/scripts/__pycache__/gaussian_blur_vignette.cpython-310.pyc b/extensions/a1111-Some-Image-Effects/scripts/__pycache__/gaussian_blur_vignette.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44d4683cd82284fa5bcd95d53036e5f8cdef03d5 Binary files /dev/null and b/extensions/a1111-Some-Image-Effects/scripts/__pycache__/gaussian_blur_vignette.cpython-310.pyc differ diff --git a/extensions/a1111-Some-Image-Effects/scripts/gaussian_blur_vignette.py b/extensions/a1111-Some-Image-Effects/scripts/gaussian_blur_vignette.py new file mode 100644 index 0000000000000000000000000000000000000000..60d8e0e0950e4d6134e5f80f2da140dfba023e62 --- /dev/null +++ b/extensions/a1111-Some-Image-Effects/scripts/gaussian_blur_vignette.py @@ -0,0 +1,391 @@ +import modules.scripts as scripts +import gradio as gr +from modules import images, shared +from modules.processing import process_images, Processed +from modules.shared import opts, state +from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageChops, ImageOps +import numpy as np +import random +import os + +class SomeImageEffectsScript(scripts.Script): + def __init__(self): + super().__init__() + self.overlay_files = [] + self.update_overlay_files() + + def update_overlay_files(self): + # Print current working directory and script location + print(f"Current working directory: {os.getcwd()}") + print(f"Script location: {os.path.dirname(os.path.abspath(__file__))}") + + # Try multiple potential locations for the overlays directory + potential_dirs = [ + os.path.join(scripts.basedir(), "overlays"), + os.path.join(os.path.dirname(os.path.abspath(__file__)), "overlays"), + os.path.join(os.getcwd(), "overlays"), + ] + + for overlay_dir in potential_dirs: + print(f"Checking for overlays in: {overlay_dir}") + if os.path.exists(overlay_dir): + self.overlay_files = [f for f in os.listdir(overlay_dir) if self.is_image_file(f)] + print(f"Found {len(self.overlay_files)} overlay files in {overlay_dir}") + return + + print("Overlay directory not found in any of the checked locations.") + + + def is_image_file(self, filename): + image_extensions = ['.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.webp'] + return any(filename.lower().endswith(ext) for ext in image_extensions) + + def title(self): + return "Advanced Image Effects" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with gr.Group(): + with gr.Accordion("Some Image Effects", open=False): + save_original = gr.Checkbox(label="Save Original Image", value=True) + + with gr.Row(): + enable_grain = gr.Checkbox(label="Enable Grain", value=False) + enable_vignette = gr.Checkbox(label="Enable Vignette", value=False) + enable_random_blur = gr.Checkbox(label="Enable Random Blur", value=False) + enable_color_offset = gr.Checkbox(label="Enable Color Offset", value=False) + + with gr.Row(): + grain_intensity = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.3, label="Grain Intensity") + + with gr.Row(): + vignette_intensity = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.3, label="Vignette Intensity") + vignette_feather = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.3, label="Vignette Feather") + vignette_roundness = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.5, label="Vignette Roundness") + + with gr.Row(): + blur_max_size = gr.Slider(minimum=0.0, maximum=0.5, step=0.05, value=0.2, label="Max Blur Size (% of image)") + blur_strength = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=3.0, label="Blur Strength") + + with gr.Row(): + color_offset_x = gr.Slider(minimum=-50, maximum=50, step=1, value=0, label="Color Offset X") + color_offset_y = gr.Slider(minimum=-50, maximum=50, step=1, value=0, label="Color Offset Y") + + with gr.Row(): + enable_overlay = gr.Checkbox(label="Enable Overlay", value=False) + overlay_file = gr.Dropdown(label="Overlay File", choices=self.overlay_files) + overlay_fit = gr.Dropdown(label="Overlay Fit", choices=["stretch", "fit_out"], value="stretch") + + with gr.Row(): + overlay_opacity = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.5, label="Overlay Opacity") + overlay_blend_mode = gr.Dropdown(label="Overlay Blend Mode", choices=[ + "normal", "multiply", "screen", "overlay", "darken", "lighten", + "color_dodge", "color_burn", "hard_light", "soft_light", "difference", + "exclusion", "hue", "saturation", "color", "luminosity" + ], value="normal") + + with gr.Row(): + enable_luminosity = gr.Checkbox(label="Enable Luminosity Adjustment", value=False) + luminosity_factor = gr.Slider(minimum=-1.0, maximum=1.0, step=0.05, value=0, label="Luminosity Factor") + + with gr.Row(): + enable_contrast = gr.Checkbox(label="Enable Contrast Adjustment", value=False) + contrast_factor = gr.Slider(minimum=0.0, maximum=2.0, step=0.05, value=1, label="Contrast Factor") + + with gr.Row(): + enable_hue = gr.Checkbox(label="Enable Hue Adjustment", value=False) + hue_factor = gr.Slider(minimum=-180, maximum=180, step=1, value=0, label="Hue Shift") + + with gr.Row(): + enable_saturation = gr.Checkbox(label="Enable Saturation Adjustment", value=False) + saturation_factor = gr.Slider(minimum=0.0, maximum=2.0, step=0.05, value=1, label="Saturation Factor") + + return [save_original, enable_grain, enable_vignette, enable_random_blur, enable_color_offset, + grain_intensity, vignette_intensity, vignette_feather, vignette_roundness, + blur_max_size, blur_strength, color_offset_x, color_offset_y, + enable_overlay, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode, + enable_luminosity, luminosity_factor, enable_contrast, contrast_factor, + enable_hue, hue_factor, enable_saturation, saturation_factor] + + def process(self, p, *args): + enabled_effects = [] + if args[1]: # enable_grain + enabled_effects.append("Grain") + if args[2]: # enable_vignette + enabled_effects.append("Vignette") + if args[3]: # enable_random_blur + enabled_effects.append("Random Blur") + if args[4]: # enable_color_offset + enabled_effects.append("Color Offset") + if args[13]: # enable_overlay + enabled_effects.append("Overlay") + if args[18]: # enable_luminosity + enabled_effects.append("Luminosity") + if args[20]: # enable_contrast + enabled_effects.append("Contrast") + if args[22]: # enable_hue + enabled_effects.append("Hue") + if args[24]: # enable_saturation + enabled_effects.append("Saturation") + + if enabled_effects: + p.extra_generation_params["Some Image Effects"] = ", ".join(enabled_effects) + + + def postprocess_image(self, p, pp, *args): + save_original, enable_grain, enable_vignette, enable_random_blur, enable_color_offset, \ + grain_intensity, vignette_intensity, vignette_feather, vignette_roundness, \ + blur_max_size, blur_strength, color_offset_x, color_offset_y, \ + enable_overlay, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode, \ + enable_luminosity, luminosity_factor, enable_contrast, contrast_factor, \ + enable_hue, hue_factor, enable_saturation, saturation_factor = args + + if hasattr(pp, 'image'): + if save_original: + self.save_original_image(pp.image) + pp.image = self.add_effects(pp.image, *args) + elif hasattr(pp, 'images'): + for i, image in enumerate(pp.images): + if save_original: + self.save_original_image(image) + pp.images[i] = self.add_effects(image, *args) + + + def add_effects(self, img, save_original, enable_grain, enable_vignette, enable_random_blur, enable_color_offset, + grain_intensity, vignette_intensity, vignette_feather, vignette_roundness, + blur_max_size, blur_strength, color_offset_x, color_offset_y, + enable_overlay, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode): + if enable_grain: + img = self.add_grain(img, grain_intensity) + + if enable_vignette: + img = self.add_vignette(img, vignette_intensity, vignette_feather, vignette_roundness) + + if enable_random_blur: + img = self.add_random_blur(img, blur_max_size, blur_strength) + + if enable_color_offset: + img = self.add_color_offset(img, color_offset_x, color_offset_y) + + if enable_overlay and overlay_file: + img = self.add_overlay(img, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode) + + return img + + def add_grain(self, img, intensity): + img_np = np.array(img) + noise = np.random.randn(*img_np.shape) * 255 * intensity + noisy_img = np.clip(img_np + noise, 0, 255).astype(np.uint8) + return Image.fromarray(noisy_img) + + def add_vignette(self, img, intensity, feather, roundness): + width, height = img.size + mask = Image.new('L', (width, height), 255) + draw = ImageDraw.Draw(mask) + + x_center, y_center = width // 2, height // 2 + max_radius = min(width, height) // 2 + + for i in range(max_radius): + alpha = int(255 * (1 - (i / max_radius) ** roundness) * intensity) + draw.ellipse([x_center - i, y_center - i, x_center + i, y_center + i], fill=alpha) + + mask = mask.filter(ImageFilter.GaussianBlur(radius=max_radius * feather)) + + enhancer = ImageEnhance.Brightness(img) + darkened = enhancer.enhance(1 - intensity * 0.5) + + return Image.composite(darkened, img, mask) + + def add_random_blur(self, img, max_size, strength): + width, height = img.size + blur_size = int(min(width, height) * max_size) + x = random.randint(0, width - blur_size) + y = random.randint(0, height - blur_size) + + mask = Image.new('L', (width, height), 0) + draw = ImageDraw.Draw(mask) + draw.ellipse([x, y, x + blur_size, y + blur_size], fill=255) + mask = mask.filter(ImageFilter.GaussianBlur(radius=blur_size // 4)) + + blurred = img.filter(ImageFilter.GaussianBlur(radius=strength)) + return Image.composite(blurred, img, mask) + + def add_color_offset(self, img, offset_x, offset_y): + r, g, b = img.split() + r = ImageChops.offset(r, offset_x, offset_y) + b = ImageChops.offset(b, -offset_x, -offset_y) + return Image.merge('RGB', (r, g, b)) + + def add_overlay(self, img, overlay_file, overlay_fit, opacity, blend_mode): + if img is None: + print("Error: Input image is None") + return None + + # Try multiple potential locations for the overlays directory + potential_dirs = [ + os.path.join(scripts.basedir(), "overlays"), + os.path.join(os.path.dirname(os.path.abspath(__file__)), "overlays"), + os.path.join(os.getcwd(), "overlays"), + ] + + overlay_path = None + for overlay_dir in potential_dirs: + temp_path = os.path.join(overlay_dir, overlay_file) + if os.path.exists(temp_path): + overlay_path = temp_path + break + + if not overlay_path: + print(f"Overlay file not found: {overlay_file}") + return img + + try: + overlay = Image.open(overlay_path).convert("RGBA") + except Exception as e: + print(f"Error opening overlay file: {e}") + return img + + # Resize overlay + if overlay_fit == "stretch": + overlay = overlay.resize(img.size, Image.LANCZOS) + elif overlay_fit == "fit_out": + img_ratio = img.width / img.height + overlay_ratio = overlay.width / overlay.height + if img_ratio > overlay_ratio: + new_width = img.width + new_height = int(new_width / overlay_ratio) + else: + new_height = img.height + new_width = int(new_height * overlay_ratio) + overlay = overlay.resize((new_width, new_height), Image.LANCZOS) + + # Crop to fit + left = (overlay.width - img.width) // 2 + top = (overlay.height - img.height) // 2 + right = left + img.width + bottom = top + img.height + overlay = overlay.crop((left, top, right, bottom)) + + # Apply opacity + overlay = Image.blend(Image.new('RGBA', img.size, (0, 0, 0, 0)), overlay, opacity) + + # Apply blend mode + if img.mode != 'RGBA': + img = img.convert('RGBA') + + # Apply blend mode + if img.mode != 'RGBA': + img = img.convert('RGBA') + + if blend_mode == "multiply": + blended = ImageChops.multiply(img, overlay) + elif blend_mode == "screen": + blended = ImageChops.screen(img, overlay) + elif blend_mode == "overlay": + blended = self.overlay_blend(img, overlay) + elif blend_mode == "darken": + blended = ImageChops.darker(img, overlay) + elif blend_mode == "lighten": + blended = ImageChops.lighter(img, overlay) + elif blend_mode == "color_dodge": + blended = self.color_dodge(img, overlay) + elif blend_mode == "color_burn": + blended = self.color_burn(img, overlay) + elif blend_mode == "hard_light": + blended = self.hard_light(img, overlay) + elif blend_mode == "soft_light": + blended = self.soft_light(img, overlay) + elif blend_mode == "difference": + blended = ImageChops.difference(img, overlay) + elif blend_mode == "exclusion": + blended = self.exclusion(img, overlay) + elif blend_mode == "hue": + blended = self.hue_blend(img, overlay) + elif blend_mode == "saturation": + blended = self.saturation_blend(img, overlay) + elif blend_mode == "color": + blended = self.color_blend(img, overlay) + elif blend_mode == "luminosity": + blended = self.luminosity_blend(img, overlay) + else: # normal + blended = Image.alpha_composite(img, overlay) + + return blended.convert("RGB") + + def adjust_luminosity(self, img, factor): + return ImageEnhance.Brightness(img).enhance(1 + factor) + + def adjust_contrast(self, img, factor): + return ImageEnhance.Contrast(img).enhance(factor) + + def adjust_hue(self, img, shift): + img = img.convert('HSV') + h, s, v = img.split() + h = h.point(lambda x: (x + shift) % 256) + return Image.merge('HSV', (h, s, v)).convert('RGB') + + def adjust_saturation(self, img, factor): + return ImageEnhance.Color(img).enhance(factor) + + def add_effects(self, img, save_original, enable_grain, enable_vignette, enable_random_blur, enable_color_offset, + grain_intensity, vignette_intensity, vignette_feather, vignette_roundness, + blur_max_size, blur_strength, color_offset_x, color_offset_y, + enable_overlay, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode, + enable_luminosity, luminosity_factor, enable_contrast, contrast_factor, + enable_hue, hue_factor, enable_saturation, saturation_factor): + if img is None: + print("Error: Input image is None") + return None + + if enable_grain: + img = self.add_grain(img, grain_intensity) + + if enable_vignette: + img = self.add_vignette(img, vignette_intensity, vignette_feather, vignette_roundness) + + if enable_random_blur: + img = self.add_random_blur(img, blur_max_size, blur_strength) + + if enable_color_offset: + img = self.add_color_offset(img, color_offset_x, color_offset_y) + + if enable_overlay and overlay_file: + img = self.add_overlay(img, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode) + + if enable_luminosity: + img = self.adjust_luminosity(img, luminosity_factor) + + if enable_contrast: + img = self.adjust_contrast(img, contrast_factor) + + if enable_hue: + img = self.adjust_hue(img, hue_factor) + + if enable_saturation: + img = self.adjust_saturation(img, saturation_factor) + + return img + + def save_original_image(self, img): + save_dir = getattr(shared.opts, 'outdir_samples', None) or getattr(shared.opts, 'outdir_txt2img_samples', None) or getattr(shared.opts, 'outdir_img2img_samples', None) or getattr(shared.opts, 'outdir_extras_samples', None) or os.getcwd() + + save_dir = os.path.join(save_dir, "originals") + os.makedirs(save_dir, exist_ok=True) + + # Use a default extension if none is provided + job_name = shared.state.job if shared.state.job else "image" + base_name, ext = os.path.splitext(job_name) + if not ext: + ext = ".png" # Default to PNG if no extension is provided + + filename = f"{base_name}_original{ext}" + save_path = os.path.join(save_dir, filename) + + try: + img.save(save_path) + print(f"Original image saved to: {save_path}") + except Exception as e: + print(f"Error saving original image: {e}") diff --git a/extensions/a1111-Some-Image-Effects/scripts/overlays/overlay1.png b/extensions/a1111-Some-Image-Effects/scripts/overlays/overlay1.png new file mode 100644 index 0000000000000000000000000000000000000000..d0979d228ac4176f81432287169c822c709c763a Binary files /dev/null and b/extensions/a1111-Some-Image-Effects/scripts/overlays/overlay1.png differ diff --git a/extensions/a1111-Some-Image-Effects/scripts/overlays/overlay2.png b/extensions/a1111-Some-Image-Effects/scripts/overlays/overlay2.png new file mode 100644 index 0000000000000000000000000000000000000000..2f4ff534231c2a7aa4f381ec24d1bad68e13f372 Binary files /dev/null and b/extensions/a1111-Some-Image-Effects/scripts/overlays/overlay2.png differ diff --git a/extensions/a1111-sd-webui-tagcomplete/.gitignore b/extensions/a1111-sd-webui-tagcomplete/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d324b1b1bf6001ab81c2f61966afe589154ff5d8 --- /dev/null +++ b/extensions/a1111-sd-webui-tagcomplete/.gitignore @@ -0,0 +1,3 @@ +tags/temp/ +__pycache__/ +tags/tag_frequency.db diff --git a/extensions/a1111-sd-webui-tagcomplete/LICENSE b/extensions/a1111-sd-webui-tagcomplete/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..72a5479833a0bc73e8b2e4b39467e7ccfc8bdc0c --- /dev/null +++ b/extensions/a1111-sd-webui-tagcomplete/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Dominik Reh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/a1111-sd-webui-tagcomplete/README.md b/extensions/a1111-sd-webui-tagcomplete/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d495692e1a2ebc08dc69bd2cb9d329e1b3dd1b4b --- /dev/null +++ b/extensions/a1111-sd-webui-tagcomplete/README.md @@ -0,0 +1,600 @@ +![tag_autocomplete_light](https://user-images.githubusercontent.com/34448969/208306863-90bbd663-2cb4-47f1-a7fe-7b662a7b95e2.png) + +
+ +# SD WebUI Tag Autocomplete +## English • [简体中文](./README_ZH.md) • [日本語](./README_JA.md) + +Booru style tag autocompletion for the AUTOMATIC1111 Stable Diffusion WebUI + +[![Github Release][release-shield]][release-url] +[![stargazers][stargazers-shield]][stargazers-url] +[![contributors][contributors-shield]][contributors-url] +[![forks][forks-shield]][forks-url] +[![issues][issues-shield]][issues-url] + +[Changelog][release-url] • +[Known Issues](#%EF%B8%8F-common-problems--known-issues) • +[Report Bug][issues-url] • +[Request Feature][issues-url] +
+
+ +# 📄 Description + +Tag Autocomplete is an extension for the popular [AUTOMATIC1111 web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) for Stable Diffusion. +You can install it using the inbuilt available extensions list, clone the files manually as described [below](#-installation), or use a pre-packaged version from [Releases](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/releases). + +It displays autocompletion hints for recognized tags from "image booru" boards such as Danbooru, which are primarily used for browsing Anime-style illustrations. +Since most custom Stable Diffusion models were trained using this information or merged with ones that did, using exact tags in prompts can often improve composition and consistency, even if the model itself has a photorealistic style. + +Disclaimer: The default tag lists contain NSFW terms, please use them responsibly. + +
+ +# ✨ Features +- 🚀 Instant completion hints while typing (under normal circumstances) +- ⌨️ Keyboard navigation +- 🌒 Dark & Light mode support +- 🛠️ Many [settings](#%EF%B8%8F-settings) and customizability +- 🌍 [Translation support](#translations) for tags, with optional live preview for the full prompt + - **Note:** Translation files are provided by the community, see [here](#list-of-translations) for a list of translations I know of. + +Tag autocomplete supports built-in completion for: +- 🏷️ **Danbooru & e621 tags** (Top 100k by post count, as of November 2022) +- ✳️ [**Wildcards**](#wildcards) +- ➕ [**Extra network**](#extra-networks-embeddings-hypernets-lora-) filenames, including + - Textual Inversion embeddings [(jump to readme section)] + - Hypernetworks + - LoRA + - LyCORIS / LoHA +- 🪄 [**Chants**](#chants) (custom format for longer prompt presets) +- 🏷️ "[**Extra file**](#extra-file)", one set of customizable extra tags + + +Additionally, some support for other third party extensions exists: +
+Click to expand + +- [Image Browser][image-browser-url] - Filename & EXIF keyword search +- [Multidiffusion Upscaler][multidiffusion-url] - Regional Prompts +- [Dataset Tag Editor][tag-editor-url] - Caption, Interrogate Result, Edit Tags & Edit Caption +- [WD 1.4 Tagger][wd-tagger-url] - Additional & Excluded tags +- [Umi AI][umi-url] - Completion for YAML wildcards +
+
+ +# 🖼️ Screenshots & Demo videos +
+Click to expand +Basic usage (with keyboard navigation): + +https://user-images.githubusercontent.com/34448969/200128020-10d9a8b2-cea6-4e3f-bcd2-8c40c8c73233.mp4 + +Wildcard script support: + +https://user-images.githubusercontent.com/34448969/200128031-22dd7c33-71d1-464f-ae36-5f6c8fd49df0.mp4 + +Extra Network preview support: + +https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/assets/34448969/3c0cad84-fb5f-436d-b05a-28db35860d13 + +Dark and Light mode supported, including tag colors: + +![results_dark](https://user-images.githubusercontent.com/34448969/200128214-3b6f21b4-9dda-4acf-820e-5df0285c30d6.png) +![results_light](https://user-images.githubusercontent.com/34448969/200128217-bfac8b60-6673-447b-90fd-dc6326f1618c.png) +
+
+ +# 📦 Installation +## Using the built-in extension list +1. Open the `Extensions` tab +2. Open the `Available` sub-tab +3. Click **Load from** +4. Find **Booru tag autocompletion** in the list + - The extension was one of the first available, so selecting "oldest first" will show it high up in the list. + - Alternatively, use CRTL + F to search for the text on the page +5. Click **Install** on the right side + +![Load from index](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/assets/34448969/b9b860c1-2e77-41b1-aa5c-a44e252f1a40) +![Order by oldest](https://user-images.githubusercontent.com/34448969/223537231-48e982b8-0920-48c5-87e5-8c81ebbb5fe3.png) +![Install](https://user-images.githubusercontent.com/34448969/223537336-5c02ccb1-233d-4e0d-9e73-d1b889252c49.png) + + +## Manual clone +```bash +git clone "https://github.com/DominikDoom/a1111-sd-webui-tagcomplete.git" extensions/tag-autocomplete +``` +(The second argument specifies the name of the folder, you can choose whatever you like). + +
+ +# ❇️ Additional completion support +## Wildcards +Autocompletion also works with wildcard files used by https://github.com/AUTOMATIC1111/stable-diffusion-webui-wildcards or other similar scripts/extensions. +Completion is triggered by typing `__` (double underscore). It will first show a list of your wildcard files, and upon choosing one, the replacement options inside that file. +This enables you to either insert categories to be replaced by the script, or directly choose one and use wildcards as a sort of categorized custom tag system. + +![Wildcard files](https://user-images.githubusercontent.com/34448969/223534518-8488c2e1-d9e5-4870-844f-adbf3bfb1eee.png) +![Wildcard replacements](https://user-images.githubusercontent.com/34448969/223534534-69597907-59de-4ba8-ae83-b01386570124.png) + + +Wildcards are searched for in every extension folder, as well as the `scripts/wildcards` folder to support legacy versions. This means that you can combine wildcards from multiple extensions. Nested folders are also supported if you have grouped your wildcards in that way. + +## Extra networks (Embeddings, Hypernets, LoRA, ...) +Completion for these types is triggered by typing `<`. By default it will show them all mixed together, but further filtering can be done in the following way: +- `
" + elif input_string == "timeout": + return div + "The CivitAI-API has timed out, please try again.
The servers might be too busy or down if the issue persists." + elif input_string == "offline": + return div + "The CivitAI servers are currently offline.
Please try again later." + elif input_string == "no_items": + return div + "Failed to retrieve any models from CivitAI
The servers might be too busy or down if the issue persists." + else: + return div + "The CivitAI-API failed to respond due to an error.
Check the logs for more details." \ No newline at end of file diff --git a/extensions/sd-civitai-browser-plus/scripts/civitai_download.py b/extensions/sd-civitai-browser-plus/scripts/civitai_download.py new file mode 100644 index 0000000000000000000000000000000000000000..b7c1a9fa5c0b31b6ba69d8d08cc12ed6d1a209d1 --- /dev/null +++ b/extensions/sd-civitai-browser-plus/scripts/civitai_download.py @@ -0,0 +1,773 @@ +import requests +import gradio as gr +import time +import subprocess +import threading +import os +import re +import random +import platform +import stat +import json +import time +from pathlib import Path +from modules.shared import opts, cmd_opts +from scripts.civitai_global import print, debug_print +import scripts.civitai_global as gl +import scripts.civitai_api as _api +import scripts.civitai_file_manage as _file +try: + from zip_unicode import ZipHandler +except ImportError: + print("Python module 'ZipUnicode' has not been imported correctly, please try to restart or install it manually.") + +total_count = 0 +current_count = 0 +dl_manager_count = 0 + +def random_number(prev=None): + number = str(random.randint(10000, 99999)) + while number == prev: + number = str(random.randint(10000, 99999)) + + return number + +gl.init() +rpc_secret = "R7T5P2Q9K6" +try: + queue = not cmd_opts.no_gradio_queue +except AttributeError: + queue = not cmd_opts.disable_queue +except: + queue = True + +def start_aria2_rpc(): + start_file = os.path.join(aria2path, '_') + running_file = os.path.join(aria2path, 'running') + null = open(os.devnull, 'w') + + if os.path.exists(running_file): + try: + if os_type == 'Linux': + env = os.environ.copy() + env['PATH'] = '/usr/bin:' + env['PATH'] + subprocess.Popen("pkill aria2", shell=True, env=env) + else: + subprocess.Popen(stop_rpc, stdout=null, stderr=null) + time.sleep(1) + except Exception as e: + print(f"Failed to stop Aria2 RPC : {e}") + else: + if os.path.exists(start_file): + os.rename(start_file, running_file) + return + else: + with open(start_file, 'w', encoding="utf-8"): + pass + + try: + show_log = getattr(opts, "show_log", False) + aria2_flags = getattr(opts, "aria2_flags", "") + cmd = f'"{aria2}" --enable-rpc --rpc-listen-all --rpc-listen-port=24000 --rpc-secret {rpc_secret} --check-certificate=false --ca-certificate=" " --file-allocation=none {aria2_flags}' + subprocess_args = {'shell': True} + if not show_log: + subprocess_args.update({'stdout': subprocess.DEVNULL, 'stderr': subprocess.DEVNULL}) + + subprocess.Popen(cmd, **subprocess_args) + if os.path.exists(running_file): + print("Aria2 RPC restarted") + else: + print("Aria2 RPC started") + except Exception as e: + print(f"Failed to start Aria2 RPC server: {e}") + +aria2path = Path(__file__).resolve().parents[1] / "aria2" +os_type = platform.system() + +if os_type == 'Windows': + aria2 = os.path.join(aria2path, 'win', 'aria2.exe') + stop_rpc = "taskkill /im aria2.exe /f" + start_aria2_rpc() +elif os_type == 'Linux': + aria2 = os.path.join(aria2path, 'lin', 'aria2') + st = os.stat(aria2) + os.chmod(aria2, st.st_mode | stat.S_IEXEC) + stop_rpc = "pkill aria2" + start_aria2_rpc() + +class TimeOutFunction(Exception): + pass + +def create_model_item(dl_url, model_filename, install_path, model_name, version_name, model_sha256, model_id, create_json, from_batch=False): + global dl_manager_count + if model_id: + model_id = int(model_id) + if model_sha256: + model_sha256 = model_sha256.upper() + if model_sha256 == "UNKNOWN": + model_sha256 = None + + filtered_items = [] + + for item in gl.json_data['items']: + if int(item['id']) == int(model_id): + filtered_items.append(item) + content_type = item['type'] + desc = item['description'] + main_folder = _api.contenttype_folder(content_type, desc) + break + + sub_folder = os.path.normpath(os.path.relpath(install_path, main_folder)) + + model_json = {"items": filtered_items} + model_versions = _api.update_model_versions(model_id) + (preview_html,_,_,_,_,_,_,_,_,_,_,existing_path,_) = _api.update_model_info(None, model_versions.get('value'), False, model_id) + + for item in gl.download_queue: + if item['dl_url'] == dl_url: + return None + + dl_manager_count += 1 + + item = { + "dl_id": dl_manager_count, + "dl_url" : dl_url, + "model_filename" : model_filename, + "install_path" : install_path, + "model_name" : model_name, + "version_name" : version_name, + "model_sha256" : model_sha256, + "model_id" : model_id, + "create_json" : create_json, + "model_json" : model_json, + "model_versions" : model_versions, + "preview_html" : preview_html['value'], + "existing_path": existing_path['value'], + "from_batch" : from_batch, + "sub_folder" : sub_folder + } + + return item + +def selected_to_queue(model_list, subfolder, download_start, create_json, current_html): + global total_count, current_count + if gl.download_queue: + number = download_start + else: + number = random_number(download_start) + total_count = 0 + current_count = 0 + + model_list = json.loads(model_list) + + for model_string in model_list: + model_name, model_id = _api.extract_model_info(model_string) + for item in gl.json_data['items']: + if int(item['id']) == int(model_id): + model_id, desc, content_type = item['id'], item['description'], item['type'] + version = item.get('modelVersions', [])[0] + version_name = version.get('name') + files = version.get('files', []) + primary_file = next((file for file in files if file.get('primary', False)), None) + if primary_file: + model_filename = _api.cleaned_name(primary_file.get('name')) + model_sha256 = primary_file.get('hashes', {}).get('SHA256') + dl_url = primary_file.get('downloadUrl') + else: + model_filename = _api.cleaned_name(files[0].get('name')) + model_sha256 = files[0].get('hashes', {}).get('SHA256') + dl_url = files[0].get('downloadUrl') + break + + model_folder = _api.contenttype_folder(content_type, desc) + + sub_opt1 = os.path.join(os.sep, _api.cleaned_name(model_name)) + sub_opt2 = os.path.join(os.sep, _api.cleaned_name(model_name), _api.cleaned_name(version_name)) + + default_sub = _api.sub_folder_value(content_type, desc) + if default_sub == f"{os.sep}Model Name": + default_sub = sub_opt1 + elif default_sub == f"{os.sep}Model Name{os.sep}Version Name": + default_sub = sub_opt2 + + if subfolder and subfolder != "None" and subfolder != "Only available if the selected files are of the same model type": + from_batch = False + if platform.system() == "Windows": + subfolder = re.sub(r'[/:*?"<>|]', '', subfolder) + + if not subfolder.startswith(os.sep): + subfolder = os.sep + subfolder + install_path = model_folder + subfolder + else: + from_batch = True + if default_sub != "None": + install_path = model_folder + default_sub + else: + install_path = model_folder + + model_item = create_model_item(dl_url, model_filename, install_path, model_name, version_name, model_sha256, model_id, create_json, from_batch) + if model_item: + gl.download_queue.append(model_item) + total_count += 1 + + html = download_manager_html(current_html) + + return ( + gr.Button.update(interactive=False, visible=False), # Download Button + gr.Button.update(interactive=True, visible=True), # Cancel Button + gr.Button.update(interactive=True if len(gl.download_queue) > 1 else False, visible=True), # Cancel All Button + gr.Textbox.update(value=number), # Download Start Trigger + gr.HTML.update(value='
'), # Download Progress + gr.HTML.update(value=html) # Download Manager HTML + ) + +def download_start(download_start, dl_url, model_filename, install_path, model_string, version_name, model_sha256, model_id, create_json, current_html): + global total_count, current_count + if model_string: + model_name, _ = _api.extract_model_info(model_string) + model_item = create_model_item(dl_url, model_filename, install_path, model_name, version_name, model_sha256, model_id, create_json) + + gl.download_queue.append(model_item) + + if len(gl.download_queue) > 1: + number = download_start + total_count += 1 + else: + number = random_number(download_start) + total_count = 1 + current_count = 0 + + html = download_manager_html(current_html) + + return ( + gr.Button.update(interactive=False, visible=True), # Download Button + gr.Button.update(interactive=True, visible=True), # Cancel Button + gr.Button.update(interactive=True if len(gl.download_queue) > 1 else False, visible=True), # Cancel All Button + gr.Textbox.update(value=number), # Download Start Trigger + gr.HTML.update(value='
'), # Download Progress + gr.HTML.update(value=html) # Download Manager HTML + ) + +def download_finish(model_filename, version, model_id): + if model_id: + model_id = int(model_id) + model_versions = _api.update_model_versions(model_id) + else: + model_versions = None + if model_versions: + version_choices = model_versions.get('choices', []) + else: + version_choices = [] + prev_version = gl.last_version + " [Installed]" + + if prev_version in version_choices: + version = prev_version + Del = True + Down = False + else: + Del = False + Down = True + + if gl.cancel_status: + Del = False + Down = True + + gl.download_fail = False + gl.cancel_status = False + + return ( + gr.Button.update(interactive=model_filename, visible=Down, value="Download model"), # Download Button + gr.Button.update(interactive=False, visible=False), # Cancel Button + gr.Button.update(interactive=False, visible=False), # Cancel All Button + gr.Button.update(interactive=Del, visible=Del), # Delete Button + gr.HTML.update(value='
'), # Download Progress + gr.Dropdown.update(value=version, choices=version_choices) # Version Dropdown + ) + +def download_cancel(): + gl.cancel_status = True + gl.download_fail = True + if gl.download_queue: + item = gl.download_queue[0] + + while True: + if not gl.isDownloading: + if item: + model_string = f"{item['model_name']} ({item['model_id']})" + _file.delete_model(0, item['model_filename'], model_string, item['version_name'], False, model_ver=item['model_versions'], model_json=item['model_json']) + break + else: + time.sleep(0.5) + return + +def download_cancel_all(): + gl.cancel_status = True + gl.download_fail = True + + if gl.download_queue: + item = gl.download_queue[0] + + while True: + if not gl.isDownloading: + if item: + model_string = f"{item['model_name']} ({item['model_id']})" + _file.delete_model(0, item['model_filename'], model_string, item['version_name'], False, model_ver=item['model_versions'], model_json=item['model_json']) + gl.download_queue = [] + break + else: + time.sleep(0.5) + return + +def convert_size(size): + for unit in ['bytes', 'KB', 'MB', 'GB']: + if size < 1024: + return f"{size:.2f} {unit}" + size /= 1024 + return f"{size:.2f} GB" + +def get_download_link(url, model_id): + headers = _api.get_headers(model_id) + proxies, ssl = _api.get_proxies() + + response = requests.get(url, headers=headers, allow_redirects=False, proxies=proxies, verify=ssl) + + if 300 <= response.status_code <= 308: + if "login?returnUrl" in response.text and "reason=download-auth" in response.text: + return "NO_API" + + download_link = response.headers["Location"] + return download_link + else: + return None + +def download_file(url, file_path, install_path, model_id, progress=gr.Progress() if queue else None): + try: + disable_dns = getattr(opts, "disable_dns", False) + split_aria2 = getattr(opts, "split_aria2", 64) + max_retries = 5 + gl.download_fail = False + aria2_rpc_url = "http://localhost:24000/jsonrpc" + + file_name = os.path.basename(file_path) + + download_link = get_download_link(url, model_id) + if not download_link: + print(f'File: "{file_name}" not found on CivitAI servers, it looks like the file is not available for download.') + gl.download_fail = True + return + + elif download_link == "NO_API": + print(f'File: "{file_name}" requires a personal CivitAI API to be downloaded, you can set your own API key in the CivitAI Browser+ settings in the SD-WebUI settings tab') + gl.download_fail = "NO_API" + if progress != None: + progress(0, desc=f'File: "{file_name}" requires a personal CivitAI API to be downloaded, you can set your own API key in the CivitAI Browser+ settings in the SD-WebUI settings tab') + time.sleep(5) + return + + if os.path.exists(file_path): + os.remove(file_path) + + if disable_dns: + dns = "false" + else: + dns = "true" + + options = { + "dir": install_path, + "max-connection-per-server": str(f"{split_aria2}"), + "split": str(f"{split_aria2}"), + "async-dns": dns, + "out": file_name + } + + payload = json.dumps({ + "jsonrpc": "2.0", + "id": "1", + "method": "aria2.addUri", + "params": ["token:" + rpc_secret, [download_link], options] + }) + + try: + response = requests.post(aria2_rpc_url, data=payload) + data = json.loads(response.text) + if 'result' not in data: + raise ValueError(f'Failed to start download: {data}') + gid = data['result'] + except Exception as e: + print(f"Failed to start download: {e}") + gl.download_fail = True + return + + while True: + if gl.cancel_status: + payload = json.dumps({ + "jsonrpc": "2.0", + "id": "1", + "method": "aria2.remove", + "params": ["token:" + rpc_secret, gid] + }) + requests.post(aria2_rpc_url, data=payload) + if progress != None: + progress(0, desc=f"Download cancelled.") + return + + try: + payload = json.dumps({ + "jsonrpc": "2.0", + "id": "1", + "method": "aria2.tellStatus", + "params": ["token:" + rpc_secret, gid] + }) + + response = requests.post(aria2_rpc_url, data=payload) + status_info = json.loads(response.text)['result'] + + total_length = int(status_info['totalLength']) + completed_length = int(status_info['completedLength']) + download_speed = int(status_info['downloadSpeed']) + + progress_percent = (completed_length / total_length) * 100 if total_length else 0 + + remaining_size = total_length - completed_length + if download_speed > 0: + eta_seconds = remaining_size / download_speed + eta_formatted = time.strftime("%H:%M:%S", time.gmtime(eta_seconds)) + else: + eta_formatted = "XX:XX:XX" + if progress != None: + progress(progress_percent / 100, desc=f"Downloading: {file_name} - {convert_size(completed_length)}/{convert_size(total_length)} - Speed: {convert_size(download_speed)}/s - ETA: {eta_formatted} - Queue: {current_count}/{total_count}") + + if status_info['status'] == 'complete': + print(f"Model saved to: {file_path}") + if progress != None: + progress(1, desc=f"Model saved to: {file_path}") + gl.download_fail = False + return + + if status_info['status'] == 'error': + if progress != None: + progress(0, desc=f"Encountered an error during download of: \"{file_name}\" Please try again.") + gl.download_fail = True + return + + time.sleep(0.25) + + except Exception as e: + print(f"Error occurred during Aria2 status update: {e}") + max_retries -= 1 + if max_retries == 0: + if progress != None: + progress(0, desc=f"Encountered an error during download of: \"{file_name}\" Please try again.") + gl.download_fail = True + return + time.sleep(5) + except: + if progress != None: + progress(0, desc=f"Encountered an error during download of: \"{file_name}\" Please try again.") + gl.download_fail = True + if os.path.exists(file_path): + os.remove(file_path) + time.sleep(5) + +def info_to_json(install_path, model_id, model_sha256, unpackList=None): + json_file = os.path.splitext(install_path)[0] + ".json" + if os.path.exists(json_file): + try: + with open(json_file, 'r', encoding="utf-8") as f: + data = json.load(f) + except Exception as e: + print(f"Failed to open {json_file}: {e}") + else: + data = {} + + data['modelId'] = model_id + data['sha256'] = model_sha256 + if unpackList: + data['unpackList'] = unpackList + + with open(json_file, 'w', encoding="utf-8") as f: + json.dump(data, f, indent=4) + +def download_file_old(url, file_path, model_id, progress=gr.Progress() if queue else None): + try: + gl.download_fail = False + max_retries = 5 + if os.path.exists(file_path): + os.remove(file_path) + + downloaded_size = 0 + tokens = re.split(re.escape(os.sep), file_path) + file_name_display = tokens[-1] + start_time = time.time() + last_update_time = 0 + update_interval = 0.25 + + download_link = get_download_link(url, model_id) + if not download_link: + print(f'File: "{file_name_display}" not found on CivitAI servers, it looks like the file is not available for download.') + if progress != None: + progress(0, desc=f'File: "{file_name_display}" not found on CivitAI servers, it looks like the file is not available for download.') + time.sleep(5) + gl.download_fail = True + return + + elif download_link == "NO_API": + print(f'File: "{file_name_display}" requires a personal CivitAI API key to be downloaded, you can set your own API key in the CivitAI Browser+ settings in the SD-WebUI settings tab') + gl.download_fail = "NO_API" + if progress != None: + progress(0, desc=f'File: "{file_name_display}" requires a personal CivitAI API key to be downloaded, you can set your own API key in the CivitAI Browser+ settings in the SD-WebUI settings tab') + time.sleep(5) + return + + headers = _api.get_headers(model_id, True) + proxies, ssl = _api.get_proxies() + + while True: + if gl.cancel_status: + if progress != None: + progress(0, desc=f"Download cancelled.") + return + if os.path.exists(file_path): + downloaded_size = os.path.getsize(file_path) + headers['Range'] = f"bytes={downloaded_size}-" + + with open(file_path, "ab") as f: + while gl.isDownloading: + try: + if gl.cancel_status: + if progress != None: + progress(0, desc=f"Download cancelled.") + return + try: + if gl.cancel_status: + if progress != None: + progress(0, desc=f"Download cancelled.") + return + response = requests.get(download_link, headers=headers, stream=True, timeout=10, proxies=proxies, verify=ssl) + if response.status_code == 404: + if progress != None: + progress(0, desc=f"Encountered an error during download of: {file_name_display}, file is not found on CivitAI servers.") + gl.download_fail = True + return + total_size = int(response.headers.get("Content-Length", 0)) + except: + raise TimeOutFunction("Timed Out") + + if total_size == 0: + total_size = downloaded_size + + for chunk in response.iter_content(chunk_size=1024): + if chunk: + if gl.cancel_status: + if progress != None: + progress(0, desc=f"Download cancelled.") + return + f.write(chunk) + downloaded_size += len(chunk) + elapsed_time = time.time() - start_time + download_speed = downloaded_size / elapsed_time + remaining_size = total_size - downloaded_size + if download_speed > 0: + eta_seconds = remaining_size / download_speed + eta_formatted = time.strftime("%H:%M:%S", time.gmtime(eta_seconds)) + else: + eta_formatted = "XX:XX:XX" + current_time = time.time() + if current_time - last_update_time >= update_interval: + if progress != None: + progress(downloaded_size / total_size, desc=f"Downloading: {file_name_display} {convert_size(downloaded_size)} / {convert_size(total_size)} - Speed: {convert_size(int(download_speed))}/s - ETA: {eta_formatted} - Queue: {current_count}/{total_count}") + last_update_time = current_time + if gl.isDownloading == False: + response.close + break + downloaded_size = os.path.getsize(file_path) + break + + except TimeOutFunction: + if progress != None: + progress(0, desc="CivitAI API did not respond, retrying...") + max_retries -= 1 + if max_retries == 0: + if progress != None: + progress(0, desc=f"Encountered an error during download of: {file_name_display}, please try again.") + gl.download_fail = True + return + time.sleep(5) + + if (gl.isDownloading == False): + break + + gl.isDownloading = False + downloaded_size = os.path.getsize(file_path) + if downloaded_size >= total_size: + if not gl.cancel_status: + print(f"Model saved to: {file_path}") + if progress != None: + progress(1, desc=f"Model saved to: {file_path}") + gl.download_fail = False + return + + else: + if progress != None: + progress(0, desc=f"Encountered an error during download of: {file_name_display}, please try again.") + print(f"File download failed: {file_name_display}") + gl.download_fail = True + if os.path.exists(file_path): + os.remove(file_path) + except: + if progress != None: + progress(0, desc=f"Encountered an error during download of: {file_name_display}, please try again.") + gl.download_fail = True + if os.path.exists(file_path): + os.remove(file_path) + time.sleep(5) + +def download_create_thread(download_finish, queue_trigger, progress=gr.Progress() if queue else None): + global current_count + current_count += 1 + if not gl.download_queue: + return ( + gr.HTML.update(), # Download Progress HTML + gr.Textbox.update(value=None), # Current Model + gr.Textbox.update(value=random_number(download_finish)), # Download Finish Trigger + gr.Textbox.update(value=queue_trigger), # Queue Trigger + gr.Button.update(interactive=False) # Cancel All Button + ) + item = gl.download_queue[0] + gl.cancel_status = False + use_aria2 = getattr(opts, "use_aria2", True) + unpack_zip = getattr(opts, "unpack_zip", False) + save_all_images = getattr(opts, "auto_save_all_img", False) + gl.recent_model = item['model_name'] + gl.last_version = item['version_name'] + + if item['from_batch']: + item['install_path'] = item['existing_path'] + + gl.isDownloading = True + _file.make_dir(item['install_path']) + + path_to_new_file = os.path.join(item['install_path'], item['model_filename']) + + if use_aria2 and os_type != 'Darwin': + thread = threading.Thread(target=download_file, args=(item['dl_url'], path_to_new_file, item['install_path'], item['model_id'], progress)) + else: + thread = threading.Thread(target=download_file_old, args=(item['dl_url'], path_to_new_file, item['model_id'], progress)) + thread.start() + thread.join() + + if not gl.cancel_status or gl.download_fail: + if os.path.exists(path_to_new_file): + unpackList = [] + if unpack_zip: + try: + if path_to_new_file.endswith('.zip'): + directory = Path(os.path.dirname(path_to_new_file)) + zip_handler = ZipHandler(path_to_new_file) + + for _, decoded_name in zip_handler.name_map.items(): + unpackList.append(decoded_name) + + zip_handler.extract_all(directory) + zip_handler.zip_ref.close() + + print(f"Successfully extracted {item['model_filename']} to {directory}") + os.remove(path_to_new_file) + except ImportError: + print("Python module 'ZipUnicode' has not been imported correctly, cannot extract zip file. Please try to restart or install it manually.") + except Exception as e: + print(f"Failed to extract {item['model_filename']} with error: {e}") + if not gl.cancel_status: + if item['create_json']: + _file.save_model_info(item['install_path'], item['model_filename'], item['sub_folder'], item['model_sha256'], item['preview_html'], api_response=item['model_json']) + info_to_json(path_to_new_file, item['model_id'], item['model_sha256'], unpackList) + _file.save_preview(path_to_new_file, item['model_json'], True, item['model_sha256']) + if save_all_images: + _file.save_images(item['preview_html'], item['model_filename'], item['install_path'], item['sub_folder'], api_response=item['model_json']) + + base_name = os.path.splitext(item['model_filename'])[0] + base_name_preview = base_name + '.preview' + + if gl.download_fail: + for root, dirs, files in os.walk(item['install_path'], followlinks=True): + for file in files: + file_base_name = os.path.splitext(file)[0] + if file_base_name == base_name or file_base_name == base_name_preview: + path_file = os.path.join(root, file) + os.remove(path_file) + + if gl.cancel_status: + print(f'Cancelled download of "{item["model_filename"]}"') + else: + if not gl.download_fail == "NO_API": + print(f'Error occured during download of "{item["model_filename"]}"') + + if gl.cancel_status: + card_name = None + else: + model_string = f"{item['model_name']} ({item['model_id']})" + (card_name, _, _) = _file.card_update(item['model_versions'], model_string, item['version_name'], True) + + if len(gl.download_queue) != 0: + gl.download_queue.pop(0) + gl.isDownloading = False + time.sleep(2) + + if len(gl.download_queue) == 0: + finish_nr = random_number(download_finish) + queue_nr = queue_trigger + else: + finish_nr = download_finish + queue_nr = random_number(queue_trigger) + + return ( + gr.HTML.update(), # Download Progress HTML + gr.Textbox.update(value=card_name), # Current Model + gr.Textbox.update(value=finish_nr), # Download Finish Trigger + gr.Textbox.update(value=queue_nr), # Queue Trigger + gr.Button.update(interactive=True if len(gl.download_queue) > 1 else False) # Cancel All Button + ) + +def remove_from_queue(dl_id): + global total_count + for item in gl.download_queue: + if int(dl_id) == int(item['dl_id']): + gl.download_queue.remove(item) + total_count -= 1 + return + +def arrange_queue(input): + id_and_index = input.split('.') + dl_id = int(id_and_index[0]) + index = int(id_and_index[1]) + 1 + for item in gl.download_queue: + if int(item['dl_id']) == dl_id: + current_item = gl.download_queue.pop(gl.download_queue.index(item)) + gl.download_queue.insert(index, current_item) + break + +def get_style(size, left_border): + return f"flex-grow: {size};" + ("border-left: 1px solid var(--border-color-primary);" if left_border else "") + "padding: 5px 10px 5px 10px;width: 0;align-self: center;" + +def download_manager_html(current_html): + html = current_html.rsplit("", 1)[0] + pattern = r'dl_id="(\d+)"' + matches = re.findall(pattern, html) + existing_item_ids = [int(match) for match in matches] + + for item in gl.download_queue: + if not item['dl_id'] in existing_item_ids: + download_item = f''' +
+
{item['model_name']}
+
{item['version_name']}
+
{item['install_path']}
+
In queue...
+
Remove
+
+ ''' + html = html + download_item + + html = html + "" + + return html \ No newline at end of file diff --git a/extensions/sd-civitai-browser-plus/scripts/civitai_file_manage.py b/extensions/sd-civitai-browser-plus/scripts/civitai_file_manage.py new file mode 100644 index 0000000000000000000000000000000000000000..c7af221b0b9d90020514a7c5d521cdd5794fb68b --- /dev/null +++ b/extensions/sd-civitai-browser-plus/scripts/civitai_file_manage.py @@ -0,0 +1,1201 @@ +import json +import gradio as gr +import urllib.request +import urllib.parse +import urllib.error +import os +import io +import re +import time +import errno +import requests +import hashlib +import base64 +from PIL import Image +from pathlib import Path +from urllib.parse import urlparse +from modules.shared import cmd_opts, opts +from scripts.civitai_global import print, debug_print +import scripts.civitai_global as gl +import scripts.civitai_api as _api +import scripts.civitai_file_manage as _file +import scripts.civitai_download as _download + +try: + from send2trash import send2trash +except ImportError: + print("Python module 'send2trash' has not been imported correctly, please try to restart or install it manually.") +try: + from bs4 import BeautifulSoup +except ImportError: + print("Python module 'BeautifulSoup' has not been imported correctly, please try to restart or install it manually.") + +gl.init() + +css_path = Path(__file__).resolve().parents[1] / "style_html.css" +no_update = False +from_ver = False +from_tag = False +from_installed = False +try: + queue = not cmd_opts.no_gradio_queue +except AttributeError: + queue = not cmd_opts.disable_queue +except: + queue = True + +def delete_model(delete_finish=None, model_filename=None, model_string=None, list_versions=None, sha256=None, selected_list=None, model_ver=None, model_json=None): + deleted = False + model_id = None + + if model_string: + _, model_id = _api.extract_model_info(model_string) + + if not model_ver: + model_versions = _api.update_model_versions(model_id) + else: model_versions = model_ver + + (model_name, ver_value, ver_choices) = _file.card_update(model_versions, model_string, list_versions, False) + if not model_json: + if model_id != None: + selected_content_type = None + for item in gl.json_data['items']: + if int(item['id']) == int(model_id): + selected_content_type = item['type'] + desc = item['description'] + break + + if selected_content_type is None: + print("Model ID not found in json_data. (delete_model)") + return + else: + for item in model_json["items"]: + selected_content_type = item['type'] + desc = item['description'] + + model_folder = os.path.join(_api.contenttype_folder(selected_content_type, desc)) + + # Delete based on provided SHA-256 hash + if sha256: + sha256_upper = sha256.upper() + for root, _, files in os.walk(model_folder, followlinks=True): + for file in files: + if file.endswith('.json'): + file_path = os.path.join(root, file) + try: + with open(file_path, 'r', encoding="utf-8") as json_file: + data = json.load(json_file) + file_sha256 = data.get('sha256', '') + if file_sha256: + file_sha256 = file_sha256.upper() + except Exception as e: + print(f"Failed to open: {file_path}: {e}") + file_sha256 = "0" + + if file_sha256 == sha256_upper: + unpack_list = data.get('unpackList', []) + for unpacked_file in unpack_list: + unpacked_file_path = os.path.join(root, unpacked_file) + if os.path.isfile(unpacked_file_path): + try: + send2trash(unpacked_file_path) + print(f"File moved to trash based on unpackList: {unpacked_file_path}") + except: + os.remove(unpacked_file_path) + print(f"File deleted based on unpackList: {unpacked_file_path}") + + base_name, _ = os.path.splitext(file) + if os.path.isfile(file_path): + try: + send2trash(file_path) + print(f"Model moved to trash based on SHA-256: {file_path}") + except: + os.remove(file_path) + print(f"Model deleted based on SHA-256: {file_path}") + delete_associated_files(root, base_name) + deleted = True + + # Fallback to delete based on filename if not deleted based on SHA-256 + filename_to_delete = os.path.splitext(model_filename)[0] + aria2_file = model_filename + ".aria2" + if not deleted: + for root, dirs, files in os.walk(model_folder, followlinks=True): + for file in files: + current_file_name = os.path.splitext(file)[0] + if filename_to_delete == current_file_name or aria2_file == file: + path_file = os.path.join(root, file) + if os.path.isfile(path_file): + try: + send2trash(path_file) + print(f"Model moved to trash based on filename: {path_file}") + except: + os.remove(path_file) + print(f"Model deleted based on filename: {path_file}") + delete_associated_files(root, current_file_name) + + number = _download.random_number(delete_finish) + + + btnDwn = not selected_list or selected_list == "[]" + + return ( + gr.Button.update(interactive=btnDwn, visible=btnDwn), # Download Button + gr.Button.update(interactive=False, visible=False), # Cancel Button + gr.Button.update(interactive=False, visible=False), # Delete Button + gr.Textbox.update(value=number), # Delete Finish Trigger + gr.Textbox.update(value=model_name), # Current Model + gr.Dropdown.update(value=ver_value, choices=ver_choices) # Version List + ) + +def delete_associated_files(directory, base_name): + for file in os.listdir(directory): + current_base_name, ext = os.path.splitext(file) + if current_base_name == base_name or current_base_name == f"{base_name}.preview" or current_base_name == f"{base_name}.api_info": + path_to_delete = os.path.join(directory, file) + try: + send2trash(path_to_delete) + print(f"Associated file moved to trash: {path_to_delete}") + except: + os.remove(path_to_delete) + print(f"Associated file deleted: {path_to_delete}") + +def save_preview(file_path, api_response, overwrite_toggle=False, sha256=None): + proxies, ssl = _api.get_proxies() + json_file = os.path.splitext(file_path)[0] + ".json" + install_path, file_name = os.path.split(file_path) + name = os.path.splitext(file_name)[0] + filename = f'{name}.preview.png' + image_path = os.path.join(install_path, filename) + + if not overwrite_toggle: + if os.path.exists(image_path): + return + + if not sha256: + if os.path.exists(json_file): + try: + with open(json_file, 'r', encoding="utf-8") as f: + data = json.load(f) + if 'sha256' in data and data['sha256']: + sha256 = data['sha256'].upper() + except Exception as e: + print(f"Failed to open {json_file}: {e}") + else: + sha256 = sha256.upper() + + for item in api_response["items"]: + for version in item["modelVersions"]: + for file_entry in version["files"]: + if file_entry["hashes"].get("SHA256") == sha256: + for image in version["images"]: + if image["type"] == "image": + url_with_width = re.sub(r'/width=\d+', f'/width={image["width"]}', image["url"]) + + response = requests.get(url_with_width, proxies=proxies, verify=ssl) + if response.status_code == 200: + with open(image_path, 'wb') as img_file: + img_file.write(response.content) + print(f"Preview saved at \"{image_path}\"") + else: + print(f"Failed to save preview. Status code: {response.status_code}") + + return + print(f"No preview images found for \"{name}\"") + return + +def get_image_path(install_path, api_response, sub_folder): + image_location = getattr(opts, "image_location", r"") + sub_image_location = getattr(opts, "sub_image_location", True) + image_path = install_path + if api_response: + json_info = api_response['items'][0] + else: + json_info = gl.json_info + if image_location: + if sub_image_location: + desc = json_info['description'] + content_type = json_info['type'] + image_path = os.path.join(_api.contenttype_folder(content_type, desc, custom_folder=image_location)) + + if sub_folder and sub_folder != "None" and sub_folder != "Only available if the selected files are of the same model type": + image_path = os.path.join(image_path, sub_folder.lstrip("/").lstrip("\\")) + else: + image_path = Path(image_location) + make_dir(image_path) + return image_path + +def save_images(preview_html, model_filename, install_path, sub_folder, api_response=None): + image_path = get_image_path(install_path, api_response, sub_folder) + img_urls = re.findall(r'data-sampleimg="true" src=[\'"]?([^\'" >]+)', preview_html) + + name = os.path.splitext(model_filename)[0] + + opener = urllib.request.build_opener() + opener.addheaders = [('User-agent', 'Mozilla/5.0')] + urllib.request.install_opener(opener) + + for i, img_url in enumerate(img_urls): + filename = f'{name}_{i}.jpg' + img_url = urllib.parse.quote(img_url, safe=':/=') + try: + with urllib.request.urlopen(img_url) as url: + with open(os.path.join(image_path, filename), 'wb') as f: + f.write(url.read()) + print(f"Downloaded {filename}") + + except urllib.error.URLError as e: + print(f'Error: {e.reason}') + +def card_update(gr_components, model_name, list_versions, is_install): + if gr_components: + version_choices = gr_components['choices'] + else: + print("Couldn't retrieve version, defaulting to installed") + model_name += ".New" + return model_name, None, None + + if is_install and not gl.download_fail and not gl.cancel_status: + version_value_clean = list_versions + " [Installed]" + version_choices_clean = [version if version + " [Installed]" != version_value_clean else version_value_clean for version in version_choices] + + else: + version_value_clean = list_versions.replace(" [Installed]", "") + version_choices_clean = [version if version.replace(" [Installed]", "") != version_value_clean else version_value_clean for version in version_choices] + + first_version_installed = "[Installed]" in version_choices_clean[0] + any_later_version_installed = any("[Installed]" in version for version in version_choices_clean[1:]) + + if first_version_installed: + model_name += ".New" + elif any_later_version_installed: + model_name += ".Old" + else: + model_name += ".None" + + return model_name, version_value_clean, version_choices_clean + +def list_files(folders): + model_files = [] + + extensions = ['.pt', '.ckpt', '.pth', '.safetensors', '.th', '.zip', '.vae'] + + for folder in folders: + if folder and os.path.exists(folder): + for root, _, files in os.walk(folder, followlinks=True): + for file in files: + _, file_extension = os.path.splitext(file) + if file_extension.lower() in extensions: + model_files.append(os.path.join(root, file)) + + model_files = sorted(list(set(model_files))) + return model_files + +def gen_sha256(file_path): + json_file = os.path.splitext(file_path)[0] + ".json" + + if os.path.exists(json_file): + try: + with open(json_file, 'r', encoding="utf-8") as f: + data = json.load(f) + + if 'sha256' in data and data['sha256']: + hash_value = data['sha256'] + return hash_value + except Exception as e: + print(f"Failed to open {json_file}: {e}") + + def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE): + while True: + chunk = file.read(size) + if not chunk: + break + yield chunk + + blocksize = 1 << 20 + h = hashlib.sha256() + length = 0 + with open(os.path.realpath(file_path), 'rb') as f: + for block in read_chunks(f, size=blocksize): + length += len(block) + h.update(block) + + hash_value = h.hexdigest() + + if os.path.exists(json_file): + try: + with open(json_file, 'r', encoding="utf-8") as f: + data = json.load(f) + + if 'sha256' in data and data['sha256']: + data['sha256'] = hash_value + + with open(json_file, 'w', encoding="utf-8") as f: + json.dump(data, f, indent=4) + except Exception as e: + print(f"Failed to open {json_file}: {e}") + else: + data = {'sha256': hash_value} + with open(json_file, 'w', encoding="utf-8") as f: + json.dump(data, f, indent=4) + + return hash_value + +def convert_local_images(html): + soup = BeautifulSoup(html) + for simg in soup.find_all("img", attrs={"data-sampleimg": "true"}): + url = urlparse(simg["src"]) + path = url.path + if not os.path.exists(path): + print("URL path does not exist: %s" % url.path) + # Try the raw url, files can be saved in windows as "C:\..." and + # that confuses urlparse because people only really test on Linux. + if os.path.exists(simg["src"]): + path = simg["src"] + else: + continue + with open(path, 'rb') as f: + imgdata = f.read() + b64img = base64.b64encode(imgdata).decode('utf-8') + imgtype = Image.open(io.BytesIO(imgdata)).format + if not imgtype: + imgtype = "PNG" + simg["src"] = f"data:image/{imgtype};base64,{b64img}" + return str(soup) + +def model_from_sent(model_name, content_type): + + modelID_failed = False + output_html = None + model_file = None + use_local_html = getattr(opts, "use_local_html", False) + local_path_in_html = getattr(opts, "local_path_in_html", False) + + model_name = re.sub(r'\.\d{3}$', '', model_name) + content_type = re.sub(r'\.\d{3}$', '', content_type).lower() + if 'inversion' in content_type: + content_type = ['TextualInversion'] + elif 'hypernetwork' in content_type: + content_type = ['Hypernetwork'] + elif 'checkpoint' in content_type: + content_type = ['Checkpoint'] + elif 'lora' in content_type: + content_type = ['LORA', 'LoCon'] + + extensions = ['.pt', '.ckpt', '.pth', '.safetensors', '.th', '.zip', '.vae'] + + for content_type_item in content_type: + folder = _api.contenttype_folder(content_type_item) + for folder_path, _, files in os.walk(folder, followlinks=True): + for file in files: + if file.startswith(model_name) and file.endswith(tuple(extensions)): + model_file = os.path.join(folder_path, file) + + if not model_file: + output_html = _api.api_error_msg("path_not_found") + print(f'Error: Could not find model path for model: "{model_name}"') + print(f'Content type: "{content_type}"') + print(f'Main folder path: "{folder}"') + use_local_html = False + + if use_local_html: + html_file = os.path.splitext(model_file)[0] + ".html" + if os.path.exists(html_file): + with open(html_file, 'r', encoding='utf-8') as html: + output_html = html.read() + index = output_html.find("") + if index != -1: + output_html = output_html[index + len(""):] + if local_path_in_html: + output_html = convert_local_images(output_html) + + if not output_html: + modelID = get_models(model_file, True) + if not modelID or modelID == "Model not found": + output_html = _api.api_error_msg("not_found") + modelID_failed = True + if modelID == "offline": + output_html = _api.api_error_msg("offline") + modelID_failed = True + if not modelID_failed: + json_data = _api.request_civit_api(f"https://civitai.com/api/v1/models?ids={modelID}&nsfw=true") + else: + json_data = None + + if not isinstance(json_data, dict): + output_html = _api.api_error_msg(json_data) + else: + model_versions = _api.update_model_versions(modelID, json_data) + output_html = _api.update_model_info(None, model_versions.get('value'), True, modelID, json_data, True) + + css_path = Path(__file__).resolve().parents[1] / "style_html.css" + with open(css_path, 'r', encoding='utf-8') as css_file: + css = css_file.read() + replacements = { + '#0b0f19': 'var(--neutral-950)', + '#F3F4F6': 'var(--body-text-color)', + 'white': 'var(--body-text-color)', + '#80a6c8': 'var(--secondary-300)', + '#60A5FA': 'var(--link-text-color-hover)', + '#1F2937': 'var(--neutral-700)', + '#1F2937': 'var(--button-secondary-background-fill-hover)', + '#374151': 'var(--input-border-color)', + '#111827': 'var(--neutral-800)', + '#111827': 'var(--button-secondary-background-fill)', + 'top: 50%;': '', + 'padding-top: 0px;': 'padding-top: 475px;', + '.civitai_txt2img': '.civitai_placeholder' + } + + for old, new in replacements.items(): + css = css.replace(old, new) + + style_tag = f'' + head_section = f'{style_tag}' + + output_html = output_html.replace('display:flex;align-items:flex-start;', 'display:flex;align-items:flex-start;flex-wrap:wrap;justify-content:center;') + output_html = str(head_section + output_html) + output_html = output_html.replace('zoom-radio', 'zoom-preview-radio') + output_html = output_html.replace('zoomRadio', 'zoomPreviewRadio') + output_html = output_html.replace('zoom-overlay', 'zoom-preview-overlay') + output_html = output_html.replace('resetZoom', 'resetPreviewZoom') + + number = _download.random_number() + + return ( + gr.Textbox.update(value=output_html, placeholder=number), # Preview HTML + ) + +def send_to_browser(model_name, content_type, click_first_item): + modelID_failed = False + output_html = None + model_file = None + number = click_first_item + + model_name = re.sub(r'\.\d{3}$', '', model_name) + content_type = re.sub(r'\.\d{3}$', '', content_type).lower() + if 'inversion' in content_type: + content_type = ['TextualInversion'] + elif 'hypernetwork' in content_type: + content_type = ['Hypernetwork'] + elif 'checkpoint' in content_type: + content_type = ['Checkpoint'] + elif 'lora' in content_type: + content_type = ['LORA', 'LoCon'] + extensions = ['.pt', '.ckpt', '.pth', '.safetensors', '.th', '.zip', '.vae'] + + for content_type_item in content_type: + folder = _api.contenttype_folder(content_type_item) + for folder_path, _, files in os.walk(folder, followlinks=True): + for file in files: + if file.startswith(model_name) and file.endswith(tuple(extensions)): + model_file = os.path.join(folder_path, file) + + if not model_file: + output_html = _api.api_error_msg("path_not_found") + print(f'Error: Could not find model path for model: "{model_name}"') + print(f'Content type: "{content_type}"') + print(f'Main folder path: "{folder}"') + if not output_html: + modelID = get_models(model_file, True) + if not modelID or modelID == "Model not found": + output_html = _api.api_error_msg("not_found") + modelID_failed = True + if modelID == "offline": + output_html = _api.api_error_msg("offline") + modelID_failed = True + + if not modelID_failed: + gl.json_data = _api.request_civit_api(f"https://civitai.com/api/v1/models?ids={modelID}&nsfw=true") + output_html = _api.model_list_html(gl.json_data) + + number = _download.random_number(click_first_item) + + return ( + gr.HTML.update(output_html), # Card HTML + gr.Button.update(interactive=False), # Prev Button + gr.Button.update(interactive=False), # Next Button + gr.Slider.update(value=1, maximum=1), # Page Slider + gr.Textbox.update(number) # Click first card trigger + ) + +def is_image_url(url): + image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp'] + parsed = urlparse(url) + path = parsed.path + return any(path.endswith(ext) for ext in image_extensions) + +def clean_description(desc): + try: + soup = BeautifulSoup(desc, 'html.parser') + for a in soup.find_all('a', href=True): + link_text = a.text + ' ' + a['href'] + if not is_image_url(a['href']): + a.replace_with(link_text) + + cleaned_text = soup.get_text() + except ImportError: + print("Python module 'BeautifulSoup' was not imported correctly, cannot clean description. Please try to restart or install it manually.") + cleaned_text = desc + return cleaned_text + +def make_dir(path): + try: + if not os.path.exists(path): + os.makedirs(path) + except OSError as e: + if e.errno == errno.EACCES: + try: + os.makedirs(path, mode=0o777) + except OSError as e: + if e.errno == errno.EACCES: + print("Permission denied even with elevated permissions.") + else: + print(f"Error creating directory: {e}") + else: + print(f"Error creating directory: {e}") + except Exception as e: + print(f"Error creating directory: {e}") + +def save_model_info(install_path, file_name, sub_folder, sha256=None, preview_html=None, overwrite_toggle=False, api_response=None): + save_path, filename = get_save_path_and_name(install_path, file_name, api_response, sub_folder) + json_file = os.path.join(install_path, f'{filename}.json') + make_dir(install_path) + + save_api_info = getattr(opts, "save_api_info", False) + use_local = getattr(opts, "local_path_in_html", False) + + if not api_response: + api_response = gl.json_data + + result = find_and_save(api_response, sha256, file_name, json_file, False, overwrite_toggle) + if result != "found": + result = find_and_save(api_response, sha256, file_name, json_file, True, overwrite_toggle) + + if preview_html: + if use_local: + img_urls = re.findall(r'data-sampleimg="true" src=[\'"]?([^\'" >]+)', preview_html) + for i, img_url in enumerate(img_urls): + img_name = f'{filename}_{i}.jpg' + preview_html = preview_html.replace(img_url,f'{os.path.join(save_path, img_name)}') + + match = re.search(r'(\s*)
', preview_html) + if match: + indentation = match.group(1) + else: + indentation = '' + css_link = f'' + utf8_meta_tag = f'{indentation}' + head_section = f'{indentation}{indentation} {utf8_meta_tag}{indentation} {css_link}{indentation}' + HTML = head_section + preview_html + path_to_new_file = os.path.join(save_path, f'{filename}.html') + with open(path_to_new_file, 'wb') as f: + f.write(HTML.encode('utf8')) + print(f"HTML saved at \"{path_to_new_file}\"") + + if save_api_info: + path_to_new_file = os.path.join(save_path, f'{filename}.api_info.json') + if not os.path.exists(path_to_new_file) or overwrite_toggle: + with open(path_to_new_file, mode="w", encoding="utf-8") as f: + json.dump(gl.json_info, f, indent=4, ensure_ascii=False) + + +def find_and_save(api_response, sha256=None, file_name=None, json_file=None, no_hash=None, overwrite_toggle=None): + save_desc = getattr(opts, "model_desc_to_json", True) + for item in api_response.get('items', []): + for model_version in item.get('modelVersions', []): + for file in model_version.get('files', []): + file_name_api = file.get('name', '') + sha256_api = file.get('hashes', {}).get('SHA256', '') + + if file_name == file_name_api if no_hash else sha256 == sha256_api: + gl.json_info = item + trained_words = model_version.get('trainedWords', []) + + if save_desc: + description = item.get('description', '') + if description != None: + description = clean_description(description) + + base_model = model_version.get('baseModel', '') + + if base_model.startswith("SD 1"): + base_model = "SD1" + elif base_model.startswith("SD 2"): + base_model = "SD2" + elif base_model.startswith("SDXL"): + base_model = "SDXL" + else: + base_model = "Other" + + if isinstance(trained_words, list): + trained_tags = ",".join(trained_words) + trained_tags = re.sub(r'<[^>]*:[^>]*>', '', trained_tags) + trained_tags = re.sub(r', ?', ', ', trained_tags) + trained_tags = trained_tags.strip(', ') + else: + trained_tags = trained_words + + if os.path.exists(json_file): + with open(json_file, 'r', encoding="utf-8") as f: + try: + content = json.load(f) + except: + content = {} + else: + content = {} + changed = False + if not overwrite_toggle: + if "activation text" not in content: + content["activation text"] = trained_tags + changed = True + if save_desc and ("description" not in content): + content["description"] = description + changed = True + if "sd version" not in content: + content["sd version"] = base_model + changed = True + else: + content["activation text"] = trained_tags + if save_desc: + content["description"] = description + content["sd version"] = base_model + changed = True + + with open(json_file, 'w', encoding="utf-8") as f: + json.dump(content, f, indent=4) + + if changed: + print(f"Model info saved to \"{json_file}\"") + return "found" + + return "not found" + +def get_models(file_path, gen_hash=None): + modelId = None + modelVersionId = None + sha256 = None + json_file = os.path.splitext(file_path)[0] + ".json" + if os.path.exists(json_file): + try: + with open(json_file, 'r', encoding="utf-8") as f: + data = json.load(f) + + if 'modelId' in data: + modelId = data['modelId'] + if 'modelVersionId' in data: + modelVersionId = data['modelVersionId'] + if 'sha256' in data and data['sha256']: + sha256 = data['sha256'] + except Exception as e: + print(f"Failed to open {json_file}: {e}") + + if not modelId or not modelVersionId or not sha256: + if not sha256 and gen_hash: + sha256 = gen_sha256(file_path) + + if sha256: + by_hash = f"https://civitai.com/api/v1/model-versions/by-hash/{sha256}" + else: + return modelId if modelId else None + + proxies, ssl = _api.get_proxies() + try: + if not modelId or not modelVersionId: + response = requests.get(by_hash, timeout=(60,30), proxies=proxies, verify=ssl) + if response.status_code == 200: + api_response = response.json() + if 'error' in api_response: + print(f"\"{file_path}\": {api_response['error']}") + return None + else: + modelId = api_response.get("modelId", "") + modelVersionId = api_response.get("id", "") + elif response.status_code == 503: + return "offline" + elif response.status_code == 404: + modelId = "Model not found" + modelVersionId = "Model not found" + + if os.path.exists(json_file): + try: + with open(json_file, 'r', encoding="utf-8") as f: + data = json.load(f) + + data['modelId'] = modelId + data['modelVersionId'] = modelVersionId + data['sha256'] = sha256.upper() + + with open(json_file, 'w', encoding="utf-8") as f: + json.dump(data, f, indent=4) + except Exception as e: + print(f"Failed to open {json_file}: {e}") + else: + data = { + 'modelId': modelId, + 'modelVersionId': modelVersionId, + 'sha256': sha256.upper() + } + with open(json_file, 'w', encoding="utf-8") as f: + json.dump(data, f, indent=4) + + return modelId + except requests.exceptions.Timeout: + print(f"Request timed out for {file_path}. Skipping...") + return "offline" + except requests.exceptions.ConnectionError: + print("Failed to connect to the API. The CivitAI servers might be offline.") + return "offline" + except Exception as e: + print(f"An error occurred for {file_path}: {str(e)}") + return None + +def version_match(file_paths, api_response): + updated_models = [] + outdated_models = [] + sha256_hashes = {} + for file_path in file_paths: + json_path = f"{os.path.splitext(file_path)[0]}.json" + if os.path.exists(json_path): + with open(json_path, 'r', encoding="utf-8") as f: + try: + json_data = json.load(f) + sha256 = json_data.get('sha256') + if sha256: + sha256_hashes[os.path.basename(file_path)] = sha256.upper() + except Exception as e: + print(f"Failed to open {json_path}: {e}") + + file_names_and_hashes = set() + for file_path in file_paths: + file_name = os.path.basename(file_path) + file_name_without_ext = os.path.splitext(file_name)[0] + file_sha256 = sha256_hashes.get(file_name, "") + if file_sha256: file_sha256 = file_sha256.upper() + file_names_and_hashes.add((file_name_without_ext, file_sha256)) + + for item in api_response.get('items', []): + model_versions = item.get('modelVersions', []) + + if not model_versions: + continue + + for idx, model_version in enumerate(model_versions): + files = model_version.get('files', []) + match_found = False + for file_entry in files: + entry_name = os.path.splitext(file_entry.get('name', ''))[0] + entry_sha256 = file_entry.get('hashes', {}).get('SHA256', "") + if entry_sha256: entry_sha256 = entry_sha256.upper() + + if (entry_name, entry_sha256) in file_names_and_hashes: + match_found = True + break + + if match_found: + if idx == 0: + updated_models.append((f"&ids={item['id']}", item["name"])) + else: + outdated_models.append((f"&ids={item['id']}", item["name"])) + break + + return updated_models, outdated_models + +def get_content_choices(scan_choices=False): + use_LORA = getattr(opts, "use_LORA", False) + if use_LORA: + content_list = ["Checkpoint", "TextualInversion", "LORA, LoCon, DoRA", "Poses", "Controlnet", "Hypernetwork", "AestheticGradient", "VAE", "Upscaler", "MotionModule", "Wildcards", "Workflows", "Other"] + else: + content_list = ["Checkpoint", "TextualInversion", "LORA", "LoCon", "DoRA", "Poses", "Controlnet", "Hypernetwork", "AestheticGradient", "VAE", "Upscaler", "MotionModule", "Wildcards", "Workflows", "Other"] + if scan_choices: + content_list.insert(0, 'All') + return content_list + return content_list + +def get_save_path_and_name(install_path, file_name, api_response, sub_folder=None): + save_to_custom = getattr(opts, "save_to_custom", False) + + name = os.path.splitext(file_name)[0] + if not sub_folder: + sub_folder = os.path.normpath(os.path.relpath(install_path, gl.main_folder)) + image_path = _file.get_image_path(install_path, api_response, sub_folder) + + if save_to_custom: + save_path = image_path + else: + save_path = install_path + + return save_path, name + +def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish, overwrite_toggle, tile_count, gen_hash, create_html, progress=gr.Progress() if queue else None): + global no_update + proxies, ssl = _api.get_proxies() + gl.scan_files = True + no_update = False + if from_ver: + number = _download.random_number(ver_finish) + elif from_tag: + number = _download.random_number(tag_finish) + elif from_installed: + number = _download.random_number(installed_finish) + elif from_preview: + number = _download.random_number(preview_finish) + + if not folders: + if progress != None: + progress(0, desc=f"No model type selected.") + no_update = True + gl.scan_files = False + time.sleep(2) + return (gr.HTML.update(value='
'), + gr.Textbox.update(value=number)) + + folders_to_check = [] + if 'All' in folders: + folders = _file.get_content_choices() + + for item in folders: + if item == "LORA, LoCon, DoRA": + folder = _api.contenttype_folder("LORA") + if folder: + folders_to_check.append(folder) + folder = _api.contenttype_folder("LoCon", fromCheck=True) + if folder: + folders_to_check.append(folder) + folder = _api.contenttype_folder("DoRA") + if folder: + folders_to_check.append(folder) + elif item == "Upscaler": + folder = _api.contenttype_folder(item, "SwinIR") + if folder: + folders_to_check.append(folder) + folder = _api.contenttype_folder(item, "RealESRGAN") + if folder: + folders_to_check.append(folder) + folder = _api.contenttype_folder(item, "GFPGAN") + if folder: + folders_to_check.append(folder) + folder = _api.contenttype_folder(item, "BSRGAN") + if folder: + folders_to_check.append(folder) + folder = _api.contenttype_folder(item, "ESRGAN") + if folder: + folders_to_check.append(folder) + else: + folder = _api.contenttype_folder(item) + if folder: + folders_to_check.append(folder) + + total_files = 0 + files_done = 0 + + files = list_files(folders_to_check) + total_files += len(files) + + if total_files == 0: + if progress != None: + progress(1, desc=f"No files in selected folder.") + no_update = True + gl.scan_files = False + time.sleep(2) + return (gr.HTML.update(value='
'), + gr.Textbox.update(value=number)) + + updated_models = [] + outdated_models = [] + all_model_ids = [] + file_paths = [] + all_ids = [] + + not_found_print = getattr(opts, "civitai_not_found_print", True) + + for file_path in files: + if gl.cancel_status: + if progress != None: + progress(files_done / total_files, desc=f"Processing files cancelled.") + no_update = True + gl.scan_files = False + time.sleep(2) + return (gr.HTML.update(value='
'), + gr.Textbox.update(value=number)) + file_name = os.path.basename(file_path) + if progress != None: + progress(files_done / total_files, desc=f"Processing file: {file_name}") + + model_id = get_models(file_path, gen_hash) + if model_id == "offline": + print("The CivitAI servers did not respond, unable to retrieve Model ID") + elif model_id == "Model not found": + if not_found_print: + print(f"model: \"{file_name}\" not found on CivitAI servers.") + elif model_id != None: + all_model_ids.append(f"&ids={model_id}") + all_ids.append(model_id) + file_paths.append(file_path) + elif not model_id: + print(f"model ID not found for: \"{file_name}\"") + files_done += 1 + + all_items = [] + + all_model_ids = list(set(all_model_ids)) + + if not all_model_ids: + progress(1, desc=f"No model IDs could be retrieved.") + print("Could not retrieve any Model IDs, please make sure to turn on the \"One-Time Hash Generation for externally downloaded models.\" option if you haven't already.") + no_update = True + gl.scan_files = False + time.sleep(2) + return (gr.HTML.update(value='
'), + gr.Textbox.update(value=number)) + + def chunks(lst, n): + for i in range(0, len(lst), n): + yield lst[i:i + n] + + if not from_installed: + model_chunks = list(chunks(all_model_ids, 500)) + + base_url = "https://civitai.com/api/v1/models?limit=100&nsfw=true" + url_list = [f"{base_url}{''.join(chunk)}" for chunk in model_chunks] + + url_count = len(all_model_ids) // 100 + if len(all_model_ids) % 100 != 0: + url_count += 1 + url_done = 0 + api_response = {} + for url in url_list: + while url: + try: + if progress is not None: + progress(url_done / url_count, desc=f"Sending API request... {url_done}/{url_count}") + response = requests.get(url, timeout=(60,30), proxies=proxies, verify=ssl) + if response.status_code == 200: + api_response_json = response.json() + + all_items.extend(api_response_json['items']) + metadata = api_response_json.get('metadata', {}) + url = metadata.get('nextPage', None) + elif response.status_code == 503: + print(f"Error: Received status code: {response.status_code} with URL: {url}") + print(response.text) + return ( + gr.HTML.update(value=_api.api_error_msg("error")), + gr.Textbox.update(value=number) + ) + else: + print(f"Error: Received status code {response.status_code} with URL: {url}") + url = None + url_done += 1 + except requests.exceptions.Timeout: + print(f"Request timed out for {url}. Skipping...") + url = None + except requests.exceptions.ConnectionError: + print("Failed to connect to the API. The servers might be offline.") + url = None + except Exception as e: + print(f"An unexpected error occurred: {e}") + url = None + + api_response['items'] = all_items + if api_response['items'] == []: + return ( + gr.HTML.update(value=_api.api_error_msg("no_items")), + gr.Textbox.update(value=number) + ) + + if progress != None: + progress(1, desc="Processing final results...") + + if from_ver: + updated_models, outdated_models = version_match(file_paths, api_response) + + updated_set = set(updated_models) + outdated_set = set(outdated_models) + outdated_set = {model for model in outdated_set if model[0] not in {updated_model[0] for updated_model in updated_set}} + + all_model_ids = [model[0] for model in outdated_set] + all_model_names = [model[1] for model in outdated_set] + + for model_name in all_model_names: + print(f'"{model_name}" is currently outdated.') + + if len(all_model_ids) == 0: + no_update = True + gl.scan_files = False + return ( + gr.HTML.update(value='
No updates found for selected models.
'), + gr.Textbox.update(value=number) + ) + + model_chunks = list(chunks(all_model_ids, tile_count)) + + base_url = "https://civitai.com/api/v1/models?limit=100&nsfw=true" + gl.url_list = {i+1: f"{base_url}{''.join(chunk)}" for i, chunk in enumerate(model_chunks)} + + if from_ver: + gl.scan_files = False + return ( + gr.HTML.update(value='
Outdated models have been found.
Please press the button above to load the models into the browser tab
'), + gr.Textbox.update(value=number) + ) + + elif from_installed: + gl.scan_files = False + return ( + gr.HTML.update(value='
Installed models have been loaded.
Please press the button above to load the models into the browser tab
'), + gr.Textbox.update(value=number) + ) + + elif from_tag: + completed_tags = 0 + tag_count = len(file_paths) + + for file_path, id_value in zip(file_paths, all_ids): + install_path, file_name = os.path.split(file_path) + save_path, name = get_save_path_and_name(install_path, file_name, api_response) + model_versions = _api.update_model_versions(id_value, api_response) + html_path = os.path.join(save_path, f'{name}.html') + + if create_html and not os.path.exists(html_path) or create_html and overwrite_toggle: + preview_html = _api.update_model_info(None, model_versions.get('value'), True, id_value, api_response, True) + else: + preview_html = None + completed_tags += 1 + if progress != None: + progress(completed_tags / tag_count, desc=f'Saving tags{" & HTML" if preview_html else ""}... {completed_tags}/{tag_count} | {name}') + sub_folder = os.path.normpath(os.path.relpath(install_path, gl.main_folder)) + save_model_info(install_path, file_name, sub_folder, preview_html=preview_html, api_response=api_response, overwrite_toggle=overwrite_toggle) + if progress != None: + progress(1, desc=f"All tags succesfully saved!") + gl.scan_files = False + time.sleep(2) + return ( + gr.HTML.update(value='
'), + gr.Textbox.update(value=number) + ) + + elif from_preview: + completed_preview = 0 + preview_count = len(file_paths) + for file in file_paths: + _, file_name = os.path.split(file) + name = os.path.splitext(file_name)[0] + completed_preview += 1 + if progress != None: + progress(completed_preview / preview_count, desc=f"Saving preview images... {completed_preview}/{preview_count} | {name}") + save_preview(file, api_response, overwrite_toggle) + gl.scan_files = False + return ( + gr.HTML.update(value='
'), + gr.Textbox.update(value=number) + ) + +def finish_returns(): + return ( + gr.Button.update(interactive=True, visible=True), + gr.Button.update(interactive=True, visible=True), + gr.Button.update(interactive=True, visible=True), + gr.Button.update(interactive=True, visible=True), + gr.Button.update(interactive=True, visible=False), # Organize models hidden until implemented + gr.Button.update(interactive=False, visible=False) + ) + +def start_returns(number): + return ( + gr.Textbox.update(value=number), + gr.Button.update(interactive=False, visible=False), + gr.Button.update(interactive=True, visible=True), + gr.Button.update(interactive=False, visible=True), + gr.Button.update(interactive=False, visible=True), + gr.Button.update(interactive=False, visible=True), + gr.Button.update(interactive=False, visible=False), # Organize models hidden until implemented + gr.HTML.update(value='
') + ) + +def set_globals(input_global=None): + global from_tag, from_ver, from_installed, from_preview, from_organize + from_tag = from_ver = from_installed = from_preview = from_organize = False + if input_global == "reset": + return + elif input_global == "from_tag": + from_tag = True + elif input_global == "from_ver": + from_ver = True + elif input_global == "from_installed": + from_installed = True + elif input_global == "from_preview": + from_preview = True + elif input_global == "from_organize": + from_organize = True + +def save_tag_start(tag_start): + set_globals('from_tag') + number = _download.random_number(tag_start) + return start_returns(number) + +def save_preview_start(preview_start): + set_globals('from_preview') + number = _download.random_number(preview_start) + return start_returns(number) + +def installed_models_start(installed_start): + set_globals('from_installed') + number = _download.random_number(installed_start) + return start_returns(number) + +def ver_search_start(ver_start): + set_globals('from_ver') + number = _download.random_number(ver_start) + return start_returns(number) + +def organize_start(organize_start): + set_globals('from_organize') + number = _download.random_number(organize_start) + return start_returns(number) + +def save_tag_finish(): + set_globals("reset") + return finish_returns() + +def save_preview_finish(): + set_globals("reset") + return finish_returns() + +def scan_finish(): + set_globals("reset") + return ( + gr.Button.update(interactive=no_update, visible=no_update), + gr.Button.update(interactive=no_update, visible=no_update), + gr.Button.update(interactive=no_update, visible=no_update), + gr.Button.update(interactive=no_update, visible=no_update), + gr.Button.update(interactive=no_update, visible=False), + gr.Button.update(interactive=False, visible=False), + gr.Button.update(interactive=not no_update, visible=not no_update) + ) + +def load_to_browser(content_type, sort_type, period_type, use_search_term, search_term, tile_count, base_filter, nsfw): + global from_ver, from_installed + + model_list_return = _api.initial_model_page(content_type, sort_type, period_type, use_search_term, search_term, 1, base_filter, False, nsfw, tile_count, True) + from_ver, from_installed = False, False + return ( + *model_list_return, + gr.Button.update(interactive=True, visible=True), + gr.Button.update(interactive=True, visible=True), + gr.Button.update(interactive=True, visible=True), + gr.Button.update(interactive=True, visible=True), + gr.Button.update(interactive=False, visible=False), + gr.Button.update(interactive=False, visible=False), + gr.HTML.update(value='
') + ) + +def cancel_scan(): + gl.cancel_status = True + + while True: + if not gl.scan_files: + gl.cancel_status = False + return + else: + time.sleep(0.5) + continue diff --git a/extensions/sd-civitai-browser-plus/scripts/civitai_global.py b/extensions/sd-civitai-browser-plus/scripts/civitai_global.py new file mode 100644 index 0000000000000000000000000000000000000000..2a6614b91bfff44f14ed3b46c5c82f35faa86449 --- /dev/null +++ b/extensions/sd-civitai-browser-plus/scripts/civitai_global.py @@ -0,0 +1,33 @@ +from modules.shared import opts +do_debug_print = getattr(opts, "civitai_debug_prints", False) +def init(): + import warnings + from urllib3.exceptions import InsecureRequestWarning + warnings.simplefilter('ignore', InsecureRequestWarning) + + global download_queue, last_version, cancel_status, recent_model, last_url, json_data, json_info, main_folder, previous_inputs, download_fail, sortNewest, isDownloading, old_download, scan_files, from_update_tab, url_list, print + + cancel_status = None + recent_model = None + json_data = None + json_info = None + main_folder = None + previous_inputs = None + last_version = None + url_list = {} + download_queue = [] + + from_update_tab = False + scan_files = False + download_fail = False + sortNewest = False + isDownloading = False + old_download = False + +_print = print +def print(print_message): + _print(f'\033[96mCivitAI Browser+\033[0m: {print_message}') + +def debug_print(print_message): + if do_debug_print: + _print(f'\033[96m[DEBUG] CivitAI Browser+\033[0m: {print_message}') \ No newline at end of file diff --git a/extensions/sd-civitai-browser-plus/scripts/civitai_gui.py b/extensions/sd-civitai-browser-plus/scripts/civitai_gui.py new file mode 100644 index 0000000000000000000000000000000000000000..67bcc799123f8e4153573f2ee06a7854e26f2eb1 --- /dev/null +++ b/extensions/sd-civitai-browser-plus/scripts/civitai_gui.py @@ -0,0 +1,1411 @@ +import gradio as gr +from modules import script_callbacks, shared +import os +import json +import fnmatch +import re +import subprocess +from modules.shared import opts, cmd_opts +from modules.paths import extensions_dir +from scripts.civitai_global import print, debug_print +import scripts.civitai_global as gl +import scripts.civitai_download as _download +import scripts.civitai_file_manage as _file +import scripts.civitai_api as _api + +def git_tag(): + try: + return subprocess.check_output([os.environ.get('GIT', "git"), "describe", "--tags"], shell=False, encoding='utf8').strip() + except: + return None + +try: + import modules_forge + forge = True + ver_bool = True +except ImportError: + forge = False + +if not forge: + try: + from packaging import version + ver = git_tag() + + if not ver: + try: + from modules import launch_utils + ver = launch_utils.git_tag() + except: + ver_bool = False + if ver: + ver = ver.split('-')[0].rsplit('-', 1)[0] + ver_bool = version.parse(ver[0:]) >= version.parse("1.7") + except ImportError: + print("Python module 'packaging' has not been imported correctly, please try to restart or install it manually.") + ver_bool = False + +gl.init() + +def saveSettings(ust, ct, pt, st, bf, cj, td, ol, hi, sn, ss, ts): + config = cmd_opts.ui_config_file + + # Create a dictionary to map the settings to their respective variables + settings_map = { + "civitai_interface/Search type:/value": ust, + "civitai_interface/Content type:/value": ct, + "civitai_interface/Time period:/value": pt, + "civitai_interface/Sort by:/value": st, + "civitai_interface/Base model:/value": bf, + "civitai_interface/Save info after download/value": cj, + "civitai_interface/Divide cards by date/value": td, + "civitai_interface/Liked models only/value": ol, + "civitai_interface/Hide installed models/value": hi, + "civitai_interface/NSFW content/value": sn, + "civitai_interface/Tile size:/value": ss, + "civitai_interface/Tile count:/value": ts + } + + # Load the current contents of the config file into a dictionary + try: + with open(config, "r", encoding="utf8") as file: + data = json.load(file) + except: + print(f"Cannot save settings, failed to open \"{file}\"") + print("Please try to manually repair the file or remove it to reset settings.") + return + + # Remove any keys containing the text `civitai_interface` + keys_to_remove = [key for key in data if "civitai_interface" in key] + for key in keys_to_remove: + del data[key] + + # Update the dictionary with the new settings + data.update(settings_map) + + # Save the modified content back to the file + with open(config, 'w', encoding="utf-8") as file: + json.dump(data, file, indent=4) + print(f"Updated settings to: {config}") + +def all_visible(html_check): + return gr.Button.update(visible="model-checkbox" in html_check) + +def show_multi_buttons(model_list, type_list, version_value): + model_list = json.loads(model_list) + type_list = json.loads(type_list) + otherButtons = True + multi_file_subfolder = False + default_subfolder = "Only available if the selected files are of the same model type" + sub_folders = ["None"] + BtnDwn = version_value and not version_value.endswith('[Installed]') and not model_list + BtnDel = version_value.endswith('[Installed]') + + dot_subfolders = getattr(opts, "dot_subfolders", True) + + multi = bool(model_list) and not len(gl.download_queue) > 0 + if model_list: + otherButtons = False + if type_list and all(x == type_list[0] for x in type_list): + multi_file_subfolder = True + model_folder = os.path.join(_api.contenttype_folder(type_list[0])) + default_subfolder = "None" + try: + for root, dirs, _ in os.walk(model_folder, followlinks=True): + if dot_subfolders: + dirs = [d for d in dirs if not d.startswith('.')] + dirs = [d for d in dirs if not any(part.startswith('.') for part in os.path.join(root, d).split(os.sep))] + for d in dirs: + sub_folder = os.path.relpath(os.path.join(root, d), model_folder) + if sub_folder: + sub_folders.append(f'{os.sep}{sub_folder}') + sub_folders.remove("None") + sub_folders = sorted(sub_folders, key=lambda x: (x.lower(), x)) + sub_folders.insert(0, "None") + + list = set() + sub_folders = [x for x in sub_folders if not (x in list or list.add(x))] + except: + sub_folders = ["None"] + + return (gr.Button.update(visible=multi, interactive=multi), # Download Multi Button + gr.Button.update(visible=BtnDwn if multi else True if not version_value.endswith('[Installed]') else False), # Download Button + gr.Button.update(visible=BtnDel if not model_list else False), # Delete Button + gr.Button.update(visible=otherButtons), # Save model info Button + gr.Button.update(visible=otherButtons), # Save images Button + gr.Dropdown.update(visible=multi, interactive=multi_file_subfolder, choices=sub_folders, value=default_subfolder) # Selected type sub folder + ) + +def txt2img_output(image_url): + clean_url = image_url[4:] + geninfo = _api.fetch_and_process_image(clean_url) + if geninfo: + nr = _download.random_number() + geninfo = nr + geninfo + return gr.Textbox.update(value=geninfo) + +def on_ui_tabs(): + page_header = getattr(opts, "page_header", False) + lobe_directory = None + + for root, dirs, files in os.walk(extensions_dir, followlinks=True): + for dir_name in fnmatch.filter(dirs, '*lobe*'): + lobe_directory = os.path.join(root, dir_name) + break + + # Different ID's for Lobe Theme + component_id = "togglesL" if lobe_directory else "toggles" + toggle1 = "toggle1L" if lobe_directory else "toggle1" + toggle2 = "toggle2L" if lobe_directory else "toggle2" + toggle3 = "toggle3L" if lobe_directory else "toggle3" + toggle5 = "toggle5L" if lobe_directory else "toggle5" + refreshbtn = "refreshBtnL" if lobe_directory else "refreshBtn" + filterBox = "filterBoxL" if lobe_directory else "filterBox" + + if page_header: + header = "headerL" if lobe_directory else "header" + else: + header = "header_off" + + api_key = getattr(opts, "custom_api_key", "") + if api_key: + toggle4 = "toggle4L_api" if lobe_directory else "toggle4_api" + show_only_liked = True + else: + toggle4 = "toggle4L" if lobe_directory else "toggle4" + show_only_liked = False + + content_choices = _file.get_content_choices() + scan_choices = _file.get_content_choices(scan_choices=True) + with gr.Blocks() as civitai_interface: + with gr.Tab(label="Browser", elem_id="browserTab"): + with gr.Row(elem_id="searchRow"): + with gr.Accordion(label="", open=False, elem_id=filterBox): + with gr.Row(): + use_search_term = gr.Radio(label="Search type:", choices=["Model name", "User name", "Tag"], value="Model name", elem_id="searchType") + with gr.Row(): + content_type = gr.Dropdown(label='Content type:', choices=content_choices, value=None, type="value", multiselect=True, elem_id="centerText") + with gr.Row(): + base_filter = gr.Dropdown(label='Base model:', multiselect=True, choices=["SD 1.4","SD 1.5","SD 1.5 LCM","SD 2.0","SD 2.0 768","SD 2.1","SD 2.1 768","SD 2.1 Unclip","SDXL 0.9","SDXL 1.0","SDXL 1.0 LCM","SDXL Distilled","SDXL Turbo","SDXL Lightning","Stable Cascade","Pony","SVD","SVD XT","Playground v2","PixArt a","Other"], value=None, type="value", elem_id="centerText") + with gr.Row(): + period_type = gr.Dropdown(label='Time period:', choices=["All Time", "Year", "Month", "Week", "Day"], value="All Time", type="value", elem_id="centerText") + sort_type = gr.Dropdown(label='Sort by:', choices=["Newest","Oldest","Most Downloaded","Highest Rated","Most Liked","Most Buzz","Most Discussed","Most Collected","Most Images"], value="Most Downloaded", type="value", elem_id="centerText") + with gr.Row(elem_id=component_id): + create_json = gr.Checkbox(label=f"Save info after download", value=True, elem_id=toggle1, min_width=171) + show_nsfw = gr.Checkbox(label="NSFW content", value=False, elem_id=toggle2, min_width=107) + toggle_date = gr.Checkbox(label="Divide cards by date", value=False, elem_id=toggle3, min_width=142) + only_liked = gr.Checkbox(label="Liked models only", value=False, interactive=show_only_liked, elem_id=toggle4, min_width=163) + hide_installed = gr.Checkbox(label="Hide installed models", value=False, elem_id=toggle5, min_width=170) + with gr.Row(): + size_slider = gr.Slider(minimum=4, maximum=20, value=8, step=0.25, label='Tile size:') + tile_count_slider = gr.Slider(label="Tile count:", minimum=1, maximum=100, value=15, step=1) + with gr.Row(elem_id="save_set_box"): + save_settings = gr.Button(value="Save settings as default", elem_id="save_set_btn") + search_term = gr.Textbox(label="", placeholder="Search CivitAI", elem_id="searchBox") + refresh = gr.Button(label="", value="", elem_id=refreshbtn, icon="placeholder") + with gr.Row(elem_id=header): + with gr.Row(elem_id="pageBox"): + get_prev_page = gr.Button(value="Prev page", interactive=False, elem_id="pageBtn1") + page_slider = gr.Slider(label='Current page:', step=1, minimum=1, maximum=1, min_width=80, elem_id="pageSlider") + get_next_page = gr.Button(value="Next page", interactive=False, elem_id="pageBtn2") + with gr.Row(elem_id="pageBoxMobile"): + pass # Row used for button placement on mobile + with gr.Row(elem_id="select_all_models_container"): + select_all = gr.Button(value="Select All", elem_id="select_all_models", visible=False) + with gr.Row(): + list_html = gr.HTML(value='
Click the search icon to load models.
Use the filter icon to filter results.
') + with gr.Row(): + download_progress = gr.HTML(value='
', elem_id="DownloadProgress") + with gr.Row(): + list_models = gr.Dropdown(label="Model:", choices=[], interactive=False, elem_id="quicksettings1", value=None) + list_versions = gr.Dropdown(label="Version:", choices=[], interactive=False, elem_id="quicksettings", value=None) + file_list = gr.Dropdown(label="File:", choices=[], interactive=False, elem_id="file_list", value=None) + with gr.Row(): + with gr.Column(scale=4): + install_path = gr.Textbox(label="Download folder:", interactive=False, max_lines=1) + with gr.Column(scale=2): + sub_folder = gr.Dropdown(label="Sub folder:", choices=[], interactive=False, value=None) + with gr.Row(): + with gr.Column(scale=4): + trained_tags = gr.Textbox(label='Trained tags (if any):', value=None, interactive=False, lines=1) + with gr.Column(scale=2, elem_id="spanWidth"): + base_model = gr.Textbox(label='Base model: ', value=None, interactive=False, lines=1, elem_id="baseMdl") + model_filename = gr.Textbox(label="Model filename:", interactive=False, value=None) + with gr.Row(): + save_info = gr.Button(value="Save model info", interactive=False) + save_images = gr.Button(value="Save images", interactive=False) + delete_model = gr.Button(value="Delete model", interactive=False, visible=False) + download_model = gr.Button(value="Download model", interactive=False) + subfolder_selected = gr.Dropdown(label="Sub folder for selected files:", choices=[], interactive=False, visible=False, value=None, allow_custom_value=True) + download_selected = gr.Button(value="Download all selected", interactive=False, visible=False, elem_id="download_all_button") + with gr.Row(): + cancel_all_model = gr.Button(value="Cancel all downloads", interactive=False, visible=False) + cancel_model = gr.Button(value="Cancel current download", interactive=False, visible=False) + with gr.Row(): + preview_html = gr.HTML(elem_id="civitai_preview_html") + with gr.Row(elem_id="backToTopContainer"): + back_to_top = gr.Button(value="↑", elem_id="backToTop") + with gr.Tab("Update Models"): + with gr.Row(): + selected_tags = gr.CheckboxGroup(elem_id="selected_tags", label="Selected content types:", choices=scan_choices) + with gr.Row(elem_id="civitai_update_toggles"): + overwrite_toggle = gr.Checkbox(elem_id="overwrite_toggle", label="Overwrite any existing files. (previews, HTMLs, tags, descriptions)", value=True, min_width=300) + skip_hash_toggle = gr.Checkbox(elem_id="skip_hash_toggle", label="One-Time Hash Generation for externally downloaded models.", value=True, min_width=300) + do_html_gen = gr.Checkbox(elem_id="do_html_gen", label="Save HTML file for each model when updating info & tags (increases process time).", value=False, min_width=300) + with gr.Row(): + save_all_tags = gr.Button(value="Update model info & tags", interactive=True, visible=True) + cancel_all_tags = gr.Button(value="Cancel updating model info & tags", interactive=False, visible=False) + with gr.Row(): + tag_progress = gr.HTML(value='
') + with gr.Row(): + update_preview = gr.Button(value="Update model preview", interactive=True, visible=True) + cancel_update_preview = gr.Button(value="Cancel updating model previews", interactive=False, visible=False) + with gr.Row(): + preview_progress = gr.HTML(value='
') + with gr.Row(): + ver_search = gr.Button(value="Scan for available updates", interactive=True, visible=True) + cancel_ver_search = gr.Button(value="Cancel updates scan", interactive=False, visible=False) + load_to_browser = gr.Button(value="Load outdated models to browser", interactive=False, visible=False) + with gr.Row(): + version_progress = gr.HTML(value='
') + with gr.Row(): + load_installed = gr.Button(value="Load all installed models", interactive=True, visible=True) + cancel_installed = gr.Button(value="Cancel loading models", interactive=False, visible=False) + load_to_browser_installed = gr.Button(value="Load installed models to browser", interactive=False, visible=False) + with gr.Row(): + installed_progress = gr.HTML(value='
') + with gr.Row(): + organize_models = gr.Button(value="Organize model files", interactive=True, visible=False) # Organize models hidden until implemented + cancel_organize = gr.Button(value="Cancel loading models", interactive=False, visible=False) + with gr.Row(): + organize_progress = gr.HTML(value='
') + with gr.Tab("Download Queue"): + + def get_style(size, left_border): + return f"flex-grow: {size};" + ("border-left: 1px solid var(--border-color-primary);" if left_border else "") + "border-bottom: 1px solid var(--border-color-primary);padding: 5px 10px 5px 10px;width: 0;" + + download_manager_html = gr.HTML(elem_id="civitai_dl_list", value=f''' +
+
Model:
+
Version:
+
Path:
+
Status:
+
Action:
+
+
+
+ In queue: (drag items to rearrange queue order) +
+
+ ''') + + #Invisible triggers/variables + #Yes, there is probably a much better way of passing variables/triggering functions + + model_id = gr.Textbox(visible=False) + queue_trigger = gr.Textbox(visible=False) + dl_url = gr.Textbox(visible=False) + civitai_text2img_output = gr.Textbox(visible=False) + civitai_text2img_input = gr.Textbox(elem_id="civitai_text2img_input", visible=False) + page_slider_trigger = gr.Textbox(elem_id="page_slider_trigger", visible=False) + selected_model_list = gr.Textbox(elem_id="selected_model_list", visible=False) + selected_type_list = gr.Textbox(elem_id="selected_type_list", visible=False) + html_cancel_input = gr.Textbox(elem_id="html_cancel_input", visible=False) + queue_html_input = gr.Textbox(elem_id="queue_html_input", visible=False) + send_to_browser = gr.Textbox(elem_id="send_to_browser", visible=False) + arrange_dl_id = gr.Textbox(elem_id="arrange_dl_id", visible=False) + remove_dl_id = gr.Textbox(elem_id="remove_dl_id", visible=False) + model_select = gr.Textbox(elem_id="model_select", visible=False) + model_sent = gr.Textbox(elem_id="model_sent", visible=False) + type_sent = gr.Textbox(elem_id="type_sent", visible=False) + click_first_item = gr.Textbox(visible=False) + empty = gr.Textbox(value="", visible=False) + download_start = gr.Textbox(visible=False) + download_finish = gr.Textbox(visible=False) + tag_start = gr.Textbox(visible=False) + tag_finish = gr.Textbox(visible=False) + preview_start = gr.Textbox(visible=False) + preview_finish = gr.Textbox(visible=False) + ver_start = gr.Textbox(visible=False) + ver_finish = gr.Textbox(visible=False) + installed_start = gr.Textbox(visible=None) + installed_finish = gr.Textbox(visible=None) + organize_start = gr.Textbox(visible=None) + organize_finish = gr.Textbox(visible=None) + delete_finish = gr.Textbox(visible=False) + current_model = gr.Textbox(visible=False) + current_sha256 = gr.Textbox(visible=False) + model_preview_html = gr.Textbox(visible=False) + + def ToggleDate(toggle_date): + gl.sortNewest = toggle_date + + def select_subfolder(sub_folder): + if sub_folder == "None" or sub_folder == "Only available if the selected files are of the same model type": + newpath = gl.main_folder + else: + newpath = gl.main_folder + sub_folder + return gr.Textbox.update(value=newpath) + + # Javascript Functions # + + list_html.change(fn=None, inputs=hide_installed, _js="(toggleValue) => hideInstalled(toggleValue)") + hide_installed.input(fn=None, inputs=hide_installed, _js="(toggleValue) => hideInstalled(toggleValue)") + + civitai_text2img_output.change(fn=None, inputs=civitai_text2img_output, _js="(genInfo) => genInfo_to_txt2img(genInfo)") + + download_selected.click(fn=None, _js="() => deselectAllModels()") + + select_all.click(fn=None, _js="() => selectAllModels()") + + list_models.select(fn=None, inputs=list_models, _js="(list_models) => select_model(list_models)") + + preview_html.change(fn=None, _js="() => adjustFilterBoxAndButtons()") + preview_html.change(fn=None, _js="() => setDescriptionToggle()") + + back_to_top.click(fn=None, _js="() => BackToTop()") + + page_slider.release(fn=None, _js="() => pressRefresh()") + + card_updates = [queue_trigger, download_finish, delete_finish] + for func in card_updates: + func.change(fn=None, inputs=current_model, _js="(modelName) => updateCard(modelName)") + + list_html.change(fn=None, inputs=show_nsfw, _js="(hideAndBlur) => toggleNSFWContent(hideAndBlur)") + show_nsfw.change(fn=None, inputs=show_nsfw, _js="(hideAndBlur) => toggleNSFWContent(hideAndBlur)") + + list_html.change(fn=None, inputs=size_slider, _js="(size) => updateCardSize(size, size * 1.5)") + size_slider.change(fn=None, inputs=size_slider, _js="(size) => updateCardSize(size, size * 1.5)") + + model_preview_html.change(fn=None, inputs=model_preview_html, _js="(html_input) => inputHTMLPreviewContent(html_input)") + + download_manager_html.change(fn=None, _js="() => setSortable()") + + click_first_item.change(fn=None, _js="() => clickFirstFigureInColumn()") + + # Filter button Functions # + + def HTMLChange(input): + return gr.HTML.update(value=input) + + queue_html_input.change(fn=HTMLChange, inputs=[queue_html_input], outputs=download_manager_html) + + remove_dl_id.change( + fn=_download.remove_from_queue, + inputs=[remove_dl_id] + ) + + arrange_dl_id.change( + fn=_download.arrange_queue, + inputs=[arrange_dl_id] + ) + + html_cancel_input.change( + fn=_download.download_cancel + ) + + html_cancel_input.change(fn=None, _js="() => cancelCurrentDl()") + + save_settings.click( + fn=saveSettings, + inputs=[ + use_search_term, + content_type, + period_type, + sort_type, + base_filter, + create_json, + toggle_date, + only_liked, + hide_installed, + show_nsfw, + size_slider, + tile_count_slider + ] + ) + + toggle_date.input( + fn=ToggleDate, + inputs=[toggle_date] + ) + + # Model Button Functions # + + civitai_text2img_input.change(fn=txt2img_output,inputs=civitai_text2img_input,outputs=civitai_text2img_output) + + list_html.change(fn=all_visible,inputs=list_html,outputs=select_all) + + def update_models_dropdown(input): + if not gl.json_data: + return ( + gr.Dropdown.update(value=None, choices=[], interactive=False), # List models + gr.Dropdown.update(value=None, choices=[], interactive=False), # List version + gr.HTML.update(value=None), # Preview HTML + gr.Textbox.update(value=None, interactive=False), # Trained Tags + gr.Textbox.update(value=None, interactive=False), # Base Model + gr.Textbox.update(value=None, interactive=False), # Model filename + gr.Textbox.update(value=None, interactive=False), # Install path + gr.Dropdown.update(value=None, choices=[], interactive=False), # Sub folder + gr.Button.update(interactive=False), # Download model btn + gr.Button.update(interactive=False), # Save image btn + gr.Button.update(interactive=False, visible=False), # Delete model btn + gr.Dropdown.update(value=None, choices=[], interactive=False), # File list + gr.Textbox.update(value=None), # DL Url + gr.Textbox.update(value=None), # Model ID + gr.Textbox.update(value=None), # Current sha256 + gr.Button.update(interactive=False), # Save model info + gr.HTML.update(value='
Click the search icon to load models.
Use the filter icon to filter results.
') # Model list + ) + + model_string = re.sub(r'\.\d{3}$', '', input) + model_name, model_id = _api.extract_model_info(model_string) + model_versions = _api.update_model_versions(model_id) + (html, tags, base_mdl, DwnButton, SaveImages, DelButton, filelist, filename, dl_url, id, current_sha256, install_path, sub_folder) = _api.update_model_info(model_string, model_versions.get('value')) + return (gr.Dropdown.update(value=model_string, interactive=True), + model_versions,html,tags,base_mdl,filename,install_path,sub_folder,DwnButton,SaveImages,DelButton,filelist,dl_url,id,current_sha256, + gr.Button.update(interactive=True), + gr.HTML.update() + ) + + model_select.change( + fn=update_models_dropdown, + inputs=[model_select], + outputs=[ + list_models, + list_versions, + preview_html, + trained_tags, + base_model, + model_filename, + install_path, + sub_folder, + download_model, + save_images, + delete_model, + file_list, + dl_url, + model_id, + current_sha256, + save_info, + list_html + ] + ) + + model_sent.change( + fn=_file.model_from_sent, + inputs=[model_sent, type_sent], + outputs=[model_preview_html] + ) + + send_to_browser.change( + fn=_file.send_to_browser, + inputs=[send_to_browser, type_sent, click_first_item], + outputs=[list_html, get_prev_page , get_next_page, page_slider, click_first_item] + ) + + sub_folder.select( + fn=select_subfolder, + inputs=[sub_folder], + outputs=[install_path] + ) + + list_versions.select( + fn=_api.update_model_info, + inputs=[ + list_models, + list_versions + ], + outputs=[ + preview_html, + trained_tags, + base_model, + download_model, + save_images, + delete_model, + file_list, + model_filename, + dl_url, + model_id, + current_sha256, + install_path, + sub_folder + ] + ) + + file_list.input( + fn=_api.update_file_info, + inputs=[ + list_models, + list_versions, + file_list + ], + outputs=[ + model_filename, + dl_url, + model_id, + current_sha256, + download_model, + delete_model, + install_path, + sub_folder + ] + ) + + # Download/Save Model Button Functions # + + selected_model_list.change( + fn=show_multi_buttons, + inputs=[selected_model_list, selected_type_list, list_versions], + outputs=[ + download_selected, + download_model, + delete_model, + save_info, + save_images, + subfolder_selected + ] + ) + + download_model.click( + fn=_download.download_start, + inputs=[ + download_start, + dl_url, + model_filename, + install_path, + list_models, + list_versions, + current_sha256, + model_id, + create_json, + download_manager_html + ], + outputs=[ + download_model, + cancel_model, + cancel_all_model, + download_start, + download_progress, + download_manager_html + ] + ) + + download_selected.click( + fn=_download.selected_to_queue, + inputs=[ + selected_model_list, + subfolder_selected, + download_start, + create_json, + download_manager_html + ], + outputs=[ + download_model, + cancel_model, + cancel_all_model, + download_start, + download_progress, + download_manager_html + ] + ) + + + for component in [download_start, queue_trigger]: + component.change(fn=None, _js="() => setDownloadProgressBar()") + component.change( + fn=_download.download_create_thread, + inputs=[download_finish, queue_trigger], + outputs=[ + download_progress, + current_model, + download_finish, + queue_trigger + ] + ) + + download_finish.change( + fn=_download.download_finish, + inputs=[ + model_filename, + list_versions, + model_id + ], + outputs=[ + download_model, + cancel_model, + cancel_all_model, + delete_model, + download_progress, + list_versions + ] + ) + + cancel_model.click(_download.download_cancel) + cancel_all_model.click(_download.download_cancel_all) + + cancel_model.click(fn=None, _js="() => cancelCurrentDl()") + cancel_all_model.click(fn=None, _js="() => cancelAllDl()") + + delete_model.click( + fn=_file.delete_model, + inputs=[ + delete_finish, + model_filename, + list_models, + list_versions, + current_sha256, + selected_model_list + ], + outputs=[ + download_model, + cancel_model, + delete_model, + delete_finish, + current_model, + list_versions + ] + ) + + save_info.click( + fn=_file.save_model_info, + inputs=[ + install_path, + model_filename, + sub_folder, + current_sha256, + preview_html + ], + outputs=[] + ) + + save_images.click( + fn=_file.save_images, + inputs=[ + preview_html, + model_filename, + install_path, + sub_folder + ], + outputs=[] + ) + + # Common input&output lists # + + page_inputs = [ + content_type, + sort_type, + period_type, + use_search_term, + search_term, + page_slider, + base_filter, + only_liked, + show_nsfw, + tile_count_slider + ] + + refresh_inputs = [empty if item == page_slider else item for item in page_inputs] + + page_outputs = [ + list_models, + list_versions, + list_html, + get_prev_page, + get_next_page, + page_slider, + save_info, + save_images, + download_model, + delete_model, + install_path, + sub_folder, + file_list, + preview_html, + trained_tags, + base_model, + model_filename + ] + + file_scan_inputs = [ + selected_tags, + ver_finish, + tag_finish, + installed_finish, + preview_finish, + overwrite_toggle, + tile_count_slider, + skip_hash_toggle, + do_html_gen + ] + + load_to_browser_inputs = [ + content_type, + sort_type, + period_type, + use_search_term, + search_term, + tile_count_slider, + base_filter, + show_nsfw + ] + + cancel_btn_list = [cancel_all_tags,cancel_ver_search,cancel_installed,cancel_update_preview] + + browser = [ver_search,save_all_tags,load_installed,update_preview] + + browser_installed_load = [cancel_installed,load_to_browser_installed,installed_progress] + browser_load = [cancel_ver_search,load_to_browser,version_progress] + + browser_installed_list = page_outputs + browser + browser_installed_load + browser_list = page_outputs + browser + browser_load + + # Page Button Functions # + + page_btn_list = { + refresh.click: (_api.initial_model_page, True), + search_term.submit: (_api.initial_model_page, True), + page_slider_trigger.change: (_api.initial_model_page, False), + get_next_page.click: (_api.next_model_page, False), + get_prev_page.click: (_api.prev_model_page, False) + } + + for trigger, (function, use_refresh_inputs) in page_btn_list.items(): + inputs_to_use = refresh_inputs if use_refresh_inputs else page_inputs + trigger(fn=function, inputs=inputs_to_use, outputs=page_outputs) + trigger(fn=None, _js="() => multi_model_select()") + + for button in cancel_btn_list: + button.click(fn=_file.cancel_scan) + + # Update model Functions # + + ver_search.click( + fn=_file.ver_search_start, + inputs=[ver_start], + outputs=[ + ver_start, + ver_search, + cancel_ver_search, + load_installed, + save_all_tags, + update_preview, + organize_models, + version_progress + ] + ) + + ver_start.change( + fn=_file.file_scan, + inputs=file_scan_inputs, + outputs=[ + version_progress, + ver_finish + ] + ) + + ver_finish.change( + fn=_file.scan_finish, + outputs=[ + ver_search, + save_all_tags, + load_installed, + update_preview, + organize_models, + cancel_ver_search, + load_to_browser + ] + ) + + load_installed.click( + fn=_file.installed_models_start, + inputs=[installed_start], + outputs=[ + installed_start, + load_installed, + cancel_installed, + ver_search, + save_all_tags, + update_preview, + organize_models, + installed_progress + ] + ) + + installed_start.change( + fn=_file.file_scan, + inputs=file_scan_inputs, + outputs=[ + installed_progress, + installed_finish + ] + ) + + installed_finish.change( + fn=_file.scan_finish, + outputs=[ + ver_search, + save_all_tags, + load_installed, + update_preview, + organize_models, + cancel_installed, + load_to_browser_installed + ] + ) + + save_all_tags.click( + fn=_file.save_tag_start, + inputs=[tag_start], + outputs=[ + tag_start, + save_all_tags, + cancel_all_tags, + load_installed, + ver_search, + update_preview, + organize_models, + tag_progress + ] + ) + + tag_start.change( + fn=_file.file_scan, + inputs=file_scan_inputs, + outputs=[ + tag_progress, + tag_finish + ] + ) + + tag_finish.change( + fn=_file.save_tag_finish, + outputs=[ + ver_search, + save_all_tags, + load_installed, + update_preview, + organize_models, + cancel_all_tags + ] + ) + + update_preview.click( + fn=_file.save_preview_start, + inputs=[preview_start], + outputs=[ + preview_start, + update_preview, + cancel_update_preview, + load_installed, + ver_search, + save_all_tags, + organize_models, + preview_progress + ] + ) + + preview_start.change( + fn=_file.file_scan, + inputs=file_scan_inputs, + outputs=[ + preview_progress, + preview_finish + ] + ) + + preview_finish.change( + fn=_file.save_preview_finish, + outputs=[ + ver_search, + save_all_tags, + load_installed, + update_preview, + organize_models, + cancel_update_preview + ] + ) + + organize_models.click( + fn=_file.organize_start, + inputs=[organize_start], + outputs=[ + organize_start, + organize_models, + cancel_organize, + load_installed, + ver_search, + save_all_tags, + update_preview, + organize_progress + ] + ) + + organize_start.change( + fn=_file.file_scan, + inputs=file_scan_inputs, + outputs=[ + organize_progress, + organize_finish + ] + ) + + organize_finish.change( + fn=_file.save_preview_finish, + outputs=[ + ver_search, + save_all_tags, + load_installed, + update_preview, + organize_models, + cancel_update_preview + ] + ) + + + load_to_browser_installed.click( + fn=_file.load_to_browser, + inputs=load_to_browser_inputs, + outputs=browser_installed_list + ) + + load_to_browser.click( + fn=_file.load_to_browser, + inputs=load_to_browser_inputs, + outputs=browser_list + ) + + if ver_bool: + tab_name = "CivitAI Browser+" + else: + tab_name = "Civitai Browser+" + + return (civitai_interface, tab_name, "civitai_interface"), + +def subfolder_list(folder, desc=None): + insert_sub_1 = getattr(opts, "insert_sub_1", False) + insert_sub_2 = getattr(opts, "insert_sub_2", False) + insert_sub_3 = getattr(opts, "insert_sub_3", False) + insert_sub_4 = getattr(opts, "insert_sub_4", False) + insert_sub_5 = getattr(opts, "insert_sub_5", False) + insert_sub_6 = getattr(opts, "insert_sub_6", False) + insert_sub_7 = getattr(opts, "insert_sub_7", False) + insert_sub_8 = getattr(opts, "insert_sub_8", False) + insert_sub_9 = getattr(opts, "insert_sub_9", False) + insert_sub_10 = getattr(opts, "insert_sub_10", False) + insert_sub_11 = getattr(opts, "insert_sub_11", False) + insert_sub_12 = getattr(opts, "insert_sub_12", False) + insert_sub_13 = getattr(opts, "insert_sub_13", False) + insert_sub_14 = getattr(opts, "insert_sub_14", False) + dot_subfolders = getattr(opts, "dot_subfolders", True) + + if folder == None: + return + try: + model_folder = _api.contenttype_folder(folder, desc) + sub_folders = ["None"] + for root, dirs, _ in os.walk(model_folder, followlinks=True): + if dot_subfolders: + dirs = [d for d in dirs if not d.startswith('.')] + dirs = [d for d in dirs if not any(part.startswith('.') for part in os.path.join(root, d).split(os.sep))] + for d in dirs: + sub_folder = os.path.relpath(os.path.join(root, d), model_folder) + if sub_folder: + sub_folders.append(f'{os.sep}{sub_folder}') + + sub_folders.remove("None") + sub_folders = sorted(sub_folders, key=lambda x: (x.lower(), x)) + sub_folders.insert(0, "None") + if insert_sub_1: + sub_folders.insert(1, f"{os.sep}Base model") + if insert_sub_2: + sub_folders.insert(2, f"{os.sep}Base model{os.sep}Author name") + if insert_sub_3: + sub_folders.insert(3, f"{os.sep}Base model{os.sep}Author name{os.sep}Model name") + if insert_sub_4: + sub_folders.insert(4, f"{os.sep}Base model{os.sep}Author name{os.sep}Model name{os.sep}Model version") + if insert_sub_5: + sub_folders.insert(5, f"{os.sep}Base model{os.sep}Model name") + if insert_sub_6: + sub_folders.insert(6, f"{os.sep}Base model{os.sep}Model name{os.sep}Model version") + if insert_sub_7: + sub_folders.insert(7, f"{os.sep}Author name") + if insert_sub_8: + sub_folders.insert(8, f"{os.sep}Author name{os.sep}Base model") + if insert_sub_9: + sub_folders.insert(9, f"{os.sep}Author name{os.sep}Base model{os.sep}Model name") + if insert_sub_10: + sub_folders.insert(10, f"{os.sep}Author name{os.sep}Base model{os.sep}Model name{os.sep}Model version") + if insert_sub_11: + sub_folders.insert(11, f"{os.sep}Author name{os.sep}Model name") + if insert_sub_12: + sub_folders.insert(12, f"{os.sep}Author name{os.sep}Model name{os.sep}Model version") + if insert_sub_13: + sub_folders.insert(13, f"{os.sep}Model name") + if insert_sub_14: + sub_folders.insert(14, f"{os.sep}Model name{os.sep}Model version") + + list = set() + sub_folders = [x for x in sub_folders if not (x in list or list.add(x))] + except: + return None + return sub_folders + +def make_lambda(folder, desc): + return lambda: {"choices": subfolder_list(folder, desc)} + +def on_ui_settings(): + if ver_bool: + browser = ("civitai_browser", "Browser") + download = ("civitai_browser_download", "Downloads") + from modules.options import categories + categories.register_category("civitai_browser_plus", "CivitAI Browser+") + cat_id = "civitai_browser_plus" + else: + section = ("civitai_browser_plus", "CivitAI Browser+") + browser = download = section + if not (hasattr(shared.OptionInfo, "info") and callable(getattr(shared.OptionInfo, "info"))): + def info(self, info): + self.label += f" ({info})" + return self + shared.OptionInfo.info = info + + # Download Options + shared.opts.add_option( + "use_aria2", + shared.OptionInfo( + True, + "Download models using Aria2", + section=download, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Disable this option if you're experiencing any issues with downloads or if you want to use a proxy.") + ) + + shared.opts.add_option( + "disable_dns", + shared.OptionInfo( + False, + "Disable Async DNS for Aria2", + section=download, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Useful for users who use PortMaster or other software that controls the DNS") + ) + + shared.opts.add_option( + "show_log", + shared.OptionInfo( + False, + "Show Aria2 logs in console", + section=download, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Requires UI reload") + ) + + shared.opts.add_option( + "split_aria2", + shared.OptionInfo( + 64, + "Number of connections to use for downloading a model", + gr.Slider, + lambda: {"maximum": "64", "minimum": "1", "step": "1"}, + section=download, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Only applies to Aria2") + ) + + shared.opts.add_option( + "aria2_flags", + shared.OptionInfo( + r"", + "Custom Aria2 command line flags", + section=download, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Requires UI reload") + ) + + shared.opts.add_option( + "unpack_zip", + shared.OptionInfo( + False, + "Automatically unpack .zip files after downloading", + section=download, + **({'category_id': cat_id} if ver_bool else {}) + ) + ) + + shared.opts.add_option( + "save_api_info", + shared.OptionInfo( + False, + "Save API info of model when saving model info", + section=download, + **({'category_id': cat_id} if ver_bool else {}) + ).info("creates an api_info.json file when saving any model info with all the API data of the model") + ) + + shared.opts.add_option( + "auto_save_all_img", + shared.OptionInfo( + False, + "Automatically save all images", + section=download, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Automatically saves all the images of a model after downloading") + ) + + # Browser Options + shared.opts.add_option( + "custom_api_key", + shared.OptionInfo( + r"", + "Personal CivitAI API key", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("You can create your own API key in your CivitAI account settings, this required for some downloads, Requires UI reload") + ) + + shared.opts.add_option( + "hide_early_access", + shared.OptionInfo( + True, + "Hide early access models", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Early access models are only downloadable for supporter tier members") + ) + + shared.opts.add_option( + "use_LORA", + shared.OptionInfo( + ver_bool, + "Combine LoCon, LORA & DoRA as one option", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("LoCon requires SD-WebUI v1.5 or higher, DoRA requires v1.9 or higher") + ) + + shared.opts.add_option( + "dot_subfolders", + shared.OptionInfo( + True, + "Hide sub-folders that start with a '.'", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ) + ) + + shared.opts.add_option( + "use_local_html", + shared.OptionInfo( + False, + "Use local HTML file for model info", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Uses the matching local HTML file when pressing CivitAI button on model cards in txt2img and img2img") + ) + + shared.opts.add_option( + "local_path_in_html", + shared.OptionInfo( + False, + "Use local images in the HTML", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Only works if all images of the corresponding model are downloaded") + ) + + shared.opts.add_option( + "page_header", + shared.OptionInfo( + False, + "Page navigation as header", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Keeps the page navigation always visible at the top, Requires UI reload") + ) + + shared.opts.add_option( + "video_playback", + shared.OptionInfo( + True, + 'Gif/video playback in the browser', + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Disable this option if you're experiencing high CPU usage during video/gif playback") + ) + + shared.opts.add_option( + "individual_meta_btn", + shared.OptionInfo( + True, + 'Individual prompt buttons', + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Turns individual prompts from an example image into a button to send it to txt2img") + ) + + shared.opts.add_option( + "model_desc_to_json", + shared.OptionInfo( + True, + 'Save model description to json', + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info('This saves the models description to the description field on model cards') + ) + + shared.opts.add_option( + "civitai_not_found_print", + shared.OptionInfo( + True, + 'Show "Model not found" print during update scanning', + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ) + ) + + shared.opts.add_option( + "civitai_send_to_browser", + shared.OptionInfo( + False, + 'Send model from the cards CivitAI button to the browser, instead of showing a popup', + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ) + ) + + shared.opts.add_option( + "image_location", + shared.OptionInfo( + r"", + "Custom save images location", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Overrides the download folder location when saving images.") + ) + + shared.opts.add_option( + "sub_image_location", + shared.OptionInfo( + True, + 'Use sub folders inside custom images location', + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Will append any content type and sub folders to the custom path.") + ) + + shared.opts.add_option( + "save_to_custom", + shared.OptionInfo( + False, + "Store the HTML and api_info in the custom images location", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ) + ) + + shared.opts.add_option( + "custom_civitai_proxy", + shared.OptionInfo( + r"", + "Proxy address", + gr.Textbox, + {"placeholder": "socks4://0.0.0.0:00000 | socks5://0.0.0.0:00000"}, + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Only works with proxies that support HTTPS, turn Aria2 off for proxy downloads") + ) + + shared.opts.add_option( + "cabundle_path_proxy", + shared.OptionInfo( + r"", + "Path to custom CA Bundle", + gr.Textbox, + {"placeholder": "/path/to/custom/cabundle.pem"}, + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Specify custom CA bundle for SSL certificate checks if required") + ) + + shared.opts.add_option( + "disable_sll_proxy", + shared.OptionInfo( + False, + "Disable SSL certificate checks", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Not recommended for security, may be required if you do not have the correct CA Bundle available") + ) + + id_and_sub_options = { + "1" : f"{os.sep}Base model", + "2" : f"{os.sep}Base model{os.sep}Author name", + "3" : f"{os.sep}Base model{os.sep}Author name{os.sep}Model name", + "4" : f"{os.sep}Base model{os.sep}Author name{os.sep}Model name{os.sep}Model version", + "5" : f"{os.sep}Base model{os.sep}Model name", + "6" : f"{os.sep}Base model{os.sep}Model name{os.sep}Model version", + "7" : f"{os.sep}Author name", + "8" : f"{os.sep}Author name{os.sep}Base model", + "9" : f"{os.sep}Author name{os.sep}Base model{os.sep}Model name", + "10" : f"{os.sep}Author name{os.sep}Base model{os.sep}Model name{os.sep}Model version", + "11" : f"{os.sep}Author name{os.sep}Model name", + "12" : f"{os.sep}Author name{os.sep}Model name{os.sep}Model version", + "13" : f"{os.sep}Model name", + "14" : f"{os.sep}Model name{os.sep}Model version", + } + + for number, string in id_and_sub_options.items(): + shared.opts.add_option( + f"insert_sub_{number}", + shared.OptionInfo( + False, + f"Insert: [{string}]", + section=download, + **({'category_id': cat_id} if ver_bool else {}) + ) + ) + + use_LORA = getattr(opts, "use_LORA", False) + + # Default sub folders + folders = [ + "Checkpoint", + "LORA, LoCon, DoRA" if use_LORA else "LORA", + "LoCon" if not use_LORA else None, + "DoRA" if not use_LORA else None, + "TextualInversion", + "Poses", + "Controlnet", + "Hypernetwork", + "MotionModule", + ("Upscaler", "SWINIR"), + ("Upscaler", "REALESRGAN"), + ("Upscaler", "GFPGAN"), + ("Upscaler", "BSRGAN"), + ("Upscaler", "ESRGAN"), + "VAE", + "AestheticGradient", + "Wildcards", + "Workflows", + "Other" + ] + + for folder in folders: + if folder == None: + continue + desc = None + if isinstance(folder, tuple): + folder_name = " - ".join(folder) + setting_name = f"{folder[1]}_upscale" + folder = folder[0] + desc = folder[1] + else: + folder_name = folder + setting_name = folder + if folder == "LORA, LoCon, DoRA": + folder = "LORA" + setting_name = "LORA_LoCon" + + shared.opts.add_option(f"{setting_name}_subfolder", shared.OptionInfo("None", folder_name, gr.Dropdown, make_lambda(folder, desc), section=download, **({'category_id': cat_id} if ver_bool else {}))) + +script_callbacks.on_ui_tabs(on_ui_tabs) +script_callbacks.on_ui_settings(on_ui_settings) \ No newline at end of file diff --git a/extensions/sd-civitai-browser-plus/style.css b/extensions/sd-civitai-browser-plus/style.css new file mode 100644 index 0000000000000000000000000000000000000000..287ab4d7554022d164cfc263eb2fae5c86f15ae0 --- /dev/null +++ b/extensions/sd-civitai-browser-plus/style.css @@ -0,0 +1,790 @@ +/* Card list HTML */ +.civmodellist { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.civmodellist figure { + margin: 6px; + transition: transform .3s ease-out, box-shadow 0.3s ease; + cursor: pointer; + border-radius: 10px; +} + +.civmodelcard { + position: relative; +} + +.civmodelcard:hover { + transform: scale(1.1, 1.1); + position: relative; + z-index: var(--layer-5); + box-shadow: 0px 0px 1px 3px whitesmoke; +} + +.civmodelcardinstalled { + box-shadow: 0px 0px 1px 3px aquamarine; +} + +.civmodelcardoutdated { + box-shadow: 0px 0px 1px 3px orange; +} + +.civmodelcard:hover figcaption{ + bottom: initial; + background-color: rgba(32, 32, 32, 0.9); +} + +.civmodelcard img, .civmodelcard .video-bg { + width: 8em; + height: 12em; + object-fit: cover; + border-radius: 10px; +} + +.civmodelcard figcaption { + position: absolute; + bottom: 5px; + text-align: center; + width: 8em; + word-break: break-word; + background-color: rgba(32, 32, 32, 0.5); + color: white !important; +} + +/* End of Card list HTML */ + +#quicksettings > div{ + max-width: None !important; + width: auto !important; +} + +#togglesL{ + margin-top: 3px; +} + +#toggles{ + margin-top: -10px; +} + +#searchType > div { + gap: 0.5em; +} + +#backToTopContainer { + position: fixed; + bottom: 0; + right: 0; + display: flex; + justify-content: flex-end; + z-index: 150; + pointer-events: none; + margin: 20px 51px 20px 20px; +} + +#backToTop { + margin: 0; + max-width: 60px; + min-width: unset; + z-index: 200; + pointer-events: auto; +} + +#browserTab { + position: relative; +} + +#browserTab > div { + gap: var(--layout-gap) !important; +} + +#browserTab > div > #header { + position: -webkit-sticky; + position: sticky; + top: 0; + background-color: var(--neutral-950); + z-index: 60; +} + +.acss-14flpmm .gap:has(#quicksettings):first-child { + gap: var(--layout-gap); +} + +#txt2img_seed > label > input{ + height: unset !important; +} + +#browserTab > div > #header, #browserTab > div > #header_off { + display: flex; + flex-direction: column; + padding-top: 15px; + margin-top: -15px; +} + +#toggle1, #toggle2, #toggle3, #toggle4, #toggle4_api, #toggle5{ + margin-top: 5px; + margin-right: 0px; + margin-left: 0px; + display: flex; + justify-content: center; +} + +#civitai_update_toggles > div { + display: flex; + flex-direction: column; +} + +#civitai_update_toggles { + margin-top: calc(-1 * var(--layout-gap)); + margin-bottom: var(--layout-gap); +} + +#toggle1L, #toggle2L, #toggle3L, #toggle4L, #toggle4L_api, #toggle5L, +#overwrite_toggle, #skip_hash_toggle, #do_html_gen { + display: flex; + justify-content: center; +} + +#centerText, #searchType { + text-align: center; +} + +#browserTab { + min-height: 650px; +} + +#download_all_button { + max-height: 40px; + height: 40px; + align-self: end; + margin-bottom: 1px; +} + +#searchBox > label > textarea { + padding-top: 11px !important; +} + +#searchBox { + max-width: 800px; + align-self: center; +} + +#baseMdl { + min-width: 100px !important; + max-width: 100px !important; +} + +#spanWidth { + display: flex !important; + flex-direction: row; +} + +#spanWidth > div { + flex-wrap: nowrap; +} + +.gradio-container-3-32-0 .prose :last-child { + margin-bottom: auto !important; +} + +.date-section { + display: block; + width: 100%; + margin-bottom: 5px; + text-align: center; +} + +.card-row { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +#selected_tags { + text-align: center; +} + +#pageBtn1, #pageBtn2 { + max-width: 120px !important; + min-width: 50px !important; +} + +#pageSlider { + max-height: 44px; +} + +#pageSlider > div:nth-child(2) { + max-height: 25px; +} + +#pageBoxMobile { + display: flex; + justify-content: space-between; +} + +#pageBox { + display: flex; + justify-content: center; + align-self: center; + max-width: 950px !important; +} + +#pageBox > div:first-child { + align-items: end; +} + +#refreshBtn, #refreshBtnL { + align-self: end; + height: 42px !important; + min-height: 42px !important; + max-height: 42px !important; + max-width: 42px !important; + min-width: 42px !important; + width: 42px !important; + padding: 0px !important; +} + +#refreshBtn > img, +#refreshBtnL > img { + margin: unset; +} + +#searchRow { + max-width: 800px; + align-self: center; +} + +#save_set_box { + display: flex; + justify-content: center; +} + +#save_set_btn { + max-width: 220px !important; + min-width: 220px !important; + margin-bottom: -6px; + padding: 5px; + height: unset !important; + min-height: 35px !important; +} + +#searchType > div:nth-child(3) { + justify-content: center; +} + +.custom-checkbox { + position: absolute; + top: 10px; + right: 10px; + width: 20px; + min-width: 20px; + height: 20px; + background: #111B; + border-radius: var(--checkbox-border-radius); + border: 1px solid #bbbbbb; + cursor: pointer; +} + +.custom-checkbox:hover { + border-color: #ffffff; +} + +.model-checkbox:checked + .custom-checkbox { + background-color: var(--checkbox-background-color-selected); + border-color: var(--checkbox-border-color-selected); + background-image: var(--checkbox-check); + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + +.open-in-civitai { + font-size: 18pt; + color: var(--body-text-color); + display: flex; + justify-content: center; + margin-top: -12px; +} + +#model_header:hover{ + color: var(--link-text-color-hover); +} + +.civitai-txt2img-btn:hover { + border-color: var(--button-secondary-border-color-hover); + background: var(--button-secondary-background-fill-hover); + color: var(--button-secondary-text-color-hover); +} + +.civitai-txt2img-btn { + border-radius: var(--button-large-radius); + border: var(--button-border-width) solid var(--button-secondary-border-color); + padding: var(--button-large-padding); + font-weight: var(--button-large-text-weight); + font-size: var(--button-large-text-size); + background: var(--button-secondary-background-fill); + color: var(--button-secondary-text-color); +} + +.civitai-tags-container { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.civitai-tag, +.civitai-meta, +.civitai-meta-btn { + background-image: var(--button-secondary-background-fill); + border-radius: 8px; + padding: 4px 6px; + border: 1px solid var(--input-border-color); +} + +.civitai-meta-btn:hover { + cursor: pointer; + background-image: var(--button-secondary-background-fill-hover); +} + +#select_all_models_container { + display: flex; + justify-content: flex-end; +} + +#select_all_models { + max-width: 100px; + min-width: 100px; + min-height: 30px; + padding: 0px; + margin-top: -25px; +} + +.civitai_dl_item, +.civitai_dl_item_completed, +.civitai_dl_item_failed { + background-color: var(--error-background-fill); + border-radius: 8px; + padding: 5px 0px; + border: 1px solid var(--input-border-color); + margin: 10px 0px; +} + +.civitai_dl_item_failed > .dl_stat > .dl_progress_bar { + background-color: transparent !important; + padding: 0px 0px 2px 0px !important; +} + +.dl_progress_bar { + background-color: var(--button-primary-border-color); + color: var(--body-text-color); + padding: 0px 0px 2px 5px; + border-radius: 8px; + transition: width 0.5s ease-in-out; +} + +.dl_progress_bar::before, +.dl_progress_bar::after { + content: ""; + display: table; + clear: both; +} + +.civitai-btn-text:hover { + color: var(--link-text-color-hover); + cursor: pointer; +} +/* Customized Accordion Filter */ + +#filterBox, +#filterBoxL { + align-self: end; + height: 42px; + max-width: 42px; + padding: unset !important; + margin: 0px !important; + display: flex; + justify-content: center; +} + +#filterBox { + background: var(--button-secondary-background-fill); +} + +#filterBoxL { + background: var(--input-background-fill); +} + +#filterBox:hover, +#filterBoxL:hover { + background: var(--button-secondary-background-fill-hover); +} + +#filterBox .label-wrap.open, +#filterBoxL .label-wrap.open{ + border-bottom: unset !important; + background: var(--button-secondary-background-fill-hover); + border-radius: 7px !important; + height: 40px; +} + +#filterBox > div:nth-child(3), +#filterBoxL > div:nth-child(3) { + padding: 20px; + position: absolute; + border-radius: 10px; + width: 300px; + z-index: 100 !important; + margin-top: 55px; +} + +.browser_tooltip { + box-shadow: var(--body-text-color) 0px 0px 2px 0px; + background: var(--background-fill-primary); + color: var(--body-text-color); + border-radius: 3px; + padding: 10px; + position: absolute; + z-index: 50; + margin-top: 30px; +} + +#toggle4 > label > span, #toggle4L > label > span { + color: var(--neutral-400); +} + +#filterBox > div:nth-child(3), #toggle4 > div:nth-child(3) { + background: var(--background-fill-primary); +} + +#filterBoxL > div:nth-child(3), #toggle4L > div:nth-child(3) { + background: var(--neutral-950); +} + +#filterBox > div:nth-child(2), +#filterBoxL > div:nth-child(2) { + padding: 10px !important; +} + +#filterBox .gradio-slider input[type="number"], +#filterBoxL .gradio-slider input[type="number"] { + width: 70px !important; +} + +#pageBox .gradio-slider input[type="number"] { + width: 5em !important; +} + +#filterBox > div:nth-child(2) > span:nth-child(1), +#filterBoxL > div:nth-child(2) > span:nth-child(1) { + display: none; +} + +#filterBox > div:nth-child(2) > span:nth-child(2), +#filterBoxL > div:nth-child(2) > span:nth-child(2) { + transform: rotate(0deg) !important; + transition: 0s !important; + display: inline-block; + width: 24px; + height: 24px; + font-size: 0; + color: transparent; + overflow: hidden; +} + +#filterBox > div:nth-child(2) > span:nth-child(2)::before, +#filterBoxL > div:nth-child(2) > span:nth-child(2)::before { + content: ""; + display: block; + width: 100%; + height: 100%; +} + +/* End of Custom Accordion */ + +.card-button { + width: 42px !important; +} + +.edit-button.card-button::before { + font-size: 90%; + vertical-align: top; +} + +.copy-path-button.card-button::before { + font-size: 110%; +} + +.copy-path-button.card-button { + margin-top: -4px; +} + +.goto-civitbrowser.card-button { + filter: drop-shadow(2px 2px 3px black); + display: flex; + align-items: center; +} + +.goto-civitbrowser.card-button:hover svg { + fill: red !important; +} + +/* Custom settings Accordion */ +.settings-accordion { + border: 1px solid var(--block-border-color); + border-radius: 8px !important; + margin: 15px 0px 2px 0px; + padding: 8px 8px; +} + +#accordionToggle { + width: 100%; + display: flex; + font-size: 12pt; + justify-content: space-between; +} + +#selected_tags > div { + justify-content: center; + padding-top: 10px; + padding-bottom: 20px; +} + +#civitai_preview_html .model-block { + box-shadow: 0px 0px 1px 3px var(--button-secondary-border-color); + border-radius: 10px; + padding: 1px 20px 10px; + margin-bottom: 20px; +} + +#civitai_preview_html .model-block code { + white-space: pre-wrap; +} + +#civitai_preview_html .model-block dl { + overflow-wrap: anywhere; +} + +#civitai_preview_html .sampleimgs .model-block img, +#civitai_preview_html .sampleimgs .model-block video { + padding-top: 1em; + max-width: 20em; + cursor: zoom-in; + transition: max-width 0.1s; +} + +/* Preview Image zoom */ +#civitai_preview_html .zoom-radio { + display: none!important; +} + +/* Style for when the image is clicked (radio button checked) */ +#civitai_preview_html .zoom-radio:checked + label > img, +#civitai_preview_html .zoom-radio:checked + label > video { + max-width: 95vw; + max-height: 95vh; + padding-top: 0px; + cursor: zoom-out; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1000; /* Higher than the overlay */ + pointer-events: none; /* Allow clicks to penetrate through to the overlay for resetting */ +} + +/* Overlay for resetting zoomed state */ +#civitai_preview_html .zoom-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, .5); + z-index: 999; /* Below the zoomed image */ + cursor: zoom-out; +} + +#civitai_preview_html .zoom-radio:checked + label + .zoom-overlay { + display: block; + pointer-events: all; /* Capture click events when displayed */ +} + +#civitai_preview_html .zoom-img-container { + min-width: 20em; +} + +#civitai_preview_html .model-uploader { + border-bottom: 1px solid; + padding-bottom: 10px; + } + +#civitai_preview_html .model-description { + border-top: 1px solid; + overflow-wrap: break-word; + overflow: hidden; + position: relative; + max-height: 400px; +} + +#civitai_preview_html .model-description::after { + content: ""; + position: absolute; + bottom: 0; + width: 100%; + height: 75px; + background: linear-gradient(to bottom, rgb(255 255 255 / 0%), var(--background-fill-primary)); + z-index: 1; +} + +.description-toggle-label { + cursor: pointer; +} + +.description-toggle-checkbox { + position: absolute; + opacity: 0; + z-index: -1; +} + +.description-toggle-checkbox:checked + .model-description { + max-height: unset !important; + position: unset !important; +} + +.model-description + .description-toggle-label::before { + content: "❯"; + width: 1em; + height: 1em; + text-align: center; + transition: all 0.3s; +} + +.model-description + .description-toggle-label { + display: flex; + padding: 10px 0px; + font-weight: bold; + cursor: pointer; + font-size: large; +} + +.description-toggle-checkbox:checked + .model-description + .description-toggle-label::before { + transform: rotate(-90deg); + margin-right: 10px; +} + +.description-toggle-checkbox:checked + .model-description + .description-toggle-label::after { + content: "Show Less..."; +} + +.description-toggle-checkbox:not(:checked) + .model-description + .description-toggle-label::after { + content: "Show All..."; +} +/*------------------------------------------*/ +/*End CSS accordion for toggling description*/ + +/*Avatar CSS mostly copied from CivitAI, but 48px instead of 32px*/ +#civitai_preview_html .avatar { + user-select: none; + overflow: hidden; + width: 48px; + height: 48px; + min-width: 48px; + border-radius: 48px; + text-decoration: none; + border: 0; + padding: 0; + background-color: rgba(0,0,0,0.31); + display: inline-block!important; + margin-left: 5px!important; + vertical-align: middle; +} + +#civitai_preview_html .avatar img { + object-fit: cover; + width: 100%; + height: 100%; + display: block; + overflow-clip-margin: content-box; + overflow: clip; + border-style: none; +} + +#civitai_preview_html dt { + font-size: medium; + color: #80a6c8!important; +} + +/* +#civitai_preview_html dt { + font-size: medium; + color: #3966bb !important; +} +*/ + +#civitai_preview_html dd { + padding: 0px 0px 10px 10px; +} + +/*CSS accordion for toggling extra metadata*/ +/*-----------------------------------------*/ +#civitai_preview_html .accordionCheckbox { + position: absolute; + opacity: 0; + z-index: -1; +} + +#civitai_preview_html .tabs { + border-radius: 10px; + overflow: hidden; +} + +#civitai_preview_html .tab { + width: 100%; + color: white; + overflow: hidden; + margin-left: -15px; +} + +#civitai_preview_html .tab-label { + display: flex; + padding: 1em; + font-weight: bold; + cursor: pointer; + font-size: large; +} + +/* Icon */ +#civitai_preview_html .tab-label::before { + content: "❯"; + width: 1em; + height: 1em; + text-align: center; + transition: all 0.3s; +} + +#civitai_preview_html .accordionCheckbox:checked + .tab-label::before { + transform: rotate(90deg); +} + +#civitai_preview_html .tab-content { + max-height: 0; + padding: 0 1em; + transition: all 0.3s; +} + +#civitai_preview_html .tab-close { + display: flex; + justify-content: flex-end; + padding: 1em; + font-size: 0.75em; + cursor: pointer; +} + +#civitai_preview_html .accordionCheckbox:checked ~ .tab-content { + max-height: unset; + padding: 1em; +} +/*-----------------------------------------*/ +/*End CSS accordion for toggling extra metadata*/ \ No newline at end of file diff --git a/extensions/sd-civitai-browser-plus/style_html.css b/extensions/sd-civitai-browser-plus/style_html.css new file mode 100644 index 0000000000000000000000000000000000000000..59a5af188bd26fd5088990eb8fc0988692d480ca --- /dev/null +++ b/extensions/sd-civitai-browser-plus/style_html.css @@ -0,0 +1,303 @@ +body { + background-color: #0b0f19; +} + +.model-block { + box-shadow: 0px 0px 1px 3px #3339ff30; + border-radius: 10px; + padding: 1px 20px 10px; + margin-bottom: 20px; +} + +.model-block code { + white-space: pre-wrap; +} + +.model-block dl { + overflow-wrap: anywhere; +} + +.civnsfw img { + filter: unset; +} + +.sampleimgs .model-block img, +.sampleimgs .model-block video { + padding-top: 1em; + max-width: 20em; + cursor: zoom-in; + transition: max-width 0.1s; +} + +/* Text adjustments */ +h1, h2, h3, h4, h5, dd, dt, p, a, label { + font-family: 'Source Sans Pro', 'ui-sans-serif', 'system-ui', sans-serif; +} + +h3 { + font-size: 16px; +} + +h2 { + margin: 16px 0px 8px; + font-size: 22px; +} + +ul { + padding-left: 18px; +} + +p { + color: #F3F4F6; + margin: 0px 0px 6px; + font-size: 14px; +} + +dt { + font-size: medium; + color: #80a6c8 !important; + font-size: 16px; +} + +dd { + padding: 0px 0px 5px 10px; + margin-inline-start: 0px; + font-size: 14px; + color: #F3F4F6; +} + +a { + color: #F3F4F6; + font-weight: bold; + text-decoration: unset; +} + +a:hover { + color: #60A5FA; +} + +.civitai_txt2img { + display: none; +} + +/* Preview Image zoom */ +.zoom-radio { + display: none !important; +} + +/* Style for when the image is clicked (radio button checked) */ +.zoom-radio:checked + label > img, +.zoom-radio:checked + label > video { + max-width: 95vw; + max-height: 95vh; + padding-top: 0px; + cursor: zoom-out; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1000; /* Higher than the overlay */ + pointer-events: none; /* Allow clicks to penetrate through to the overlay for resetting */ +} + +/* Overlay for resetting zoomed state */ +.zoom-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, .5); + z-index: 999; /* Below the zoomed image */ + cursor: zoom-out; +} + +.zoom-radio:checked + label + .zoom-overlay { + display: block; + pointer-events: all; /* Capture click events when displayed */ +} + +.zoom-img-container { + min-width: 20em; +} + +.model-uploader { + border-bottom: 1px solid; + padding-bottom: 10px; + color: white; + } + +.model-description { + border-top: 1px solid; + padding-bottom: 10px; + margin-bottom: 10px; + color: white; + } + +/*Avatar CSS mostly copied from CivitAI, but 48px instead of 32px*/ +.avatar { + user-select: none; + overflow: hidden; + width: 48px; + height: 48px; + min-width: 48px; + border-radius: 48px; + text-decoration: none; + border: 0; + padding: 0; + background-color: rgba(0,0,0,0.31); + display: inline-block!important; + margin-left: 5px!important; + vertical-align: middle; +} + +.avatar img { + object-fit: cover; + width: 100%; + height: 100%; + display: block; + overflow-clip-margin: content-box; + overflow: clip; + border-style: none; +} + +.model-description { + border-top: 1px solid; + overflow-wrap: break-word; + overflow: hidden; + position: relative; + max-height: 400px; +} + +.model-description::after { + content: ""; + position: absolute; + bottom: 0; + width: 100%; + height: 75px; + background: linear-gradient(to bottom, rgb(255 255 255 / 0%), #0b0f19); + z-index: 1; +} + +.description-toggle-label { + display: flex; + padding: 10px 0px 0px 0px; + font-weight: bold; + cursor: pointer; + font-size: large; + color: white; +} + +.description-toggle-checkbox { + position: absolute; + opacity: 0; + z-index: -1; +} + +.description-toggle-checkbox:checked + .model-description { + max-height: none !important; + position: unset !important; +} + +.model-description + .description-toggle-label::before { + content: "❯"; + width: 1em; + height: 1em; + text-align: center; + transition: all 0.3s; +} + +.description-toggle-checkbox:checked + .model-description + .description-toggle-label::before { + transform: rotate(-90deg); + margin-right: 10px; +} + +.description-toggle-checkbox:checked + .model-description + .description-toggle-label::after { + content: "Show Less..."; +} + +.description-toggle-checkbox:not(:checked) + .model-description + .description-toggle-label::after { + content: "Show All..."; +} + +/*CSS accordion for toggling extra metadata*/ +/*-----------------------------------------*/ +.accordionCheckbox { + position: absolute; + opacity: 0; + z-index: -1; +} + +.tabs { + border-radius: 10px; + overflow: hidden; +} + +.tab { + width: 100%; + color: white; + overflow: hidden; + margin-left: -15px; +} + +.tab-label { + display: flex; + padding: 1em; + font-weight: bold; + cursor: pointer; + font-size: large; +} + +.civitai-tags-container { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.civitai-tag, +.civitai-meta-btn { + background-color: #111827; + border-radius: 8px; + padding: 4px 6px; + border: 1px solid #374151; +} + +.civitai-meta-btn:hover { + cursor: pointer; + background-color: #1F2937; +} + +/* Icon */ +.tab-label::before { + content: "❯"; + width: 1em; + height: 1em; + text-align: center; + transition: all 0.3s; +} + +.accordionCheckbox:checked + .tab-label::before { + transform: rotate(90deg); +} + +.tab-content { + max-height: 0; + padding: 0 1em; + transition: all 0.3s; +} + +.tab-close { + display: flex; + justify-content: flex-end; + padding: 1em; + font-size: 0.75em; + cursor: pointer; +} + +.accordionCheckbox:checked ~ .tab-content { + max-height: 100vh; + padding: 1em; +} +/*-----------------------------------------*/ +/*End CSS accordion for toggling extra metadata*/ \ No newline at end of file diff --git a/extensions/sd-danbooru-tags-upsampler/.gitignore b/extensions/sd-danbooru-tags-upsampler/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..68bc17f9ff2104a9d7b6777058bb4c343ca72609 --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/extensions/sd-danbooru-tags-upsampler/LICENSE b/extensions/sd-danbooru-tags-upsampler/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/extensions/sd-danbooru-tags-upsampler/README-ja.md b/extensions/sd-danbooru-tags-upsampler/README-ja.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac48ed8354ebcbf26fa7fa726da8eedf226c6ac --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/README-ja.md @@ -0,0 +1,176 @@ +# sd-danbooru-tags-upsampler + +English version is [here](./README.md). + +これは軽量な LLM を利用して danbooru タグを生成/補完することで、プロンプトをアップサンプルすることができる Stable Diffusion WebUI 向けの拡張機能です。 + +長いプロンプトを考えたくない場合や、何も考えないで**多様**かつ**自然**で**高品質**な画像を見ていたいという場合に便利です。 + +## 更新履歴 + +- 2024/2/29: v0.2.0。新機能: 生成オプション、多様性レベル、範囲禁止タグの実装。 +- 2024/2/25: v0.1.1。シード値の処理、括弧のエスケープ処理、軽微な不具合が修正されました。 +- 2024/2/23: 最初のバージョンである v0.1.0 をリリースしました + +## 使い方 + +拡張機能のスクリーンショット + +`Danbooru Tags Upsampler` と書かれたアコーディオンを開き、`Enabled` チェックボックスにチェックをいれることで拡張機能を有効化できます。 + +パラメーターの説明: + +| パラメーター名 | 説明 | 例 | +| -------------- | ----------- | ------------- | +| **Total tag length** | これは **タグの補完後のプロンプト内のタグの総量を指定します**。 補完するタグの量ではありません。 `very short` は「タグ10個以下」, `short` は「タグ20個以下」, `long` は「タグ40個以下」、 `very long` は「それよりも多い」を意味します。 | 推奨は `long` です | +| **Ban tags** | ここで指定された全てのタグは補完時に出現しなくなります。出てきて欲しくないタグがあるときに便利です。`*` は全ての文字列にマッチします。(例: `* background` は `simple background`、`white background` 等にマッチします) | `official alternate costume, english text, * background, ...` | +| **Seed for upsampling tags** | この値とポジティブプロンプトが固定された場合、補完されるタグも固定されます。`-1` は毎回ことなるシードで補完することを意味します。 | 毎回異なる補完をしてほしい場合は `-1` に設定します。 | +| **Upsampling timing** | sd-dynamic-prompts や webui の styles 機能などの、他のプロンプト加工処理が実行される前にアップサンプルするか、後にアップサンプルするかどうかです。 | `After applying other prompt processing` | +| **Variety level** | このパラメーターは `Generation config` のプリセットです。アップサンプルされるタグの多様度を指定できます。 | `varied` | +| **Generation config** | タグの生成に利用される LLM のパラメーターです。言語モデルの生成パラメーターに詳しくない場合は触らず、 `Variety level` を使うことをおすすめします。 || + +## ショーケース + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
入力のプロンプトアップサンプルなしアップサンプルあり
1girl, solo, cowboy shot (seed: 2396487241) + Sample image 1 generated without upsampling + Sample image 1 generated with upsampling
(最終的なプロンプト) 1girl, solo, cowboy shot1girl, solo, cowboy shot, ahoge, animal ears, bare shoulders, blue hair, blush, closed mouth, collarbone, collared shirt, dress, eyelashes, fox ears, fox girl, fox tail, hair between eyes, heart, long hair, long sleeves, looking at viewer, neck ribbon, ribbon, shirt, simple background, sleeves past wrists, smile, tail, white background, white dress, white shirt, yellow eyes
3girls (seed: 684589178) + Sample image 2 generated without upsampling + Sample image 2 generated with upsampling
(最終的なプロンプト) 3girls3girls, black footwear, black hair, black thighhighs, boots, bow, bowtie, chibi, closed mouth, collared shirt, flower, grey hair, hair between eyes, hair flower, hair ornament, long hair, long sleeves, looking at viewer, multiple girls, purple eyes, red eyes, shirt, short hair, sitting, smile, thighhighs, vest, white shirt, white skirt
no humans, scenery (seed: 3702717413) + Sample image 3 generated without upsampling + Sample image 3 generated with upsampling
(最終的なプロンプト) no humans, sceneryno humans, scenery, animal, animal focus, bird, blue eyes, cat, dog, flower, grass, leaf, nature, petals, shadow, sitting, star (sky), sunflower, tree
1girl, frieren, sousou no frieren + (seed: 787304393) + Sample image 4 generated without upsampling + Sample image 4 generated with upsampling
(最終的なプロンプト) 1girl, frieren, sousou no frieren1girl, frieren, sousou no frieren, black pantyhose, cape, closed mouth, elf, fingernails, green eyes, grey hair, hair between eyes, long hair, long sleeves, looking at viewer, pantyhose, pointy ears, simple background, skirt, solo, twintails, white background, white skirt
+ +生成設定: + +- モデル: [AnimagineXL 3.0](https://huggingface.co/cagliostrolab/animagine-xl-3.0) +- ネガティブプロンプト (animaginexl 3.0 公式の推奨設定と同じ): + +``` +nsfw, lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, artist name +``` + +アップサンプル設定: + +- Total tag length: `long` +- Ban tags: 指定なし +- Seed: `-1` +- When to perform the process: `Before applying styles` + +### 多様性レベル + +(シードは同じではないです。) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
入力プロンプトVery unvariedUnvariedNormalVariedVery varied
1girl, solo, from sideVariation sample image 1; very unvariedVariation sample image 1; unvariedVariation sample image 1; normalVariation sample image 1; variedVariation sample image 1; very varied
1girl, frieren, sousou no frieren,Variation sample image 2; very unvariedVariation sample image 2; unvariedVariation sample image 2; normalVariation sample image 2; variedVariation sample image 2; very varied
no humans, sceneryVariation sample image 3; very unvariedVariation sample image 3; unvariedVariation sample image 3; normalVariation sample image 3; variedVariation sample image 3; very varied
+ +`Very unvaried`, `Unvaried` は多様性が低いことを意味しますが、同時に入力プロンプトに忠実であり、比較的無難なタグを生成します。また、`Very varied`, `Varied` はより多様なタグが生成されますが、入力プロンプトに従わなかったり不自然な生成になったりしやすくなります。 + +## モデルへのアクセス + +この拡張機能では次のモデルを使用しています: + +- `p1atdev/dart-v1-sft`: [🤗 HuggingFace](https://huggingface.co/p1atdev/dart-v1-sft) + +## Stable Diffusion WebUI なしで使いたいですか? + +🤗 Space 上にデモがあるのでインストール不要で試すことができます: + +デモ: https://huggingface.co/spaces/p1atdev/danbooru-tags-transformer + +## デフォルト値を変更するには? + +`[webui のルート]/ui-config.json` を開き、`customscript/dart_upsampler.py/` で始まるパラメーターを探して編集してください。 + +もしデフォルト値が壊れていると感じたら、それらのパラメータを削除することでデフォルト値をリセットできます。 + +## 謝辞 + +このプロジェクトは以下のプロジェクトや研究の影響を受けています。 これらのプロジェクトの開発者および貢献者に敬意と感謝の意を表します: + +- succinctly/text2image-prompt-generator: https://huggingface.co/succinctly/text2image-prompt-generator +- Gustavosta/MagicPrompt-Stable-Diffusion: https://huggingface.co/Gustavosta/MagicPrompt-Stable-Diffusion +- FredZhang7/anime-anything-promptgen-v2: https://huggingface.co/FredZhang7/anime-anything-promptgen-v2 +- sd-dynamic-prompts: https://github.com/adieyal/sd-dynamic-prompts +- DALL-E 3: https://cdn.openai.com/papers/dall-e-3.pdf +- caption-upsampling: https://github.com/sayakpaul/caption-upsampling +- StableDiffusionWebUI: https://github.com/AUTOMATIC1111/stable-diffusion-webui とその派生物 diff --git a/extensions/sd-danbooru-tags-upsampler/README.md b/extensions/sd-danbooru-tags-upsampler/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3d5fc99556cc8c752e220bdd1aa624f4b0327dd2 --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/README.md @@ -0,0 +1,180 @@ +# sd-danbooru-tags-upsampler + +日本語は[こちら](./README-ja.md)へ + +An extension for Stable Diffusion Web UI that upsamples prompts by generating or completing danbooru tags using lightweight LLM. + +It's useful for people who don't want think about long prompt or want to see **diverse**, **natural** and **high quality** images without any thinking. + +## Upsates + +- 2024/2/29: v0.2.0 has been released. New features; generation options, variety level and range ban tags. +- 2024/2/25: v0.1.1 has been released. Handling of seeds, escaping processing of brackets and many bugs are fixed. +- 2024/2/23: First version v0.1.0 has been released. + +## Usage + +Scrennshot of this extension + +Open the `Danbooru Tags Upsampler` accordion and check the `Enabled` checkbox to enable this extension. + +Explanation of parameters: + +| Parameter name | Description | Example value | +| -------------- | ----------- | ------------- | +| **Total tag length** | This parameter can specify the amount of **total tags after completing the positive prompt**. Not the amount of completing tags. `very short` means "less than 10 tags", `short` means "less than 20 tags", `long` means "less than 40 tags" and `very long` is more than that. | `long` is recommended | +| **Ban tags** | All tags in this field will never appear in completion tags. It's useful when you don't want to contain some specific tags. Using `*` maches to any character. (e.g. `* background` matches to `simple background`, `white background`, ...) | `official alternate costume, english text, * background, ...` | +| **Seed for upsampling tags** | If this number and the positive prompt are fixed, the completion tags are also fixed. `-1` means "generates tags using random seed every time" | If you want to generate images with different final prompts every time, set to `-1`. | +| **Upsampling timing** | When to upsample, before or after other prompt processing (e.g. sd-dynamic-prompts or webui's styles feature) are applied. | `After applying other prompt processings` | +| **Variety level** | These parameters are presets of the `Generation config`. This can change the variety of upsampled tags. | `varied` | +| **Generation config** | LLM parameters of generating tags. It's recommended not to touch if you are not familiar with language model's generation parameters, and use `Variety level` option instead. || + +Scrennshot of generation config options + + +## Showcases + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Input promptWithout upsamplingWith upsampling
1girl, solo, cowboy shot (seed: 2396487241) + Sample image 1 generated without upsampling + Sample image 1 generated with upsampling
(prompts used to generate) 1girl, solo, cowboy shot1girl, solo, cowboy shot, ahoge, animal ears, bare shoulders, blue hair, blush, closed mouth, collarbone, collared shirt, dress, eyelashes, fox ears, fox girl, fox tail, hair between eyes, heart, long hair, long sleeves, looking at viewer, neck ribbon, ribbon, shirt, simple background, sleeves past wrists, smile, tail, white background, white dress, white shirt, yellow eyes
3girls (seed: 684589178) + Sample image 2 generated without upsampling + Sample image 2 generated with upsampling
(prompts used to generate) 3girls3girls, black footwear, black hair, black thighhighs, boots, bow, bowtie, chibi, closed mouth, collared shirt, flower, grey hair, hair between eyes, hair flower, hair ornament, long hair, long sleeves, looking at viewer, multiple girls, purple eyes, red eyes, shirt, short hair, sitting, smile, thighhighs, vest, white shirt, white skirt
no humans, scenery (seed: 3702717413) + Sample image 3 generated without upsampling + Sample image 3 generated with upsampling
(prompts used to generate) no humans, sceneryno humans, scenery, animal, animal focus, bird, blue eyes, cat, dog, flower, grass, leaf, nature, petals, shadow, sitting, star (sky), sunflower, tree
1girl, frieren, sousou no frieren + (seed: 787304393) + Sample image 4 generated without upsampling + Sample image 4 generated with upsampling
(prompts used to generate) 1girl, frieren, sousou no frieren1girl, frieren, sousou no frieren, black pantyhose, cape, closed mouth, elf, fingernails, green eyes, grey hair, hair between eyes, long hair, long sleeves, looking at viewer, pantyhose, pointy ears, simple background, skirt, solo, twintails, white background, white skirt
+ + +Generation settings: + +- Model: [AnimagineXL 3.0](https://huggingface.co/cagliostrolab/animagine-xl-3.0) +- Negative prompt (same as the recommended settings of animaginexl 3.0): + +``` +nsfw, lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, artist name +``` + +Upsampling settings: + +- Total tag length: `long` +- Ban tags: none +- Seed: `-1` +- When to perform the process: `Before applying styles` + +### Variation levels + +(The seeds are not the same.) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Input promptVery unvariedUnvariedNormalVariedVery varied
1girl, solo, from sideVariation sample image 1; very unvariedVariation sample image 1; unvariedVariation sample image 1; normalVariation sample image 1; variedVariation sample image 1; very varied
1girl, frieren, sousou no frieren,Variation sample image 2; very unvariedVariation sample image 2; unvariedVariation sample image 2; normalVariation sample image 2; variedVariation sample image 2; very varied
no humans, sceneryVariation sample image 3; very unvariedVariation sample image 3; unvariedVariation sample image 3; normalVariation sample image 3; variedVariation sample image 3; very varied
+ +`Very unvaried` and `Unvaried` mean less variety, but at the same time, faithful to the input prompt and generate relatively acceptable tags. Also, `Very varied` and `Varied` mean more variety, but tend to ignore the input prompt and often generate weird tags. + +## Access to the model weights + +This extension uses the following model: + +- `p1atdev/dart-v1-sft`: [🤗 HuggingFace](https://huggingface.co/p1atdev/dart-v1-sft) + +## Want to use without sd webui? + +A demo on 🤗 Space is avaiable, so you can try upsampling tags without installing this extension: + +Demo: https://huggingface.co/spaces/p1atdev/danbooru-tags-transformer + +## How to change default values? + +Open `[webui's root directory]/ui-config.json`, then find parameters staring with `customscript/dart_upsampler.py/` and edit them. + +If you feel that the default values are broken, you can delete parameters staring with `customscript/dart_upsampler.py/txt2img/` to reset the default values. + +## Acknowledgements + +This project has been influenced by the following projects and researches. We express our respect and gratitude to the developers and contributors of these projects: + +- succinctly/text2image-prompt-generator: https://huggingface.co/succinctly/text2image-prompt-generator +- Gustavosta/MagicPrompt-Stable-Diffusion: https://huggingface.co/Gustavosta/MagicPrompt-Stable-Diffusion +- FredZhang7/anime-anything-promptgen-v2: https://huggingface.co/FredZhang7/anime-anything-promptgen-v2 +- sd-dynamic-prompts: https://github.com/adieyal/sd-dynamic-prompts +- DALL-E 3: https://cdn.openai.com/papers/dall-e-3.pdf +- caption-upsampling: https://github.com/sayakpaul/caption-upsampling +- StableDiffusionWebUI: https://github.com/AUTOMATIC1111/stable-diffusion-webui and its derivatives diff --git a/extensions/sd-danbooru-tags-upsampler/dart/__init__.py b/extensions/sd-danbooru-tags-upsampler/dart/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/__init__.cpython-310.pyc b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b8202b9c1761a83cc4f4382d6a7102d92a77eb8 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/__init__.cpython-310.pyc differ diff --git a/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/analyzer.cpython-310.pyc b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/analyzer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efb3490802729d9cff530a20b67751b2d1bc2e7a Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/analyzer.cpython-310.pyc differ diff --git a/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/generator.cpython-310.pyc b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/generator.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d7eb1f296c4005be57517fdd988721565b65cd5 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/generator.cpython-310.pyc differ diff --git a/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/logits_processor.cpython-310.pyc b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/logits_processor.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28d6e27737403a03c64e72f67fd749faa2212879 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/logits_processor.cpython-310.pyc differ diff --git a/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/settings.cpython-310.pyc b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/settings.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..895356dd90c6e763d3a0f9a5c6a1e4ac8e495a2f Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/settings.cpython-310.pyc differ diff --git a/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/utils.cpython-310.pyc b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7348258f9cb1a9da1d8567743f7c156bba7e671 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/dart/__pycache__/utils.cpython-310.pyc differ diff --git a/extensions/sd-danbooru-tags-upsampler/dart/analyzer.py b/extensions/sd-danbooru-tags-upsampler/dart/analyzer.py new file mode 100644 index 0000000000000000000000000000000000000000..b50aae25684ccb3a0fdd7279ef624a93842d1a49 --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/dart/analyzer.py @@ -0,0 +1,241 @@ +import logging +from pathlib import Path +from dataclasses import dataclass + +from modules.extra_networks import parse_prompt +from modules.prompt_parser import parse_prompt_attention +from modules.shared import opts + +from dart.settings import parse_options +from dart.utils import ( + escape_webui_special_symbols, + unescape_webui_special_symbols, +) + +logger = logging.getLogger(__name__) + + +DART_RATING_GENERAL = "rating:general" +DART_RATING_SENSITIVE = "rating:sensitive" +DART_RATING_QUESTIONABLE = "rating:questionable" +DART_RATING_EXPLICIT = "rating:explicit" + +INPUT_RATING_GENERAL = DART_RATING_GENERAL +INPUT_RATING_SENSITIVE = DART_RATING_SENSITIVE +INPUT_RATING_QUESTIONABLE = DART_RATING_QUESTIONABLE +INPUT_RATING_EXPLICIT = DART_RATING_EXPLICIT + +DART_RATING_SFW = "rating:sfw" +DART_RATING_NSFW = "rating:nsfw" + +INPUT_RATING_SFW = "sfw" +INPUT_RATING_NSFW = "nsfw" + +ALL_INPUT_RATING_TAGS = [ + INPUT_RATING_GENERAL, + INPUT_RATING_SENSITIVE, + INPUT_RATING_QUESTIONABLE, + INPUT_RATING_EXPLICIT, + INPUT_RATING_SFW, + INPUT_RATING_NSFW, +] + +RATING_TAG_PRIORITY = { + INPUT_RATING_GENERAL: 0, + INPUT_RATING_SENSITIVE: 1, + INPUT_RATING_QUESTIONABLE: 2, + INPUT_RATING_EXPLICIT: 3, +} + +RATING_PARENT_TAG_PRIORITY = {INPUT_RATING_SFW: 0, INPUT_RATING_NSFW: 1} + +DART_RATING_DEFAULT_PAIR = DART_RATING_SFW, DART_RATING_GENERAL + + +def get_rating_tag_pair(tag: str) -> tuple[str, str]: + if tag == INPUT_RATING_NSFW: # nsfw + return DART_RATING_NSFW, DART_RATING_EXPLICIT + + elif tag == INPUT_RATING_SFW: # sfw + return DART_RATING_DEFAULT_PAIR + + elif tag == INPUT_RATING_GENERAL: # rating:general + return DART_RATING_DEFAULT_PAIR + + elif tag == INPUT_RATING_SENSITIVE: # rating:general + return DART_RATING_SFW, DART_RATING_SENSITIVE + + elif tag == INPUT_RATING_QUESTIONABLE: # rating:questionable + return DART_RATING_NSFW, DART_RATING_QUESTIONABLE + + elif tag == INPUT_RATING_EXPLICIT: # rating:explicit + return DART_RATING_NSFW, DART_RATING_EXPLICIT + + else: + raise Exception(f"Unkown rating tag: {tag}") + + +def get_strongest_rating_tag(tags: list[str]) -> str: + strongest_tag = INPUT_RATING_GENERAL + for tag in tags: + if RATING_TAG_PRIORITY[tag] > RATING_TAG_PRIORITY[strongest_tag]: + strongest_tag = tag + return strongest_tag + + +def normalize_rating_tags(tags: list[str]) -> tuple[str, str]: + """ + Returns [Parent Rating Tag, Child Rating Tag or None] + """ + + if len(tags) == 0: + # default + return DART_RATING_DEFAULT_PAIR + + # only one tag + if len(tags) == 1: + tag = tags[0] + + return get_rating_tag_pair(tag) + + # len(tags) >= 2 + + # if all of tags are parent tag + if all([tag in RATING_PARENT_TAG_PRIORITY.keys() for tag in tags]): + logger.warning( + 'Both "sfw" and "nsfw" are specified in positive image prompt! Rating tag fell back to "sfw" for upsampling.' + ) + return DART_RATING_DEFAULT_PAIR + + # one of the tag is parent tag + if any([tag in RATING_PARENT_TAG_PRIORITY.keys() for tag in tags]): + parent_tag = INPUT_RATING_SFW # sfw or nsfw + child_tags = [] + for tag in tags: + if tag in RATING_PARENT_TAG_PRIORITY: + if ( + RATING_PARENT_TAG_PRIORITY[tag] + > RATING_PARENT_TAG_PRIORITY[parent_tag] + ): + parent_tag = tag + else: + child_tags.append(tag) + + # pick strongest tag + child_tag = get_strongest_rating_tag(child_tags) + + fallback_pair = get_rating_tag_pair(parent_tag) + if child_tag != fallback_pair[1]: + # e.g. rating:general, nsfw + logger.warning( + f'Specified rating tag "{child_tag}" mismatches to "{parent_tag}". "{fallback_pair[1]}" will be used for upsampling instead.' + ) + return fallback_pair + return parent_tag, child_tag + + # remains are child tag + # give priority to the strong + strongest_tag = get_strongest_rating_tag(tags) + return get_rating_tag_pair(strongest_tag) + + +def load_tags_in_file(path: Path): + if not path.exists(): + logger.error(f"File not found: {path}") + return [] + + with open(path, "r", encoding="utf-8") as file: + tags = [tag.strip() for tag in file.readlines() if tag.strip() != ""] + + return tags + + +@dataclass +class ImagePromptAnalyzingResult: + """A class of the result of analyzing tags""" + + rating_parent: str + rating_child: str | None + copyright: str + character: str + general: str + quality: str + unknown: str + + +class DartAnalyzer: + """A class for analyzing provided prompt and composing prompt for upsampling""" + + def __init__(self, extension_dir: str, vocab: list[str], special_vocab: list[str]): + self.options = parse_options(opts) + if self.options["debug_logging"]: + logger.setLevel(logging.DEBUG) + + self.tags_dir = Path(extension_dir) / "tags" + + self.rating_tags = ALL_INPUT_RATING_TAGS + + self.copyright_tags = load_tags_in_file(self.tags_dir / "copyright.txt") + self.character_tags = load_tags_in_file(self.tags_dir / "character.txt") + self.quality_tags = load_tags_in_file(self.tags_dir / "quality.txt") + + self.vocab = vocab + self.special_vocab = special_vocab + + if self.options["escape_input_brackets"]: + logger.debug("Allows tags with escaped brackets") + self.copyright_tags += escape_webui_special_symbols(self.copyright_tags) + self.character_tags += escape_webui_special_symbols(self.character_tags) + self.vocab += escape_webui_special_symbols(self.vocab) + + def split_tags(self, image_prompt: str) -> list[str]: + return [tag.strip() for tag in image_prompt.split(",") if tag.strip() != ""] + + def extract_tags(self, input_tags: list[str], extract_tag_list: list[str]): + matched: list[str] = [] + not_matched: list[str] = [] + + for input_tag in input_tags: + if input_tag in extract_tag_list: + matched.append(input_tag) + else: + not_matched.append(input_tag) + + return matched, not_matched + + def preprocess_tags(self, tags: list[str]) -> str: + """Preprocess tags to pass to dart model.""" + + # \(\) -> () + if self.options["escape_output_brackets"]: + tags = unescape_webui_special_symbols(tags) + + return ", ".join(tags) + + def analyze(self, image_prompt: str) -> ImagePromptAnalyzingResult: + input_tags = self.split_tags(",".join([x[0] for x in parse_prompt_attention(parse_prompt(image_prompt)[0])])) + + input_tags = list(set(input_tags)) # unique + + rating_tags, input_tags = self.extract_tags(input_tags, self.rating_tags) + copyright_tags, input_tags = self.extract_tags(input_tags, self.copyright_tags) + character_tags, input_tags = self.extract_tags(input_tags, self.character_tags) + quality_tags, input_tags = self.extract_tags(input_tags, self.quality_tags) + + # escape special tags + _special_tags, input_tags = self.extract_tags(input_tags, self.special_vocab) + + # general tags and unknown tags + other_tags, unknown_tags = self.extract_tags(input_tags, self.vocab) + + rating_parent, rating_child = normalize_rating_tags(rating_tags) + + return ImagePromptAnalyzingResult( + rating_parent=rating_parent, + rating_child=rating_child, + copyright=self.preprocess_tags(copyright_tags), + character=self.preprocess_tags(character_tags), + general=self.preprocess_tags(other_tags), + quality=self.preprocess_tags(quality_tags), + unknown=self.preprocess_tags(unknown_tags), + ) diff --git a/extensions/sd-danbooru-tags-upsampler/dart/generator.py b/extensions/sd-danbooru-tags-upsampler/dart/generator.py new file mode 100644 index 0000000000000000000000000000000000000000..9368b219c8ac0fcf894b45b56339b5596782dfb0 --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/dart/generator.py @@ -0,0 +1,228 @@ +import logging + +import time +import re + +import torch +from transformers import ( + AutoTokenizer, + AutoModelForCausalLM, + PreTrainedModel, + PreTrainedTokenizer, + PreTrainedTokenizerFast, + LogitsProcessorList, +) +from optimum.onnxruntime import ORTModelForCausalLM + +from modules.shared import opts + +from dart.settings import MODEL_BACKEND_TYPE, parse_options +from dart.utils import ( + escape_webui_special_symbols, + get_valid_tag_list, + get_patterns_from_tag_list, +) +from dart.logits_processor import UnbatchedClassifierFreeGuidanceLogitsProcessor + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +class DartGenerator: + """A class for generating danbooru tags""" + + dart_model: PreTrainedModel | ORTModelForCausalLM | None = None + dart_tokenizer: PreTrainedTokenizer | PreTrainedTokenizerFast | None = None + + def __init__( + self, + model_name: str, + tokenizer_name: str, + model_backend: str, + model_device: str = "cpu", + ): + self.options = parse_options(opts) + + self.model_name = model_name + self.tokenizer_name = tokenizer_name + + assert model_backend in list( + MODEL_BACKEND_TYPE.values() + ), f"Unknown model type: {model_backend}" + self.model_backend = model_backend + self.model_device = model_device + + if self.options["debug_logging"]: + logger.setLevel(logging.DEBUG) + + def _load_dart_model( + self, + ): + if self.model_backend == MODEL_BACKEND_TYPE["ORIGINAL"]: + self.dart_model = AutoModelForCausalLM.from_pretrained(self.model_name) + else: + self.dart_model = ORTModelForCausalLM.from_pretrained( + self.model_name, + file_name=( + "model_quantized.onnx" + if self.model_backend == MODEL_BACKEND_TYPE["ONNX_QUANTIZED"] + else None + ), + ) + logger.info(f"Dart model backend is {self.model_backend }") + + assert self.dart_model is not None + + self.dart_model.to(self.model_device) # type: ignore + + def _load_dart_tokenizer(self): + self.dart_tokenizer = AutoTokenizer.from_pretrained( + self.tokenizer_name, trust_remote_code=True + ) + + def _check_model_avaiable(self): + return self.dart_model is not None + + def _check_tokenizer_avaiable(self): + return self.dart_tokenizer is not None + + def load_model_if_needed(self): + if not self._check_model_avaiable(): + self._load_dart_model() + + def load_tokenizer_if_needed(self): + if not self._check_tokenizer_avaiable(): + self._load_dart_tokenizer() + + def get_vocab_list(self) -> list[str]: + self.load_tokenizer_if_needed() + + return list(self.dart_tokenizer.vocab.keys()) # type: ignore + + def get_special_vocab_list(self) -> list[str]: + self.load_tokenizer_if_needed() + + return list(self.dart_tokenizer.get_added_vocab().values()) # type: ignore + + def compose_prompt( + self, rating: str, copyright: str, character: str, general: str, length: str + ): + # self.load_tokenizer_if_needed() + # assert self.dart_tokenizer is not None + + # sadly webui's transformers version is very old and apply_chat_template method deos not exist + # return self.dart_tokenizer.apply_chat_template( + # { + # "rating": rating, + # "copyright": copyright, + # "character": character, + # "general": general, + # "length": length, + # }, + # tokenize=True, + # ) + + return f"<|bos|>{rating}{copyright}{character}{length}{general}<|input_end|>" + + def get_bad_words_ids(self, tag_text: str) -> list[list[int]] | None: + if tag_text.strip() == "": + return None + + self.load_tokenizer_if_needed() + assert self.dart_tokenizer is not None + + self.dart_tokenizer.sanitize_special_tokens() + + # get ban tag pattern by regex + ban_tags = get_valid_tag_list(tag_text) + ban_tag_patterns = get_patterns_from_tag_list(ban_tags) + + # filter matched tokens from vocab + ban_words_ids: list[int] = [] + for pattern in ban_tag_patterns: + for tag, id in self.dart_tokenizer.vocab.items(): # type:ignore + if pattern.match(tag): + ban_words_ids.append(id) + + # dedup + ban_words_ids = list(set(ban_words_ids)) + + # return type should be list[list[int]] + return [[id] for id in ban_words_ids] + + @torch.no_grad() + def generate( + self, + prompt: str, + max_new_tokens: int = 128, + min_new_tokens: int = 0, + do_sample: bool = True, + temperature: float = 1.0, + top_p: float = 1, + top_k: int = 20, + num_beams: int = 1, + bad_words_ids: list[list[int]] | None = None, + negative_prompt: str | None = None, + cfg_scale: float = 1.5, + ) -> str: + """Upsamples prompt""" + + start_time = time.time() + + self.load_tokenizer_if_needed() + self.load_model_if_needed() + + assert self.dart_tokenizer is not None + assert self.dart_model is not None + + input_ids = self.dart_tokenizer.encode_plus( + prompt, return_tensors="pt" + ).input_ids + negative_prompt_ids = ( + self.dart_tokenizer.encode_plus( + negative_prompt, + return_tensors="pt", + ).input_ids + if negative_prompt is not None + else None + ) + + # output_ids is list[list[int]] + output_ids = self.dart_model.generate( + input_ids, + max_new_tokens=max_new_tokens, + min_new_tokens=min_new_tokens, + do_sample=do_sample, + temperature=temperature, + top_p=top_p, + top_k=top_k, + num_beams=num_beams, + bad_words_ids=bad_words_ids, + no_repeat_ngram_size=1, + logits_processor=( + LogitsProcessorList( + [ + UnbatchedClassifierFreeGuidanceLogitsProcessor( + guidance_scale=cfg_scale, + model=self.dart_model, + unconditional_ids=negative_prompt_ids, + ) + ] + ) + if negative_prompt_ids is not None + else None + ), + ) + + decoded = self.dart_tokenizer.decode( + output_ids[0][len(input_ids[0]) :], + skip_special_tokens=True, + ) + logger.debug(f"Generated tags: {decoded}") + + escaped = ", ".join(escape_webui_special_symbols(decoded.split(", "))) + + end_time = time.time() + logger.info(f"Upsampling tags has taken {end_time-start_time:.2f} seconds") + + return escaped diff --git a/extensions/sd-danbooru-tags-upsampler/dart/logits_processor.py b/extensions/sd-danbooru-tags-upsampler/dart/logits_processor.py new file mode 100644 index 0000000000000000000000000000000000000000..66368d0be3eb63ed17171368c8fe3c944a4b3e8a --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/dart/logits_processor.py @@ -0,0 +1,146 @@ +# coding=utf-8 +# Copyright 2020 The HuggingFace Inc. team +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import Optional + +import torch + +from transformers.generation import LogitsProcessor + +logger = logging.Logger(__name__) + + +# Copied from transformers.generation.logits_processor.UnbatchedClassifierFreeGuidanceLogitsProcessor +class UnbatchedClassifierFreeGuidanceLogitsProcessor(LogitsProcessor): + r""" + Logits processor for Classifier-Free Guidance (CFG). The processors computes a weighted average across scores + from prompt conditional and prompt unconditional (or negative) logits, parameterized by the `guidance_scale`. + The unconditional scores are computed internally by prompting `model` with the `unconditional_ids` branch. + + See [the paper](https://arxiv.org/abs/2306.17806) for more information. + + Args: + guidance_scale (`float`): + The guidance scale for classifier free guidance (CFG). CFG is enabled by setting `guidance_scale != 1`. + Higher guidance scale encourages the model to generate samples that are more closely linked to the input + prompt, usually at the expense of poorer quality. A value smaller than 1 has the opposite effect, while + making the negative prompt provided with negative_prompt_ids (if any) act as a positive prompt. + model (`PreTrainedModel`): + The model computing the unconditional scores. Supposedly the same as the one computing the conditional + scores. Both models must use the same tokenizer. + unconditional_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*): + Indices of input sequence tokens in the vocabulary for the unconditional branch. If unset, will default to + the last token of the prompt. + unconditional_attention_mask (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*): + Attention mask for unconditional_ids. + use_cache (`bool`, *optional*, defaults to `True`): + Whether to cache key/values during the negative prompt forward pass. + + + Examples: + + ```python + >>> from transformers import AutoTokenizer, AutoModelForCausalLM + + >>> model = AutoModelForCausalLM.from_pretrained("openai-community/gpt2") + >>> tokenizer = AutoTokenizer.from_pretrained("openai-community/gpt2") + >>> inputs = tokenizer(["Today, a dragon flew over Paris, France,"], return_tensors="pt") + >>> out = model.generate(inputs["input_ids"], guidance_scale=1.5) + >>> tokenizer.batch_decode(out, skip_special_tokens=True)[0] + 'Today, a dragon flew over Paris, France, killing at least 50 people and injuring more than 100' + + >>> # with a negative prompt + >>> neg_inputs = tokenizer(["A very happy event happened,"], return_tensors="pt") + >>> out = model.generate(inputs["input_ids"], guidance_scale=2, negative_prompt_ids=neg_inputs["input_ids"]) + >>> tokenizer.batch_decode(out, skip_special_tokens=True)[0] + 'Today, a dragon flew over Paris, France, killing at least 130 people. French media reported that' + + >>> # with a positive prompt + >>> neg_inputs = tokenizer(["A very happy event happened,"], return_tensors="pt") + >>> out = model.generate(inputs["input_ids"], guidance_scale=0, negative_prompt_ids=neg_inputs["input_ids"]) + >>> tokenizer.batch_decode(out, skip_special_tokens=True)[0] + "Today, a dragon flew over Paris, France, and I'm very happy to be here. I" + ``` + """ + + def __init__( + self, + guidance_scale: float, + model, + unconditional_ids: Optional[torch.LongTensor] = None, + unconditional_attention_mask: Optional[torch.LongTensor] = None, + use_cache: Optional[bool] = True, + ): + self.guidance_scale = guidance_scale + self.model = model + self.unconditional_context = { + "input_ids": unconditional_ids, + "attention_mask": unconditional_attention_mask, + "use_cache": use_cache, + "past_key_values": None, + "first_pass": True, + } + + def get_unconditional_logits(self, input_ids): + if self.unconditional_context["first_pass"]: + if self.unconditional_context["input_ids"] is None: + self.unconditional_context["input_ids"] = input_ids[:, -1:] + if self.unconditional_context["attention_mask"] is None: + self.unconditional_context["attention_mask"] = torch.ones_like( + self.unconditional_context["input_ids"], dtype=torch.long + ) + input_ids = self.unconditional_context["input_ids"] + attention_mask = self.unconditional_context["attention_mask"] + self.unconditional_context["first_pass"] = False + else: + attention_mask = torch.cat( + [ + self.unconditional_context["attention_mask"], + torch.ones_like(input_ids[:, -1:], dtype=torch.long), + ], + dim=1, + ) + if not self.unconditional_context["use_cache"]: + input_ids = torch.cat( + [self.unconditional_context["input_ids"], input_ids[:, -1:]], dim=1 + ) + else: + input_ids = input_ids[:, -1:] + self.unconditional_context["input_ids"] = input_ids + self.unconditional_context["attention_mask"] = attention_mask + + out = self.model( + input_ids, + attention_mask=attention_mask, + use_cache=self.unconditional_context["use_cache"], + past_key_values=self.unconditional_context["past_key_values"], + ) + self.unconditional_context["past_key_values"] = out.get("past_key_values", None) + + return out.logits + + def __call__(self, input_ids, scores): + scores = torch.nn.functional.log_softmax(scores, dim=-1) + if self.guidance_scale == 1: + return scores + + logits = self.get_unconditional_logits(input_ids) + + unconditional_logits = torch.nn.functional.log_softmax(logits[:, -1], dim=-1) + out = ( + self.guidance_scale * (scores - unconditional_logits) + unconditional_logits + ) + return out diff --git a/extensions/sd-danbooru-tags-upsampler/dart/settings.py b/extensions/sd-danbooru-tags-upsampler/dart/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..4d3dee92f7a08d17c4136c3ab51e18cd3198044b --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/dart/settings.py @@ -0,0 +1,126 @@ +import logging +from typing import Literal, Any + +import gradio as gr + +from modules import shared +from modules.options import Options + +logger = logging.getLogger(__name__) + + +MODEL_BACKEND_TYPE = { + "ORIGINAL": "Original", + "ONNX": "ONNX", + "ONNX_QUANTIZED": "ONNX (Quantized)", +} + +OPTION_NAME = Literal[ + "model_name", + "tokenizer_name", + "model_backend_type", + "model_device", + "debug_logging", + "escape_input_brackets", + "escape_output_brackets", +] + +DEFAULT_VALUES: dict[OPTION_NAME, Any] = { + "model_name": "p1atdev/dart-v1-sft", + "tokenizer_name": "p1atdev/dart-v1-sft", + "model_backend_type": MODEL_BACKEND_TYPE["ONNX_QUANTIZED"], + "model_device": "cpu", + "escape_input_brackets": True, + "escape_output_brackets": True, + "debug_logging": False, +} + + +def parse_options(opts: Options | None) -> dict[OPTION_NAME, Any]: + + def get_value(key: OPTION_NAME): + assert opts is not None + # fallback if the key doest not exist + return opts.__getattr__(key) if hasattr(opts, key) else DEFAULT_VALUES[key] + + return { + "model_name": get_value("model_name"), + "tokenizer_name": get_value("tokenizer_name"), + "model_backend_type": get_value("model_backend_type"), + "model_device": get_value("model_device"), + "escape_input_brackets": get_value("escape_input_brackets"), + "escape_output_brackets": get_value("escape_output_brackets"), + "debug_logging": get_value("debug_logging"), + } + + +def on_ui_settings(): + section = ("dart_upsampler", "Danbooru Tags Upsampler") + shared.opts.add_option( + key="model_name", + info=shared.OptionInfo( + default=DEFAULT_VALUES["model_name"], + label="The model to use for upsampling danbooru tags.", + component=gr.Dropdown, + component_args={"choices": ["p1atdev/dart-v1-sft"]}, + section=section, + ), + ) + shared.opts.add_option( + key="tokenizer_name", + info=shared.OptionInfo( + default=DEFAULT_VALUES["tokenizer_name"], + label="The tokenizer for the upsampling model.", + component=gr.Dropdown, + component_args={"choices": ["p1atdev/dart-v1-sft"]}, + section=section, + ), + ) + shared.opts.add_option( + key="model_backend_type", + info=shared.OptionInfo( + default=DEFAULT_VALUES["model_backend_type"], + label="The type of model backend.", + component=gr.Dropdown, + component_args={"choices": list(MODEL_BACKEND_TYPE.values())}, + section=section, + ).info( + "Original = inefficient computation; ONNX = efficient computing but the model size is very large; ONNX (Quantized) = efficient computation, smallest model file size, and fastest" + ), + ) + shared.opts.add_option( + key="model_device", + info=shared.OptionInfo( + default=DEFAULT_VALUES["model_device"], + label="The device to run upsampling model on.", + component=gr.Textbox, + section=section, + ), + ) + shared.opts.add_option( + key="escape_input_brackets", + info=shared.OptionInfo( + default=DEFAULT_VALUES["escape_input_brackets"], + label="Allow escaped brackets in input prompt.", + component=gr.Checkbox, + section=section, + ), + ) + shared.opts.add_option( + key="escape_output_brackets", + info=shared.OptionInfo( + default=DEFAULT_VALUES["escape_output_brackets"], + label="Escape brackets in upsampled tags.", + component=gr.Checkbox, + section=section, + ), + ) + shared.opts.add_option( + key="debug_logging", + info=shared.OptionInfo( + default=DEFAULT_VALUES["debug_logging"], + label="Enblae debug logging.", + component=gr.Checkbox, + section=section, + ), + ) diff --git a/extensions/sd-danbooru-tags-upsampler/dart/utils.py b/extensions/sd-danbooru-tags-upsampler/dart/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..c73f76a7a2e966ece6268f949bb381fe9700809b --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/dart/utils.py @@ -0,0 +1,106 @@ +import logging +import random +import re + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from modules.processing import ( + StableDiffusionProcessingTxt2Img, + StableDiffusionProcessingImg2Img, + ) + + StableDiffusionProcessing = ( + StableDiffusionProcessingTxt2Img | StableDiffusionProcessingImg2Img + ) +else: + StableDiffusionProcessing = Any + + +logger = logging.getLogger(__name__) + +SEED_MIN = 0 +SEED_MAX = 2**32 - 1 + +# special symbols in webui prompt syntax +SPECIAL_SYMBOL_PATTERN = re.compile(r"([()])") + +# escaped and unescaped symbols pair to unescaping processing +ESCAPED_SYMBOL_PATTERNS = {re.compile(r"\\\("): "(", re.compile(r"\\\)"): ")"} + +# a pttern of escaping special symbols in regex +TAG_ESCAPE_SYMBOL_PATTERN = re.compile(r"[\\^${}[\]()?+|]") + + +def get_random_seed(): + return random.randint(SEED_MIN, SEED_MAX) + + +# ref: https://github.com/adieyal/sd-dynamic-prompts/blob/main/sd_dynamic_prompts/helpers.py +def get_upmsapling_seeds( + p: StableDiffusionProcessing, + num_seeds: int, + custom_seed: int, +) -> list[int]: + if p.subseed_strength != 0: + subseed = int(p.all_subseeds[0]) + else: + subseed = int(p.subseed) + + if subseed == -1: + subseed = get_random_seed() + + if custom_seed != -1: + # if custom_seed is specified, use the same seeds for prompts + all_subseeds = [int(custom_seed)] * num_seeds + else: + # increase randomness by adding images' seeds + all_subseeds = [ + (int(p.seed) + subseed + i) % SEED_MAX for i in range(num_seeds) + ] + + return all_subseeds + + +def escape_webui_special_symbols(tags: list[str]) -> list[str]: + """Returns tags only which has brackets escaped.""" + + escaped_tags = [SPECIAL_SYMBOL_PATTERN.sub(r"\\\1", tag) for tag in tags] + + return escaped_tags + + +def unescape_webui_special_symbols(tags: list[str]) -> list[str]: + """Returns all tags after unescaping.""" + unescaped_tags = [] + + for tag in tags: + for pattern, replace_to in ESCAPED_SYMBOL_PATTERNS.items(): + tag = pattern.sub(replace_to, tag) + + unescaped_tags.append(tag) + + return unescaped_tags + + +def _get_tag_pattern(tag: str) -> re.Pattern: + """Returns a regex pattern of a tag""" + + if "*" in tag: + tag = tag.replace("*", ".*") + tag = TAG_ESCAPE_SYMBOL_PATTERN.sub(lambda m: "\\" + m.group(0), tag) + else: + # escape all + tag = re.escape(tag) + + return re.compile(tag) + + +def get_patterns_from_tag_list(tags: list[str]) -> list[re.Pattern]: + """Returns regex patterns from tag list""" + return [_get_tag_pattern(tag) for tag in tags] + + +def get_valid_tag_list(tag_text: str) -> list[str]: + """Returns a list of non-empty tags from a tag text""" + return [tag.strip() for tag in tag_text.split(",") if tag.strip() != ""] diff --git a/extensions/sd-danbooru-tags-upsampler/images/sample-1-w.jpg b/extensions/sd-danbooru-tags-upsampler/images/sample-1-w.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dd2faa890f61e05a087738072e1fc2ad5a0ce790 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/sample-1-w.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/sample-1-wo.jpg b/extensions/sd-danbooru-tags-upsampler/images/sample-1-wo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..649d72abee1efa829024e1493eefb30732f717c5 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/sample-1-wo.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/sample-2-w.jpg b/extensions/sd-danbooru-tags-upsampler/images/sample-2-w.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b1df61a5b145f8ad587720ff9629ce86383b07f4 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/sample-2-w.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/sample-2-wo.jpg b/extensions/sd-danbooru-tags-upsampler/images/sample-2-wo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..307aee777268d593b6d3adb4ec20d6937707ec8a Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/sample-2-wo.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/sample-3-w.jpg b/extensions/sd-danbooru-tags-upsampler/images/sample-3-w.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0e49ce56d34085e1b6155227c1b54b270286d1e2 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/sample-3-w.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/sample-3-wo.jpg b/extensions/sd-danbooru-tags-upsampler/images/sample-3-wo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..421b7b9c7c93dd179f5f4067aaaf9a2c031533e7 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/sample-3-wo.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/sample-4-w.jpg b/extensions/sd-danbooru-tags-upsampler/images/sample-4-w.jpg new file mode 100644 index 0000000000000000000000000000000000000000..783d15e82714cbe815829e6857537a910fdd22c2 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/sample-4-w.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/sample-4-wo.jpg b/extensions/sd-danbooru-tags-upsampler/images/sample-4-wo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c5be775475fe04807e2a003f553b98cf9b2abe13 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/sample-4-wo.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/screenshot-1.jpg b/extensions/sd-danbooru-tags-upsampler/images/screenshot-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f784f6dbcbce9e3b440d81c3ecbe6d7b143a2f27 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/screenshot-1.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/screenshot-2.jpg b/extensions/sd-danbooru-tags-upsampler/images/screenshot-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c6440fedc2748d36760d76c8055094e9aeded33e Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/screenshot-2.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-normal-1.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-normal-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..38c2c10eb0f774633cac0b8b621b750d888b8c20 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-normal-1.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-normal-2.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-normal-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7ca4b3b93ce2ccecf19057a790163bea3eeda802 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-normal-2.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-normal-3.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-normal-3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f407fecbea9ae697f8e327194c5ee065a92aebff Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-normal-3.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-unvaried-1.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-unvaried-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a04d2e9654bf4c6aebc2aaadced96de143160b9 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-unvaried-1.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-unvaried-2.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-unvaried-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b52df0d7b37901ac7bef18a6286d695c2ff80deb Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-unvaried-2.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-unvaried-3.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-unvaried-3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7d46a1ecb66fddd35af51937ed53d5f6d6c8794c Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-unvaried-3.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-varied-1.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-varied-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0700811105a40ebabc3d92a0924c44ac5ebb0e52 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-varied-1.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-varied-2.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-varied-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..019feec8aecbafe42837a9b979fa6fb3af71cb91 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-varied-2.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-varied-3.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-varied-3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..48797c6d4b3bfb474c865ba8cfaff7bb438c8a93 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-varied-3.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-very_unvaried-1.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-very_unvaried-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..885f72b0ba3f6d3a9d256fe903f248ee4f09ae60 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-very_unvaried-1.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-very_unvaried-2.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-very_unvaried-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e2720cec6e7b8f684262ceb6e3222ad94810517c Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-very_unvaried-2.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-very_unvaried-3.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-very_unvaried-3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d6ccd61931a1b4d2af78c0125413c86bf9cd9b97 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-very_unvaried-3.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-very_varied-1.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-very_varied-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b4ba9109bee0b82425ee0b8c6bc51e4652b41d0a Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-very_varied-1.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-very_varied-2.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-very_varied-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e0ffc0e1aa866a2b11bc2b5c65f764e31d0dbcb8 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-very_varied-2.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/images/variation-very_varied-3.jpg b/extensions/sd-danbooru-tags-upsampler/images/variation-very_varied-3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8733839283b58ca8c07fbc62fa5551c48325df53 Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/images/variation-very_varied-3.jpg differ diff --git a/extensions/sd-danbooru-tags-upsampler/install.py b/extensions/sd-danbooru-tags-upsampler/install.py new file mode 100644 index 0000000000000000000000000000000000000000..37844d4c50b344fb74a78fef9f0771c0fa58483c --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/install.py @@ -0,0 +1,23 @@ +import launch + +if not launch.is_installed("optimum"): + launch.run_pip( + "install optimum[onnxruntime]", "requirements for Danbooru Tags Upsampler" + ) + + +if not launch.is_installed("onnxruntime"): + launch.run_pip( + "install optimum[onnxruntime]", "requirements for Danbooru Tags Upsampler" + ) + + +if launch.is_installed("tensorflow"): + show_result = launch.run( + f'"{launch.python}" -m pip show tensorflow', + ) + if "2.15.1" not in show_result: + launch.run_pip( + "install -U tensorflow==2.15.1", + "tensorflow for Danbooru Tags Upsampler to avoid the error with transformers==4.30.2", + ) diff --git a/extensions/sd-danbooru-tags-upsampler/localizations/ja_JP.json b/extensions/sd-danbooru-tags-upsampler/localizations/ja_JP.json new file mode 100644 index 0000000000000000000000000000000000000000..83b9244e0a9056ab51644207a18a248d4e797088 --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/localizations/ja_JP.json @@ -0,0 +1,39 @@ +{ + "Danbooru Tags Upsampler": "Danbooru タグアップサンプラー", + "Enabled": "有効にする", + "Total tag length": "タグの合計長", + "very short": "とても短い", + "short": "短い", + "long": "長い", + "very long": "とても長い", + "Ban tags": "禁止タグ", + "Separate with comma. Using `*` matches to any character (e.g. `* ears` matches to `animal ears`, `cat ears`, ...)": "カンマで区切ります。 `*` は全ての文字列にマッチします。(例: `* ears` は `animal ears`, `cat ears` 等にマッチします)", + "Seed for upsampling tags": "タグのアップサンプリング用シード値", + "Randomize": "ランダム化", + "Shuffle": "シャッフル", + "Upsampling timing": "アップサンプルのタイミング", + "Before applying other prompt processings": "他のプロンプト加工処理の実行前", + "After applying other prompt processings": "他のプロンプト加工処理の実行後", + "_Prompt upsampling will be applied to **only the first image in batch**, **before** sd-dynamic-promps and the webui's styles feature are applied_": "_アップサンプリングは sd-dynamic-prompts や webui の styles が適用される**前**に、**バッチの一番最初の画像のみ**に対して適用されます_", + "_Prompt upsampling will be applied to **all images in batch**, **after** sd-dynamic-promps and the webui's styles feature are applied_": "_アップサンプリングは sd-dynamic-prompts や webui の styles が適用された**後**に、**バッチのすべての画像**に対して適用されます_", + "Variety level": "多様性レベル", + "Just easy presets of generation config below": "下の生成オプションのプリセットです。", + "very unvaried": "非常に単調", + "unvaried": "単調", + "normal": "普通", + "varied": "多様", + "very varied": "非常に多様", + "Generation config": "生成オプション", + "← less random | more random →": "← 低ランダム性 | 高ランダム性 →", + "← more random, less computation | less random, more computation →": "← 低ランダム性, 少計算量 | 高ランダム性, 多計算量 →", + "The model to use for upsampling danbooru tags.": "Danbooru タグのアップサンプルに利用するモデル", + "The tokenizer for the upsampling model.": "アップサンプルモデルで使うトークナイザー", + "The type of model backend.": "モデルのバックエンド", + "Original = inefficient computation; ONNX = efficient computing but the model size is very large; ONNX (Quantized) = efficient computation, smallest model file size, and fastest": "Original = 計算効率よくない; ONNX = 計算効率良いがモデルサイズが大きい; ONNX (Quantized) = 計算効率が良く、モデルサイズも小さく、最速", + "Original": "オリジナル", + "ONNX (Quantized)": "ONNX (量子化)", + "The device to run upsampling model on.": "アップサンプルモデルを実行するデバイス", + "Allow escaped brackets in input prompt.": "入力プロンプト中のエスケープされた括弧を許容する", + "Escape brackets in upsampled tags.": "出力されるタグの括弧をエスケープする", + "Enable debug logging.": "デバッグログを有効にする" +} diff --git a/extensions/sd-danbooru-tags-upsampler/metadata.ini b/extensions/sd-danbooru-tags-upsampler/metadata.ini new file mode 100644 index 0000000000000000000000000000000000000000..8ef805066872dd5784d545c9e149d0be3de1c836 --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/metadata.ini @@ -0,0 +1,78 @@ +# This section contains information about the extension itself. +# This section is optional. +[Extension] + +# A canonical name of the extension. +# Only lowercase letters, numbers, dashes and underscores are allowed. +# This is a unique identifier of the extension, and the loader will refuse to +# load two extensions with the same name. If the name is not supplied, the +# name of the extension directory is used. Other extensions can use this +# name to refer to this extension in the file. +Name = sd-danbooru-tags-upsampler + +# A comma-or-space-separated list of extensions that this extension requires +# to be installed and enabled. +# The loader will generate a warning if any of the extensions in this list is +# not installed or disabled. +; Requires = another-extension, yet-another-extension + +# Declaring relationships of folders +# +# This section declares relations of all files in `scripts` directory. +# By changing the section name, it can also be used on other directories +# walked by `load_scripts` function (for example `javascript` and `localization`). +# This section is optional. +[scripts] + +# A comma-or-space-separated list of extensions that files in this folder requires +# to be present. +# It is only allowed to specify an extension here. +# The loader will generate a warning if any of the extensions in this list is +# not installed or disabled. +; Requires = another-extension, yet-another-extension + +# A comma-or-space-separated list of extensions that files in this folder wants +# to be loaded before. +# It is only allowed to specify an extension here. +# The loading order of all files in the specified folder will be moved so that +# the files in the current extension are loaded before the files in the same +# folder in the listed extension. +; Before = another-extension, yet-another-extension + +# A comma-or-space-separated list of extensions that files in this folder wants +# to be loaded after. +# Other details are the same as `Before` key. +After = sd-dynamic-prompts + +# Declaring relationships of a specific file +# +# This section declares relations of a specific file to files in the same +# folder of other extensions. +# By changing the section name, it can also be used on other directories +# walked by `load_scripts` function (for example `javascript` and `localization`). +# This section is optional. +; [scripts/another-script.py] + +# A comma-or-space-separated list of extensions/files that this file requires +# to be present. +# The `Requires` key in the folder section will be prepended to this list. +# The loader will generate a warning if any of the extensions/files in this list is +# not installed or disabled. +# It is allowed to specify either an extension or a specific file. +# When referencing a file, the folder name must be omitted. +# +# For example, the `yet-another-extension/another-script.py` item refers to +# `scripts/another-script.py` in `yet-another-extension`. +; Requires = another-extension, yet-another-extension/another-script.py, xyz_grid.py + +# A comma-or-space-separated list of extensions that this file wants +# to be loaded before. +# The `Before` key in the folder section will be prepended to this list. +# The loading order of this file will be moved so that this file is +# loaded before the referenced file in the list. +; Before = another-extension, yet-another-extension/another-script.py, xyz_grid.py + +# A comma-or-space-separated list of extensions that this file wants +# to be loaded after. +# Other details are the same as `Before` key. +; After = another-extension, yet-another-extension/another-script.py, xyz_grid.py diff --git a/extensions/sd-danbooru-tags-upsampler/scripts/__pycache__/dart_upsampler.cpython-310.pyc b/extensions/sd-danbooru-tags-upsampler/scripts/__pycache__/dart_upsampler.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4055df4e16bc2a382e08597a5c297998626d61e Binary files /dev/null and b/extensions/sd-danbooru-tags-upsampler/scripts/__pycache__/dart_upsampler.cpython-310.pyc differ diff --git a/extensions/sd-danbooru-tags-upsampler/scripts/dart_upsampler.py b/extensions/sd-danbooru-tags-upsampler/scripts/dart_upsampler.py new file mode 100644 index 0000000000000000000000000000000000000000..eb936affdf6254c9402adc5810b6ee91ef64dddd --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/scripts/dart_upsampler.py @@ -0,0 +1,472 @@ +import logging + + +import gradio as gr +from transformers import set_seed + +from modules import script_callbacks +import modules.scripts as scripts +from modules.scripts import basedir +from modules.processing import ( + StableDiffusionProcessingTxt2Img, + StableDiffusionProcessingImg2Img, +) +from modules.shared import opts + +from dart.generator import DartGenerator +from dart.analyzer import DartAnalyzer +from dart.settings import on_ui_settings, parse_options +import dart.utils as utils +from dart.utils import SEED_MAX + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +TOTAL_TAG_LENGTH = { + "VERY_SHORT": "very short", + "SHORT": "short", + "LONG": "long", + "VERY_LONG": "very long", +} + +TOTAL_TAG_LENGTH_TAGS = { + TOTAL_TAG_LENGTH["VERY_SHORT"]: "<|very_short|>", + TOTAL_TAG_LENGTH["SHORT"]: "<|short|>", + TOTAL_TAG_LENGTH["LONG"]: "<|long|>", + TOTAL_TAG_LENGTH["VERY_LONG"]: "<|very_long|>", +} + +PROCESSING_TIMING = { + "BEFORE": "Before applying other prompt processings", + "AFTER": "After applying other prompt processings", +} + +VARIETY_OPTIONS = { + "VERY_UNVARIED": "very unvaried", + "UNVARIED": "unvaried", + "NORMAL": "normal", + "VARIED": "varied", + "VERY_VARIED": "very varied", +} +# value: kye +VARIETY_OPTIONS_VK = {v: k for k, v in VARIETY_OPTIONS.items()} + +VARIETY_PRESETS = { + # [temperature, top_p, top_k, num_beams] + "VERY_UNVARIED": [0.85, 0.9, 20, 2], + "UNVARIED": [0.9, 0.95, 20, 1], + "NORMAL": [1.0, 1, 30, 1], + "VARIED": [1.5, 1, 50, 1], + "VERY_VARIED": [2.0, 0.9, 100, 1], +} + +extension_dir = basedir() + + +def _join_texts(prefix: str, suffix: str) -> str: + return ", ".join([part for part in [prefix, suffix] if part.strip() != ""]) + + +def _concatnate_texts(prefix: list[str], suffix: list[str]) -> list[str]: + return [_join_texts(prompt, suffix[i]) for i, prompt in enumerate(prefix)] + + +class DartUpsampleScript(scripts.Script): + generator: DartGenerator + analyzer: DartAnalyzer + + def __init__(self): + super().__init__() + + self.options = parse_options(opts) + if self.options["debug_logging"]: + logger.setLevel(logging.DEBUG) + + self.generator = DartGenerator( + self.options["model_name"], + self.options["tokenizer_name"], + self.options["model_backend_type"], + ) + self.analyzer = DartAnalyzer( + extension_dir, + self.generator.get_vocab_list(), + self.generator.get_special_vocab_list(), + ) + + script_callbacks.on_ui_settings(on_ui_settings) + + def title(self): + return "Danbooru Tags Upsampler" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with gr.Accordion(open=False, label=self.title()): + with gr.Column(): + enabled_check = gr.Checkbox(label="Enabled", value=False) + + tag_length_radio = gr.Radio( + label="Total tag length", + choices=list(TOTAL_TAG_LENGTH.values()), + value=TOTAL_TAG_LENGTH["LONG"], + ) + ban_tags_textbox = gr.Textbox( + label="Ban tags", + info="Separate with comma. Using `*` matches to any character (e.g. `* ears` matches to `animal ears`, `cat ears`, ...)", + value="", + placeholder="umbrella, official *, * text, * background, ...", + ) + + with gr.Group(): + with gr.Row(): + seed_num_input = gr.Number( + label="Seed for upsampling tags", + minimum=-1, + maximum=SEED_MAX, + step=1, + scale=4, + value=-1, + ) + seed_random_btn = gr.Button(value="Randomize") + seed_shuffle_btn = gr.Button(value="Shuffle") + + def click_random_seed_btn(): + return -1 + + seed_random_btn.click( + click_random_seed_btn, outputs=[seed_num_input] + ) + + def click_shuffle_seed_btn(): + return utils.get_random_seed() + + seed_shuffle_btn.click( + click_shuffle_seed_btn, outputs=[seed_num_input] + ) + + with gr.Group(): + process_timing_dropdown = gr.Dropdown( + label="Upsampling timing", + choices=list(PROCESSING_TIMING.values()), + value=PROCESSING_TIMING["AFTER"], + ) + + def on_process_timing_dropdown_changed(timing: str): + if timing == PROCESSING_TIMING["BEFORE"]: + return "_Prompt upsampling will be applied to **only the first image in batch**, **before** sd-dynamic-promps and the webui's styles feature are applied_" + elif timing == PROCESSING_TIMING["AFTER"]: + return "_Prompt upsampling will be applied to **all images in batch**, **after** sd-dynamic-promps and the webui's styles feature are applied_" + raise Exception(f"Unknown timing: {timing}") + + process_timing_md = gr.Markdown( + on_process_timing_dropdown_changed( + process_timing_dropdown.value + ) + ) + + process_timing_dropdown.change( + on_process_timing_dropdown_changed, + inputs=[process_timing_dropdown], + outputs=[process_timing_md], + ) + + with gr.Group(): + variety_preset_radio = gr.Radio( + label="Variety level", + info="Just easy presets of generation config below", + choices=list(VARIETY_OPTIONS.values()), + value=VARIETY_OPTIONS["NORMAL"], + ) + + with gr.Accordion(label="Generation config", open=False): + do_cfg_check = gr.Checkbox( + label="Do CFG", + info="Enables classifier-free guidance, this takes double of computation", + visible=False, + ) + negative_prompt_textbox = gr.Textbox( + label="Negative tags", + placeholder="simple background, ...", + value="", + visible=False, + ) + cfg_scale_slider = gr.Slider( + label="CFG scale", + minimum=0.1, + maximum=3.0, + value=1.5, + step=0.1, + visible=False, + ) + + temperature_slider = gr.Slider( + label="Temperature", + info="← less random | more random →", + maximum=4.0, + minimum=0.1, + step=0.01, + value=1.0, + ) + top_p_slider = gr.Slider( + label="Top p", + info="← less random | more random →", + maximum=1.0, + minimum=0.0, + step=0.01, + value=1.0, + ) + top_k_slider = gr.Slider( + label="Top k", + info="← less random | more random →", + maximum=1000, + minimum=10, + step=1, + value=20, + ) + + num_beams_slider = gr.Slider( + label="Num beams", + info="← more random, less computation | less random, more computation →", + maximum=20, + minimum=1, + step=1, + value=1, + ) + + # update generation config when the preset is changed + def on_variety_preset_radio_change(level: str): + if level in VARIETY_OPTIONS.values(): + return VARIETY_PRESETS[VARIETY_OPTIONS_VK[level]] + else: + raise Exception(f"Unknown variety level: {level}") + + variety_preset_radio.change( + on_variety_preset_radio_change, + inputs=[variety_preset_radio], + outputs=[ + temperature_slider, + top_p_slider, + top_k_slider, + num_beams_slider, + ], + ) + + return [ + enabled_check, + tag_length_radio, + ban_tags_textbox, + seed_num_input, + process_timing_dropdown, + # generation config + do_cfg_check, + negative_prompt_textbox, + cfg_scale_slider, + temperature_slider, + top_p_slider, + top_k_slider, + num_beams_slider, + ] + + def process( + self, + p: StableDiffusionProcessingTxt2Img | StableDiffusionProcessingImg2Img, + is_enabled: bool, + tag_length: str, + ban_tags: str, + seed_num: int, + process_timing: str, + # generation config + do_cfg: bool, + negative_prompt: str, + cfg_scale: float, + temperature: float, + top_p: float, + top_k: int, + num_bemas: int, + ): + """This method will be called after sd-dynamic-prompts and the styles are applied.""" + + if not is_enabled: + return + + if process_timing != PROCESSING_TIMING["AFTER"]: + return + + analyzing_results = [self.analyzer.analyze(prompt) for prompt in p.all_prompts] + logger.debug(f"Analyzed: {analyzing_results}") + + upsampling_prompt = [ + self.generator.compose_prompt( + rating=f"{result.rating_parent}, {result.rating_child}", + copyright=result.copyright, + character=result.character, + general=result.general, + length=TOTAL_TAG_LENGTH_TAGS[tag_length], + ) + for result in analyzing_results + ] + logger.debug(f"Upsampling prompt: {upsampling_prompt}") + bad_words_ids = self.generator.get_bad_words_ids(ban_tags) + + upsampling_negative_prompts = [] + if do_cfg and negative_prompt is not None: + negative_analyzing_result = self.analyzer.analyze(negative_prompt) + logger.debug(f"Analyzed (negative): {negative_analyzing_result}") + + for analyzing_result in analyzing_results: + upsampling_negative_prompt = self.generator.compose_prompt( + rating=f"{analyzing_result.rating_parent}, {analyzing_result.rating_child}", + copyright=_join_texts( + analyzing_result.copyright, negative_analyzing_result.copyright + ), + character=_join_texts( + analyzing_result.character, negative_analyzing_result.character + ), + general=negative_analyzing_result.general, + length=TOTAL_TAG_LENGTH_TAGS[tag_length], + ) + upsampling_negative_prompts.append(upsampling_negative_prompt) + + num_images = p.n_iter * p.batch_size + upsampling_seeds = utils.get_upmsapling_seeds( + p, + num_images, + custom_seed=seed_num, + ) + + # this list has only 1 item + upsampled_tags = self._upsample_tags( + upsampling_prompt, + seeds=upsampling_seeds, + temperature=float(temperature), + top_p=float(top_p), + top_k=int(top_k), + num_bemas=int(num_bemas), + bad_words_ids=bad_words_ids, + negative_prompts=upsampling_negative_prompts if do_cfg else None, + cfg_scale=float(cfg_scale), + ) + logger.debug(f"Upsampled tags: {upsampled_tags}") + + # set new prompts + p.all_prompts = _concatnate_texts(p.all_prompts, upsampled_tags) + + def before_process( + self, + p: StableDiffusionProcessingTxt2Img | StableDiffusionProcessingImg2Img, + is_enabled: bool, + tag_length: str, + ban_tags: str, + seed_num: int, + process_timing: str, + # generation config + do_cfg: bool, + negative_prompt: str, + cfg_scale: float, + temperature: float, + top_p: float, + top_k: int, + num_bemas: int, + ): + """This method will be called before sd-dynamic-prompts and the styles are applied.""" + + if not is_enabled: + return + + if process_timing != PROCESSING_TIMING["BEFORE"]: + return + + analyzing_result = self.analyzer.analyze(p.prompt) + logger.debug(f"Analyzed: {analyzing_result}") + + upsampling_prompt = self.generator.compose_prompt( + rating=f"{analyzing_result.rating_parent}, {analyzing_result.rating_child}", + copyright=analyzing_result.copyright, + character=analyzing_result.character, + general=analyzing_result.general, + length=TOTAL_TAG_LENGTH_TAGS[tag_length], + ) + logger.debug(f"Upsampling prompt: {upsampling_prompt}") + bad_words_ids = self.generator.get_bad_words_ids(ban_tags) + + upsampling_negative_prompt = None + if do_cfg and negative_prompt is not None: + negative_analyzing_result = self.analyzer.analyze(p.prompt) + logger.debug(f"Analyzed (negative): {analyzing_result}") + + upsampling_negative_prompt = self.generator.compose_prompt( + rating=f"{analyzing_result.rating_parent}, {analyzing_result.rating_child}", + copyright=_join_texts( + analyzing_result.copyright, negative_analyzing_result.copyright + ), + character=_join_texts( + analyzing_result.character, negative_analyzing_result.character + ), + general=negative_analyzing_result.general, + length=TOTAL_TAG_LENGTH_TAGS[tag_length], + ) + + upsampling_seeds = utils.get_upmsapling_seeds( + p, + num_seeds=1, # only for the first prompt + custom_seed=seed_num, + ) + + # this list has only 1 item + upsampled_tags = self._upsample_tags( + [upsampling_prompt], + seeds=upsampling_seeds, + temperature=float(temperature), + top_p=float(top_p), + top_k=int(top_k), + num_bemas=int(num_bemas), + bad_words_ids=bad_words_ids, + negative_prompts=( + [upsampling_negative_prompt] + if upsampling_negative_prompt is not None + else None + ), + cfg_scale=float(cfg_scale), + ) + logger.debug(f"Upsampled tags: {upsampled_tags}") + + # set a new prompt + p.prompt = _concatnate_texts([p.prompt], upsampled_tags)[0] + + def _upsample_tags( + self, + prompts: list[str], + seeds: list[int], + temperature: float = 1.0, + top_p: float = 1.0, + top_k: int = 20, + num_bemas: int = 1, + bad_words_ids: list[list[int]] | None = None, + negative_prompts: list[str] | None = None, + cfg_scale: float = 1.5, + ) -> list[str]: + """Upsamples tags using provided prompts and returns added tags.""" + + if len(prompts) == 1 and len(prompts) != len(seeds): + prompts = prompts * len(seeds) + + upsampled_tags = [] + for i, (prompt, seed) in enumerate(zip(prompts, seeds, strict=True)): + set_seed(seed) + upsampled_tags.append( + self.generator.generate( + prompt, + temperature=temperature, + top_p=top_p, + top_k=top_k, + num_beams=num_bemas, + bad_words_ids=bad_words_ids, + negative_prompt=( + negative_prompts[i] if negative_prompts is not None else None + ), + cfg_scale=cfg_scale, + ) + ) + return upsampled_tags diff --git a/extensions/sd-danbooru-tags-upsampler/tags/character.txt b/extensions/sd-danbooru-tags-upsampler/tags/character.txt new file mode 100644 index 0000000000000000000000000000000000000000..0228e7b2067c9399320154d08a4daf40d97ef24a --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/tags/character.txt @@ -0,0 +1,12171 @@ +pecorine (summer) (princess connect!) +minami yume +clair (pokemon) +danua (summer) (granblue fantasy) +seele vollerei (stygian nymph) +ushiromiya rudolf +miyako (swimsuit) (blue archive) +grayfia lucifuge +manaka hitomi +ninomiya asuka +essex (azur lane) +eden (honkai impact) +snuffy (vtuber) +amamiya shizuku (hizuki yayoi) +tomoe (queen's blade) +yuzuki roa (1st costume) +winda priestess of gusto +natsuiro matsuri (matsuri's day off) +morichika rinnosuke +igawa asagi +misato (princess connect!) +ayukawa madoka +tianzi +magical ruby +assassin cross (ragnarok online) +nice nature (run&win) (umamusume) +akane (blue archive) +urahara kisuke +kirakishou +nagara (kancolle) +vira (granblue fantasy) +m4 sopmod ii jr +melody (projektmelody) +mirae (closers) +yakumo yukari +bardiche +soren (fire emblem) +wind sneaker (elsword) +hasegawa kodaka +yohane bonaventura +hawks (boku no hero academia) +nidoran (male) +takamachi nanoha (exceed mode) +elsa granhilte +takizawa kyouko +fighting master alleyne +sableye +katou marika +sand cat (kemono friends) +miyuki kazuya +u-1146 +emilia (re:zero) +yukihira souma +kirito (sao-alo) +cu chulainn alter (fate) +lenna charlotte tycoon +circe (fate) +mashiro blan de windbloom +ellin meiji +suomi (girls' frontline) +amber (genshin impact) +heroine (dq3) +yarai miu +rosa (tears of themis) +naganohara mio +giuseppe garibaldi (kancolle) +vodka (umamusume) +coco bandicoot +the emperor (arknights) +diancie +changsheng (genshin impact) +kageyama shien +ume (kancolle) +reijo (blue archive) +cul +skyfire (arknights) +osaragi hazumu +luna (punishing: gray raven) +ninian (fire emblem) +carmilla (swimsuit rider) (fate) +velvet (odin sphere) +yama raja (elsword) +palmtop tiger +nono hana +tuscaloosa (kancolle) +kagami hayato +jason voorhees +maria (fire emblem) +rugal bernstein +kenny mccormick +faust (arknights) +aki rosenthal (1st costume) +klaudia valentz +numby (honkai: star rail) +iwatooshi +mitsukai dokuro +ashley graham +guild girl (goblin slayer!) +artemis (fate) +rebecca chambers +rolo lamperouge +golden snub-nosed monkey (kemono friends) +charles-henri sanson (fate) +spongebob squarepants (character) +farrah (granblue fantasy) +yan qing (fate) +frederica sawyer +tenten (naruto) +dimitri alexandre blaiddyd +vegetto +inkling +jeanne (bayonetta) +fighting miku (project voltage) +jolteon +ienaga mugi +shimada arisu +tomori nao +kazama asuka +arcueid brunestud +junketsu +kyoichi (live a hero) +ichii yui +k/da kai'sa +jochuu-san +scathach skadi (fate) +plachta +grimmsnarl +aeru (simoun) +strict nun (diva) +kaerre +president maa +nagasone kotetsu +ima-no-tsurugi +hiyori (princess connect!) +zero(z) (mega man) +tsareena +masurao (sekaiju) +numel +akashi kuniyuki +saya (twrlare) +paffy pafuricia +smallfry (splatoon) +fushiguro megumi +yakumo ran +akaza (kimetsu no yaiba) +stylet +itou noemi +sheeta +akagi (kancolle) +katou megumi +producer (idolmaster) +virginia maxwell +satsuki rin +suu (monster musume) +neferpitou +mogami shizuka +judal +zangoose +karenina (punishing: gray raven) +katsushika hokusai (swimsuit saber) (fate) +suzuran (yukibare) (arknights) +sarashiki tatenashi +akari (raigou) +ichinose honami (youjitsu) +emmet (pokemon) +bort +sheik +rogue titan +yukino bijin (umamusume) +dainsleif (genshin impact) +hirato (kancolle) +kusugawa sasara +fighter (7th dragon) +usuzawa sae +fujimoto rina +shiba tomoshibi +matsuoka miu +lin (arknights) +genny (fire emblem) +gilgamesh (caster) (fate) +haxorus +citrinne (fire emblem) +komakusa sannyo +aether (genshin impact) +el (girls und panzer) +suletta mercury +f91 gundam +dracule mihawk +black jack (character) +izayoi liko +kuga natsuki +bardiche (standby form) +sura (ragnarok online) +sakura nene +kamisato ayaka (heytea) +luigi di savoia duca degli abruzzi (kancolle) +mayuzumi kai +master artoria +kamiya midori +miyamoto rei +arona (blue archive) +chii +tympole +reaper (final fantasy) +sazanami (kancolle) +abigail williams (festival outfit) (fate) +senjougahara hitagi +amitie (puyopuyo) +axel syrios +joy (pokemon) +itsumi erika +kenjou akira +dusty (gravity daze) +faruzan (genshin impact) +mercedes von martritz +katarina (league of legends) +chabashira tenko +charlotte aulin +ikezawa hanako +edward elric +seaport summer princess +family computer robot +yukino (blue archive) +akishimo (kancolle) +shanghai doll +italia (kancolle) +alcina dimitrescu +lancet-2 (arknights) +aliza (granblue fantasy) +kiran (male) (fire emblem) +adachi tooru +masaki sasami jurai +takei junko +jean kirchstein +ia (vocaloid) +ryuuhou (kancolle) +nunnally vi britannia +swim swim +takakamo shizuno +d.va (overwatch) +yorktown ii (azur lane) +sakagami tomoyo +katori (kancolle) +prinz eugen (unfading smile) (azur lane) +merii (mazohaha) +cid highwind +dark haired kappa +yamada michiru +hippogriff +jibanyan +kira tsubasa +thief (ragnarok online) +ezo red fox (kemono friends) +repede (tales) +tristana +himemori luna (3rd costume) +amagi yukiko +yuri lowell +shermie (kof) +kinugasa kai ni (kancolle) +yamashio maru (kancolle) +lilith (fire emblem) +luz noceda +weedle +kamille bidan +ushiromiya battler +morinaka kazaki +melusine (fate) +kuraishi tanpopo +ume-sensei +leeron littner +saotome mary +shamare (arknights) +liskarm (arknights) +hoshimachi suisei +ramza beoulve +miramikaru riran +hisou tensoku +rock howard +marshtomp +susie (deltarune) +seiryuu (kemono friends) +kurosawa rin (aikatsu!) +ishigami nozomi +kumai natsu +manticore (arknights) +lina (michihasu) +briar rose (sinoalice) +phoebe (pokemon) +ajiro shinpei +peach panther (kemono friends) +kirara (blue archive) +bracelet girls +parlor dragonmaid +algerie (white sand paradise) (azur lane) +tsubasa ryuuji +luca kaneshiro (1st costume) +takara miyuki +tsu-class light cruiser +su-san +charybdis (azur lane) +tanino gimlet (umamusume) +silence suzuka (umamusume) +lilia vanrouge +la pluma (summer flowers) (arknights) +shirai hinako +takasu ryuuji +aoba tsugumi +ikazuchi (kancolle) +chouno ami +yukikaze (kancolle) +inoue yurina +medusa (fate) +keroro +brest (azur lane) +cure la mer +priite hari (torriet) +aila jyrkiainen +isla (plastic memories) +horn (arknights) +paladin (final fantasy) +aron +snow white (grimm) +yuni (real) (princess connect!) +alucard (hellsing) +healslime +mayano top gun (umamusume) +ushiwakamaru (fate) +reala (tales) +sakuraba rola +oerba dia vanille +alipheese fateburn xvi +manhattan cafe (umamusume) +seraphine (league of legends) +yae (ganbare goemon) +annie mei +samuel b. roberts (kancolle) +sakuragi mai +akashi yuuna +kafuru (senran kagura) +kamikaze (kancolle) +filia (skullgirls) +haruna (blue archive) +boros (ouro kronii) +arima senka +freyja (fire emblem) +sugamo mutsuki +ho-class light cruiser +steven stone +vampire (azur lane) +terra (kingdom hearts) +accelerator (toaru majutsu no index) +taihou (azur lane) +turn a gundam (mobile suit) +special week (umamusume) +mega man volnutt +miku (lee) +hie (hiememiko) +feower (granblue fantasy) +heavy cruiser princess +usami taiga +lrl (last origin) +romulus quirinus (fate) +geomancer (fft) +luocha (honkai: star rail) +white mage +yuuki (princess connect!) +riddle rosehearts +chel (the road to el dorado) +illustrious (maiden lily's radiance) (azur lane) +hong kong (hetalia) +souza samonji +mym (dragalia lost) +mordred (memories at trifas) (fate) +mandricardo (fate) +araki hina +lucas (mother 3) +stg44 (girls' frontline) +shalon +kiroranke +sinon (sao-alo) +hagoromo lala +nijihara ink +shibuki ran +alphonse elric +titan (shingeki no kyojin) +goutokuji mike +shiny luminous +ptilopsis (arknights) +amamiya kokoro +tamamo no mae (swimsuit lancer) (first ascension) (fate) +subaru nakajima +kobayakawa yutaka +gilles de rais (saber) (fate) +enterprise (warship girls r) +van arkride +fujisawa yayoi (uchuu no stellvia) +rex (xenoblade) +zero two (darling in the franxx) +sajou ayaka (fate/prototype) +kshatriya +hiiragi yuzu +murakami tomoe +mashiro (blue archive) +yagami taichi +senju muramasa +princess chain chomp +sorey (tales) +klee (blossoming starlight) (genshin impact) +isuzu (azur lane) +chrollo lucilfer +lady nagant +yuki miku (2010) +daifuku (yukihana lamy) +shionji yuuko +shimabara elena +enterprise (azur lane) +taiga (ookami mio) +north carolina (azur lane) +taihou (sweet time after school) (azur lane) +ganesha (fate) +hyougintou +nia teppelin +ronald mcdonald +karna (fate) +gascogne (azur lane) +lulu (league of legends) +falin thorden +hibiki yuuta +uryuu minene +garnet til alexandros xvii +isayama yomi +min min (arms) +theresa apocalypse +super sailor jupiter +suzuya (kancolle) +koffing +nino (fire emblem) +sophia esteed +cutiefly +yor briar +hakui koyori (1st costume) +girl with a blonde braid (tomoshibi hidekazu) +eunectes (arknights) +kajiki yumi +ceylon (arknights) +carmen (project moon) +alolan meowth +sakuma rinko +goredolf musik +lynette (genshin impact) +cassandra alexandra +poppi alpha (xenoblade) +riza hawkeye +pohwaran +chloe von einzbern (beast style) +kazami yuuka +bewear +biwa hayahide (umamusume) +mimi houllier von schwarzlang +okita souji alter (first ascension) (fate) +morrigan aensland +kuraki suzuna +magical mirai miku (2017) +hachune miku +tangaroa (housamo) +yatsu murasaki +ningyo hime (sinoalice) +kusakabe satsuki +vietnam (hetalia) +cerberus (last origin) +duo maxwell +nqrse +miyamura izumi +tachikoma +masquerain +taihou (kancolle) +cure black +gold experience +elza straherz +kazuma asogi +rebecca bluegarden +battleship princess +inuyama tamaki +dog child (doitsuken) +saber lily +kushizaki (vtuber) +artoria pendragon (swimsuit archer) (fate) +nishihara yasoko +an-94 (girls' frontline) +eva 01 +anya melfissa (1st costume) +hero (faraway) (omori) +kazanari tsubasa +sakura miko +scamp (kancolle) +broly (dragon ball z) +hungry nun (diva) +beidou (genshin impact) +neptune (neptunia) +reiko holinger +makinami mari illustrious +saikawa riko +kitarou +fionna the human girl +hoshina hikaru +hawkeye (marvel) +minnie parker +shirase sakuya +hiro (hidamari sketch) +catherine +mikasa (azur lane) +tweyen (granblue fantasy) +uni (neptunia) +high priest (ragnarok online) +rena erindel +nao (dream c club) +ravel phenex +kosaka chihiro +regulus (reverse:1999) +a-chan (hololive) +tokino sora (1st costume) +ribeyrolles (girls' frontline) +kako (kemono friends) +venom snake +drifloon +kagamine rin (vocaloid4) +dana zane +flametail (arknights) +jinx (league of legends) +threo (granblue fantasy) +waai fu (arknights) +kisaragi momo +repulse (azur lane) +red panda (kemono friends) +kushieda minori +four murasame +greater roadrunner (kemono friends) +mutsuki (new year) (blue archive) +joey jones +matsurisu +muramatsu sakura +hisakawa nagi +hidan (naruto) +shoukaku (kancolle) +yuuki makoto (persona 3) +etie (fire emblem) +tamaki mari +pawn (dragon's dogma) +warfarin (arknights) +maria (hayate no gotoku!) +ibuki douji (swimsuit berserker) (first ascension) (fate) +kasamoto eri +lilim (monster girl encyclopedia) +amy (suisei no gargantia) +kneesocks (psg) +marcille +nagato (azur lane) +type 97 (girls' frontline) +golbez +hammann (azur lane) +hyuuga neji +shishiro botan (3rd costume) +scathach (fate) +idw (girls' frontline) +kise ryouta +envy (fma) +colt revolver (girls' frontline) +deedlit +mizusaki tsubame +commandant (punishing: gray raven) +saber (fate) +island fox (kemono friends) +toujou koneko +iwato suzume +hyakumantenbara salome +hythlodaeus +queen elizabeth (azur lane) +elphelt valentine +mizuki shiranui +tangela +cis (tiger & bunny) +specter (arknights) +chansey +lloyd bannings +megurine luka +gareth (fate) +shinonome nano +chao (sonic) +yunyun (konosuba) +ikaruga (senran kagura) +volcarona +klein (sao) +krile mayer baldesion (ff5) +suigintou +irene (arknights) +jingei (kancolle) +shadow the hedgehog +tony tony chopper +samuel oak +welrod mkii (girls' frontline) +milo (pokemon) +ursula (23) +abyssal admiral (kancolle) +scary monsters (stand) +iwasaki minami +popplio +junko (blue archive) +yumi (senran kagura) +kasugano sora +deerling (spring) +nono (top wo nerae 2!) +hayashio (kancolle) +hebiyoi tier +pyukumuku +tinkaton +miles edgeworth +josuiji shinri +reppuu (kancolle) +amane kanata (another world) +saria (zelda) +don-chan (usada pekora) +ghirahim +ruquia +barret wallace +sandshrew +may of doom +tsukuyo (gintama) +honda futayo +scharnhorst (azur lane) +galarian corsola +kuroi mato +yamiyono moruru +victor (pokemon) +cuora (arknights) +carole peppers +yuzuriha inori +kuroki tomoki +marui futaba +yuugumo (kancolle) +titania (fire emblem) +seychelles (hetalia) +cogita (pokemon) +yuuki setsuna (love live!) +fuwawa abyssgard +sekomumasada sensei +fate testarossa (impulse form) +unryuu kai (kancolle) +hoshikawa mafuyu +kaji ryouji +kicchou yachie +natori sana +assassin (ragnarok online) +tenkyuu chimata +tougou hifumi +pikl (elira pendora) +fischl (ein immernachtstraum) (genshin impact) +royal penguin (kemono friends) +cradily +female doctor (arknights) +haguro kai ni (kancolle) +zas m21 (girls' frontline) +ashlock (arknights) +engineer (tf2) +bismarck (azur lane) +tohsaka tokiomi +wakasagihime +venti (archon) (genshin impact) +pipkin pippa +ame-chan (needy girl overdose) +mejiro dober (umamusume) +viridi +shirai kuroko +uzaki hana +horikawa raiko +akashiya moka +lee sin +leina (queen's blade) +shigure (blue archive) +tiger (kemono friends) +fernandia malvezzi +nate (pokemon) +venera-sama +yveltal +matsuri (teriyaki) +catra +plain doll +shirakami fubuki (new year) +koizumi itsuki (female) +megatron +ilfa (to heart) +chuck (psg) +serah farron +kotonoha akane +mudrock (silent night) (arknights) +ch'en the holungday (arknights) +leona (league of legends) +morte (arknights) +anubis (mythology) +submarine princess +jenet behrn +alca (wakatanka4) +neroli (pokemon) +gumiya +ibaraki kasen +vf-1s +shiki (senran kagura) +insane black rock shooter +tsukikage yuri +senya (dq11) +bob-omb +yorita yoshino +nerissa ravencroft +haruno sakura +zero (mega man) +tsukumo benben +nadja applefield +shiranui flare (1st costume) +mesousa +jigen daisuke +akira (blue archive) +deutschland (service time?!) (azur lane) +otomachi una (talkex) +dvalin (genshin impact) +fairy maid (touhou) +athena (granblue fantasy) +serika (blue archive) +jill stingray +flower (vocaloid3) +fukuzawa yumi +kazagumo kai ni (kancolle) +i-26 (kancolle) +rinne berlinetta +kaburamaru +m99 (girls' frontline) +mew ichigo +sherlock holmes +aircraft carrier princess +milk (yes! precure 5) +kana (female) (fire emblem) +fionn mac cumhaill (fate/grand order) +midori (maid) (blue archive) +mizuhara koyomi +chibikko (morihito) +mareanie +ushiromiya maria +orianna (league of legends) +futoshi (darling in the franxx) +mitsurugi meiya +makino kanna +hans christian andersen (fate) +usami mizuki +banzoin hakka +no.21 (punishing: gray raven) +death the kid +xingchen +creamy mami +nagi (akito) +luigi +midori (blue archive) +shiraishi an +kotohara hinari +cafe (cafe-chan to break time) +elise (league of legends) +sakamoto ryuuji +nonomi (swimsuit) (blue archive) +natsuiro matsuri (5th costume) +taihou (phoenix's spring song) (azur lane) +arima senne +shiromanta (character) +massachusetts (dressed to impress) (azur lane) +hachiroku (maitetsu) +blissey +fiora (xenoblade) +ibuki munemasa +char aznable +tsubasa (kureha) +hortensia (fire emblem) +tama kai ni (kancolle) +kagehira mika +doll (ib) +girl (deemo) +saigyouji yuyuko +gawr gura (2nd costume) +sirin +tadano hitohito +sam porter bridges +daidou sayo +tokoyami towa +new submarine princess +mono (shadow of the colossus) +karakuri chachamaru +old snake +featherine augustus aurora +subaru (houkago no pleiades) +star guardian jinx +moona hoshinova (1st costume) +warspite (kancolle) +maou (maoyuu) +ookami mio (1st costume) +sharuru (dokidoki! precure) +steenee +vega (street fighter) +yagyuu munenori (fate) +cliffheart (arknights) +kuriyama mirai +kenshiro +kamori sayaka +sideroca (arknights) +maou sadao +they (kiman) +anchorage oni +murasame (senren) +robin (female) (fire emblem) +pearl fey +li (rob ishi) +hanae (blue archive) +kataoka yuuki +izumi (blue archive) +tuxedo kamen +nahida (genshin impact) +merchant (dq3) +kousaka yukiho +amy rose +maita rui +phrenapates (blue archive) +archetype earth +raphtalia +senko (sewayaki kitsune no senko-san) +pavolia reine (1st costume) +fujiwara chika +sawatari akane (chainsaw man) +hiei (yu yu hakusho) +scarlet ibis (kemono friends) +ryuumonbuchi touka +griseo +giratina +seele vollerei (starchasm nyx) +trailblazer (honkai: star rail) +alcremie (strawberry sweet) +miyako (princess connect!) +goat-chan (enarane) +nikaidou saki +daidouji tomoyo +kamen rider gaim +merchant (ragnarok online) +tentacruel +nishizono chigusa +tailtiu (fire emblem) +diane (nanatsu no taizai) +forte (shingeki no bahamut) +nagayoshi subaru +arisa bannings +kirishima (aoki hagane no arpeggio) +kiryu coco (1st costume) +hinata (pure pure) +nikaidou (dorohedoro) +beagle (arknights) +verniy (kancolle) +popola +soleil (fire emblem) +saijou claudine +bedivere (fate) +ryugasaki rene (2nd costume) +jakuzure nonon +manabe nodoka +haruna (track) (blue archive) +cleffa +komaeda nagito +shiki (samurai spirits) +bakura ryou +rumi (girls und panzer) +yuzuhara haruka +baizhu (genshin impact) +utage (disguise) (arknights) +applin +fuu (samurai champloo) +yurishiro ginko +elizabeth bathory (halloween caster) (fate) +lappland (refined horrormare) (arknights) +magisa (granblue fantasy) +rumia +toriel +non-human admiral (kancolle) +akagi (azur lane) +asashio (kancolle) +caenis (fate) +poochyena +jeanne d'arc (azur lane) +reizei mako +akebono (kancolle) +roronoa zoro +dokuro-kun (houshou marine) +kiyohime (third ascension) (fate) +vaan +mammon (umineko) +usada pekora (prisoner) +hamburglar +saratoga (azur lane) +amagi hiiro +kabu (pokemon) +wang liu mei +cibo +z1 leberecht maass (kancolle) +yamato iori +nanachi (made in abyss) +yukihana lamy +barbara (dq6) +kasai amane +dreamer (girls' frontline) +aislinn wishart +coffret (heartcatch precure!) +kawashima mizuki +deviljho +summoner (final fantasy) +uesugi fuutarou +mix (aquarion) +kabutops +scarle yonaguni +hecatia lapislazuli +edmond (nu carnival) +orange (touhou) +ironmouse +blake belladonna +heidi (arknights) +makigumo kai ni (kancolle) +mochizuki himari +chiester00 +nobeta +kako kai ni (kancolle) +seer (apex legends) +kawazoe tamaki +curse maker 2 +duke of york (azur lane) +nagisa kaworu +tanikaze amane +lucy (cyberpunk) +maya (azur lane) +ayase yue +polt +miyano shiho +volkner (pokemon) +hakos baelz (1st costume) +kinjyou (shashaki) +kitakoji hisui +murata himeko (vermillion knight) +momoki run +amuro tooru +akaza akari +inigo (fire emblem) +banagher links +enma ai +torchic +iwasawa masami +south dakota (kancolle) +dawn (pokemon) +atra mixta +mu-12 +toki (blue archive) +tatsumi koutarou +hana (pangya) +neptune (azur lane) +machiko ryou +amagi (azur lane) +cassiopeia (league of legends) +i-class destroyer +anchorage water oni +light cruiser princess +sothis (fire emblem) +enterprise (wind catcher) (azur lane) +sasaki akebi +sailor pluto +yang xiao long +meisho doto (umamusume) +christina sierra +kappa mob (touhou) +tanaka asuka +sella (fate) +robert e. o. speedwagon +bb (fate/extra) +nishina toriko +eizen (tales) +kimura seiko +valis +nino (sunaba suzume) +chainsaw devil +dugtrio +jynx +kaito (vocaloid3) +ogata hyakunosuke +tamamo (mon-musu quest!) +raven cronwell +jarvan iv (league of legends) +haruna (azur lane) +yuzuki yukari +hyakkimaru (dororo) +sabotender +ginko +elize lutus +amanogawa kirara +muten roushi +morag ladair (xenoblade) +type 99 dive bomber +azumi (girls und panzer) +takei hisa +shichimiya satone +alisha diphda +alphys +tanemura koyori +snubbull +sorakado ao +chariot (black rock shooter) +electro cicin mage (genshin impact) +gunner 2 (sekaiju) +sage (valorant) +hero (omori) +kishibe taiga +feraligatr +leonardo da vinci (rider) (fate) +n (pokemon) +koko hekmatyar +yamato kai ni (kancolle) +santa claus +honma meiko +carly nagisa +nemoto hina +aqua (kingdom hearts) +aikawa chiho +inkling girl +iris heart +ump9 (shiba investigator) (girls' frontline) +wakan tanka +persicaria (neural cloud) +raphiel shiraha ainsworth +myaku-myaku +matsumoto yoriko +udagawa tomoe +ephnel +renge (blue archive) +king dedede +hattori shizuka +megu (blue archive) +rodion (project moon) +asakusa midori +eagle spirit (touhou) +atarashi ako +scaramouche (kabukimono) (genshin impact) +mudkip +poniko (lielos) +omaru polka (3rd costume) +hirako shinji +donquixote rocinante +komaro-chan +dima (girls' frontline) +ib (ib) +mia taylor +hiryuu kai ni (kancolle) +faye valentine +gloria (pokemon) +nakiri ayame (hololive summer 2019) +zaizen aoi +type 95 (girls' frontline) +samus aran +chandelure +vaseraga +riesbyfe stridberg +lailah (tales) +yue (cardcaptor sakura) +midna (true) +ri-class heavy cruiser +shun (small) (blue archive) +pipimi +sakakura juuzou +saria (arknights) +gouma hyudor +furukawa sanae +compa +tsukiumi +kageyama torako +misato (summer) (princess connect!) +ursula charistes +aegis (persona) +kururun (precure) +wo-class aircraft carrier +potpourri (heartcatch precure!) +eunie (xenoblade) +atago (azur lane) +latooni subota +y'shtola rhul +cucouroux (granblue fantasy) +niijima sae +machamp +roman torchwick +satono crown (umamusume) +child gilgamesh (fate) +ichii tooru +akitaru oubi +kumoi ichirin +midna (imp) +shirakami fubuki (fubukitek) +irisviel von einzbern (caster) +matsuri (yume no owari) +vertin (reverse:1999) +esidisi +suzunami (kancolle) +cure sky +katsura kotarou +tiki (fire emblem) +sigewinne (genshin impact) +airani iofifteen (1st costume) +scathach skadi (swimsuit ruler) (fate) +nishikigi chisato +sasamori karin +belmond banderas +roxanne (pokemon) +sakamoto (nichijou) +banba mahiru +ilsa (granblue fantasy) +seele vollerei +sakata nemuno +miyamoto musashi (third ascension) (fate) +frosmoth +indianapolis (azur lane) +fakir (princess tutu) +glalie +anila (granblue fantasy) +tenjouin asuka +may (gundam build divers re:rise) +kitagawa yuusuke +gretel (granblue fantasy) +shinigami (rain code) +kallen kaslana +mysterious heroine x alter (first ascension) (fate) +ripper (girls' frontline) +star sapphire +tenryuu kai ni (kancolle) +konno junko +monk (ragnarok online) +wanderer (genshin impact) +suzunoki rin +cinnamiku +machita chima +starmie +brionne +yuuki makoto +hitachi mako +ness (mother 2) +suzumi tamao +candy (smile precure!) +kallen stadtfeld +ceres fauna +heero yuy +giorno giovanna +feh (fire emblem heroes) +kokoro (doa) +kakine teitoku +haibara ai +hiita (yu-gi-oh!) +pidove +korwa +siberian tiger (kemono friends) +minneapolis (azur lane) +maria robotnik +gojou wakana +tartaglia (genshin impact) +aina (mao lian) +suzuki hina +isobe noriko +hermes (ff14) +edmond dantes (fate) +fine motion (umamusume) +eureka (eureka seven) +zaku ii +hayasaka akira +orange pekoe (girls und panzer) +meer campbell +katsuragi keima +shenhe (genshin impact) +amakura mio +jack-o' valentine +milcery +usada hikaru +zeta gundam (mobile suit) +ami (orenchi no maidosan) +clemont (pokemon) +myoukou kai ni (kancolle) +sudowoodo +renne (eiyuu densetsu) +sailor v +martina crespi +owari akane +panda (jujutsu kaisen) +kujou hikari +corrin (female) (nohr noble) (fire emblem) +kinoshita shizuka +nito nazuna +mg5 (girls' frontline) +rattata +midnight (boku no hero academia) +aihara enju +superman +cure sparkle +sakurai yuuto (shiromanta) +sharla (xenoblade) +anya melfissa +mizuno ami +uesugi u. kyouko +liliya olenyeva +eren yeager +mia flatpaddy +sylvie (dorei to no seikatsu) +greed (fma) +merlin (fate) +judgement (helltaker) +lei fang +diantha (pokemon) +fubuki (one-punch man) +chain chomp +rathalos +kugisaki nobara +shaw (arknights) +shadow chaser (ragnarok online) +noelle (genshin impact) +furude rika +hijikata toshizou (fate) +soma peries +amatsuka uto +bronzong +coyopotato +manako +calem (pokemon) +oberon (third ascension) (fate) +dejiko +koyama yuzu +astgenne (arknights) +sawashiro miyuki +fu hua (phoenix) +ganyu (genshin impact) +geralt of rivia +yun jin (genshin impact) +takenaka hanbee (oda nobuna no yabou) +reona west +doomguy +chidori kaname +noi (dorohedoro) +swinub +tone kai ni (kancolle) +takagi-san +hashida itaru +xiang yu (fate) +unicorn (long-awaited date) (azur lane) +sekhmet of death +suzumiya haruka +horizon (apex legends) +kagamine len +sumeragi kaguya +hazawa tsugumi +habara (danshi koukousei) +medic (tf2) +percival (fate) +gunpla boy (ishiyumi) +miriam hildegard von gropius +red panda (ex) (kemono friends) +lady maria of the astral clocktower +nemone +kiana kaslana (white comet) +midorikawa hana +ridley +spirit blossom ahri +hazuki watora +astraea (fate) +tsumiki mikan +jellyfish (splatoon) +karen (sister princess) +hoshiguma (ronin huntress) (arknights) +marle (chrono trigger) +rowlet +saya (saya no uta) +itsuka shidou +ch'en (ageless afterglow) (arknights) +akazutsumi momoko +cure twinkle +arancia +adiane +hisaishi kanade +sumomo (blue archive) +lucy maria misora +toudou naoya +ciel (mega man) +lumiere +projekt red (light breeze) (arknights) +ntw-20 (girls' frontline) +ciel (tsukihime) +eria (yu-gi-oh!) +etorofu (kancolle) +scopedog +izayoi aki +matsuno choromatsu +cheelai +snoopy +kuchiku i-kyuu +romani archaman +nun bora (2nd costume) +tea (cafe-chan to break time) +creeper +suzumiya haruhi +edinburgh (azur lane) +lemrina vers envers +igarashi kyoko +captain (honkai impact) +honekoneko (psg) +ciri +komaru (himouto! umaru-chan) +meowy (chainsaw man) +rarity (my little pony) +tokoro megumi +doppel (monster musume) +igrene (fire emblem) +elincia ridell crimea +angewomon +omastar +ichihara yuuko +elysia (herrscher of human:ego) (honkai impact) +mori calliope (new year) +tirpitz (warship girls r) +jack atlas +chieru (princess connect!) +piyomon +plusle +garie tuman +aardwolf (kemono friends) +charlotte corday (third ascension) (fate) +hoopa (confined) +chiron (fate) +scholar (final fantasy) +ene (kagerou project) +daidouji kira +garterbelt (psg) +sakimiya iruka +wednesday addams +tepig +hirose koichi +wicke (pokemon) +kinako (40hara) +nobi nobita +minami mirei +artoria pendragon (alter swimsuit rider) (second ascension) (fate) +closure (arknights) +kiana kaslana (divine prayer) +feli (puyopuyo) +ichinose shiki +iz (asteroid ill) +kano shuuya +great grey wolf sif +charlotte dunois +muffet +fie claussell +koshimizu ami +mutsuki (azur lane) +marley (pokemon) +lyra (pokemon) +jean bart (azur lane) +ryunosuke naruhodo +shuten douji (fate) +james moriarty (ruler) (fate) +lilimon +suzuka gozen (santa) (fate) +domyoji karin +miteiru (shirakami fubuki) +miori celesta +suzuki masaru +hosshiwa +alina gray +miss cloud +esdeath +feitan portor +shishigami bang +irisu kyouko +ladybug (character) +imaizumi kagerou +reco +martha (swimsuit ruler) (third ascension) (fate) +levantine +shiva (final fantasy) +minette (skullgirls) +looker (pokemon) +tohsaka aoi +getou suguru +zabel zarock +shiratama kitsune +luozhu (the legend of luoxiaohei) +m37 (girls' frontline) +prunce (precure) +fubuki kai ni (kancolle) +asahina mitsuru +agent 3 (splatoon 3) +amajiki tamaki +klefki +classic revenant +capybara-san +cammy white +mysterious heroine x alter (third ascension) (fate) +dan heng (honkai: star rail) +sanger zonvolt +kayn (league of legends) +hoppip +antonio lopez +miura azusa +karyl (new year) (princess connect!) +shisui kiki (1st costume) +plume (arknights) +roto (dq3) +kuroyukihime +lee (punishing: gray raven) +marin (zelda) +pesci +karasawa toshiyuki +dedenne +shaymin +piplup +yuuhi (cookie) +hinaichigo +crazy diamond +iowa (kancolle) +hida ema +kousaka honoka +lapis lazuli (houseki no kuni) +furret +arlecchino (genshin impact) +taihou (seaside daydreams) (azur lane) +isolated island oni +kirahoshi ciel +karen (pokemon) +artoria caster (second ascension) (fate) +servant (danganronpa) +mokona +elsa (frozen) +mk48 (girls' frontline) +hoozuki (hoozuki no reitetsu) +roy (fire emblem) +sothe (fire emblem) +portland (azur lane) +fujiwara hazuki +izayoi sakuya +saitou ena +aipom +mr. mime +shoukaku (azur lane) +enoshima junko +kagura rei +super orion (fate) +eucliwood hellscythe +machoke +ump45 (agent lop rabbit) (girls' frontline) +aki minoriko +mettaton ex +gaap (umineko) +dhole (kemono friends) +pine (bombergirl) +charmeleon +squirtle +tejina senpai +vf-1 +southern italy (hetalia) +fairimon +catherine (fire emblem) +hinomoto hikari +guido mista +strike gundam +bb (swimsuit mooncancer) (second ascension) (fate) +killer t (hataraku saibou) +nyon (cookie) +mioda ibuki +kamishiro rize +bambinata (punishing: gray raven) +naruse ibara +hakubi washuu +sora (arknights) +misawa maho +jirou tachi +hiker (pokemon) +asriel dreemurr +fukawa toko +vivy +godsworn alexiel +saori (blue archive) +takanashi rikka +relena peacecraft +nagatoro hayase +brid (nikke) +destroyer princess +natsuiro matsuri +phantump +hi-nu gundam +kako (kancolle) +soriz +frenda seivelun +wargreymon +white lion (kemono friends) +yamamura sadako +wing gundam zero custom +linhardt von hevring +artoria pendragon (swimsuit ruler) (fate) +pheromosa +kamen rider agito +dinergate (girls' frontline) +tsubaki (blue archive) +nekomiya hinata +guile +uruha rushia (1st costume) +kawashiro nitori +squall leonhart +satan (puyopuyo) +sovetskaya rossiya (azur lane) +marivel armitage +ookami mio (5th costume) +bridget (guilty gear) +team spica's trainer +marta lualdi +futaba riho +kadoc zemlupus +fox mccloud +cecile croomy +akebono kai ni (kancolle) +samson (skullgirls) +amemiya taiyou +hoshino yuumi +akai ringo (ookami-san) +blaziken +totoro +kisaragi (kancolle) +aizawa ema +tailred +akizuki (kancolle) +star butterfly +gran (granblue fantasy) +aurora (arknights) +lao jiu +mudrock (arknights) +oosanshouuo-san +alphinaud leveilleur +vulcan (arknights) +sukuna shinmyoumaru +zuikaku (kancolle) +shirayuki ren +bianca (pokemon heroes) +jill valentine +sunny milk +dragonair +marie (persona 4) +vincent valentine +cure muse (yellow) +choukai (kancolle) +mutou yuugi +rick (kirby) +xurkitree +sorano aoi +welt yang +dewgong +xuangzang sanzang (fate) +kasumi (doa) +kunisaki yukito +nanashi (ganesagi) +kizuna akari +ebenholz (arknights) +magical mirai miku (2016) +zara due (kancolle) +huang lingyin +arisawa tatsuki +himeragi yukina +may (pokemon) +isshiki momo +buddha +yuigahama yui's mother +ceruledge +diddy kong +kojo anna +alastor (shakugan no shana) +tokai teio (umamusume) +crobat +naegi komaru +alfred (fire emblem) +shitodo kuroji +rei (cookie) +saegusa ibara +nonohara yuzuko +hanasaki miyabi +i-no +aak (arknights) +sakamoto mio +shun (blue archive) +tsukimura suzuka +aris (maid) (blue archive) +hatsukaze (kancolle) +azuki azusa +rei (princess connect!) +venomoth +amano hina (tenki no ko) +banica conchita +esia mariveninne +cure milky +eugen (granblue fantasy) +horikita suzune +hilda ware +yura (kancolle) +sasaki (suzumiya haruhi) +p-chan (needy girl overdose) +akemi homura (black dress) +fuuka (new year) (blue archive) +jun (princess connect!) +shima rin +sophie (toast of the town) (tales) +tsana (lansane) +nina (breath of fire iv) +mach caliber +musume (yuunama) +gastrodon +uozumi kurumi +kos-mos re: +meruru (oreimo) +shiori (princess connect!) +black general +meltryllis (fate) +hyuuga saki +kamikita komari +larvesta +severa (fire emblem) +mk23 (girls' frontline) +hibiki ryouga +ouro kronii (1st costume) +elle mel martha +sophie (tales) +elizabeth liones +sora (blue archive) +wii fit trainer +akikawa yayoi (umamusume) +tashkent (the bound cruiser) (azur lane) +harpy (closers) +professor nemo (fate) +shirogane noel (dirndl) +chung seiker +annelotte (queen's blade) +chitose sana +nekomusume (gegege no kitarou 6) +roxy migurdia +leopard (yatterman) +kitazawa shiho +tanaka ichi +torii eriko +manticore (under a veil) (arknights) +beerus +nijou noriko +xenovia quarta +makihatayama hana +silica (sao-alo) +ram (neptunia) +huyan zhuo (fate) +blacksmith (ragnarok online) +hirasawa yui +green heart (neptunia) +takanashi kiara (1st costume) +raiden mei (valkyrie bladestrike) +cure selene +papi (monster musume) +dark miku (project voltage) +jahy +ayra (fire emblem) +misdreavus +amano keita +marianne von edmund +hanadera nodoka +alolan vulpix +hakodate omiko +t-head admiral +vermeil (arknights) +lilith (evangelion) +fuwa kokone +maikaze (kancolle) +minamoto chizuru +yuri (dirty pair) +hiyajou maho +genetic (ragnarok online) +matsukai mao +tomioka giyuu +elira pendora (3rd costume) +rie petoriyacowa +sha wujing +iori utahime +ash (titanfall 2) +lethe (fire emblem) +charles babbage (fate) +akuno hideo +lightning farron +houlen yabusame +makinami (kancolle) +greninja +gabumon +black-tailed prairie dog (kemono friends) +han juri +nero claudius (olympian bloomers) (fate) +hazuki nagisa +narmaya (summer) (granblue fantasy) +cure happy +yukimura chizuru +toutetsu yuuma +kuzuki souichirou +nikki (miracle nikki) +kuma (kancolle) +edmond honda +artoria caster (fate) +tokiko (touhou) +nekomata okayu (4th costume) +jitomi monoe +perrault (last origin) +toki (hokuto no ken) +kaga (battleship) (azur lane) +ryuzaki kaoru +daiba canon +mc liz +shiraki meiko +ruin guard (genshin impact) +indomitable (azur lane) +chitose yuma +kou (granblue fantasy) +ning hai (azur lane) +farah oersted +ema skye +ryouko (tenchi muyou!) +oda nobunaga (swimsuit berserker) (fate) +roro (gunvolt) +queen draco (second ascension) (fate) +dogoo +electrode (pokemon) +ducklett +rotom (normal) +jane t. godfrey +akiyama rinko +abigail williams (second ascension) (fate) +ji-yoon (jourd4n) +female commander (azur lane) +baobhan sith (swimsuit pretender) (fate) +kawakaze (kancolle) +edea lee +igarashi rika +momo (breath of fire) +kurobe natsumi (shiromanta) +imai midori +kimikage yui +light cruiser oni +kouenji sayuka +guts (kill la kill) +sheryl nome +satomi touka +daiichi ruby (umamusume) +acolyte (ragnarok online) +m200 (girls' frontline) +wilma bishop +cure fontaine +minakami mai +seele (alter ego) +bronya zaychik (herrscher of reason) +orimura chifuyu +mari (faraway) (omori) +kurokami medaka +atago (stunning speedster) (azur lane) +cure white +nidai nekomaru +yui tsuruno +tir mcdohl +podenco (arknights) +kofune ushio +princess ruto +kaiba seto +elsword (character) +ashe (overwatch) +violette +hourai doll +hapu (pokemon) +sangonomiya kokomi +kamijou kyousuke +brooklyn (kancolle) +selvaria bles +vegeta +cure finale +lissa (fire emblem) +ars almal (1st costume) +doujima nanako +shimakaze (kancolle) +mori calliope +kaminari denki +takanami (kancolle) +hamuzou +poring +miltank +ribombee +fartooth (arknights) +kagura nana +nasu no yoichi +super sonic +amasawa yuuko +soul evans +sage (granblue fantasy) +tien (granblue fantasy) +manjoume jun +momose kurumi +misaka shiori +honda mio +konata (kankin jk) +kamio misuzu +wakasa yuuri +heavy (tf2) +soraka (league of legends) +draco centauros +sussurro (arknights) +junkrat (overwatch) +limone (simoun) +lie ren +kearsarge (azur lane) +miyaura sanshio +kagamihara nadeshiko +sonoda umi +sara (touhou) +camilla (summer) (fire emblem) +vita (nanoha) +shirakami fubuki (1st costume) +orphen +takiya makoto +prince of wales (azur lane) +aina rive +vista-tan +hoshino (swimsuit) (blue archive) +cure peace +honebami toushirou +shuckle +maruruk +space ishtar (second ascension) (fate) +ariados +kaolla su +urd (aa megami-sama) +kariya masaki +swadloon +kinagase tsumugu +philia (sao) +koko (kamitsubaki studio) +taigei (kancolle) +dorothy (nikke) +shangri-la (azur lane) +lum +inteleon +kusakabe wakaba +hatenna +avatar (mabinogi) +dori (genshin impact) +hagakure yasuhiro +sakura chiyono o (umamusume) +painwheel (skullgirls) +nuko (shoujo shuumatsu ryokou) +miss fortune (league of legends) +elira pendora (2nd costume) +norman (yakusoku no neverland) +midare toushirou +caren hortensia (amor caren) +foo fighters (jojo) +jungle crow (kemono friends) +mirai (senran kagura) +cecily cambell +orion (bear) (fate) +hikage (senran kagura) +ariake (kancolle) +jellicent (male) +luthica preventer +mayano top gun (sunlight bouquet) (umamusume) +inubouzaki fuu +zenigata kouichi +watashi (jintai) +hector (fate) +hakui koyori +willard h. wright +trish una +cure chocolat +yusa emi +nakata sae +shyrei faolan +vulcanus (disgaea 4) +nakigitsune's fox +croix meridies +pidgeotto +yuudachi (kancolle) +vritra (fate) +whimsicott +asada shino +viy (fate) +leafeon +nonohara akane +mamoswine +mash kyrielight (swimsuit of perpetual summer ver.02) +yuuki mizuho +koyoi (iroha (iroha matsurika)) +rinwell (tales) +eiscue +karasuma chitose (girlish number) +hatsuseno alpha +mordred (fate) +emboar +big boss +chocola (nekopara) +debi tarou +kaku seiga +hestia (danmachi) +matsumoto rangiku +degenbrecher (arknights) +xerneas +anna williams +iijima renka +shiokko (murasaki shion) +irys (irys 2.0) (hololive) +yonaga angie +layla prismriver +byleth (fire emblem) +yamcha +togame +togata mirio +yaoyao (genshin impact) +hibari (senran kagura) +puff (go! princess precure) +litchi faye ling +magical mirai miku (2020 summer) +bowser +shylily +aegir (housamo) +slaine troyard +nakano ichika +lycoris princess +kratos +smol gura +tita russell +quaxly +trainer (idolmaster) +naoki miki +thanatos (persona) +kuwayama chiyuki +hibiscus (arknights) +nakiri alice +shion (tensei shitara slime datta ken) +mabosstiff +yuki miku (2019) +akashi (live a hero) +loki (fire emblem) +hanabatake chaika +zagreus (hades) +bronya zaychik (black nucleus) +oozora subaru (2nd costume) +makabe mizuki +daiwa scarlet (umamusume) +elysia (miss pink elf) (honkai impact) +mitsukasa ayase +dreepy +nah (fire emblem) +nina wang +rebecca (fire emblem) +kiana kaslana (valkyrie ranger) +meltryllis (swimsuit lancer) (second ascension) (fate) +dudley +minion 3 (zannen onna-kanbu black general-san) +haku (sen to chihiro no kamikakushi) +jeanne d'arc alter (fate) +tharja (fire emblem) +nekomura iroha +gii +mejiro ardan (umamusume) +uzuki (cookie) +hanayagi kaoruko +makoto nanaya +yoshioka saki +urushibara ruka +virgilia (umineko) +kihara tsumugu +shindou takuto +isis (p&d) +luetzow (azur lane) +ookami mio +murata himeko +shokatsuryou +komori kiri +yoo joonghyuk +sakaki (azumanga daioh) +gooey (kirby) +twilight (spy x family) +nishizumi maho +tosa (hometown zest) (azur lane) +hikasa tomoshika +vivi (ac8231) +kan'u unchou +lanturn +shingyoku (female) +hayasui (kancolle) +hubert ozwell +batou (gits) +yagokoro eirin +takafuji kako +hanna england +lucio (overwatch) +madeline (woofycakes) +shishio chris +mirko +rosado (fire emblem) +miquella (elden ring) +miyamoto musashi (swimsuit berserker) (second ascension) (fate) +luffyko +saitou kaede (yama no susume) +reinforce zwei +balthier +kirisu mafuyu +tia-chan +takitsubo rikou +shinomiya runa +eria the water charmer +koseki reina +prinz eugen (cordial cornflower) (azur lane) +zentreya (cyborg) +amano megumi +enlightened byleth (male) +ancient princess menace +vestia zeta +hirofumi (orenchi no maidosan) +maximilian jenius +lady (devil may cry) +terumi mei +tasmanian devil (kemono friends) +nanami mami +lysithea von ordelia +yashiro kasumi +houshou hanon +beatrix (ff9) +chinatsu (hot spring) (blue archive) +kawashima sapphire +suzuya juuzou +kamioka senri +takarada rikka +waddle doo +dancer (final fantasy) +lumine (genshin impact) +hasegawa langa +mirai akari +hinata yukari +menou kaname +edward wong hau pepelu tivrusky iv +almeida (granblue fantasy) +busujima saeko +bb (swimsuit mooncancer) (first ascension) (fate) +dobrynya nikitich (fate) +fairy (jintai) +konparu nozomi +yura kai ni (kancolle) +basil (headspace) (omori) +hassu +hinoka (fire emblem) +dog (duck hunt) +onion knight +jessica (arknights) +live twin lil-la +sabin rene figaro +kazakiri hyouka +mori yuki +ten'i (ikkitousen) +mizuho (kancolle) +harada minoru +takasaki yuu +onishima homare +raikou +nekomata okayu (1st costume) +kanda mizuki +mr. koiwai +prinz rupprecht (azur lane) +miya utsutsu +nosa kouko +ramona flowers +naruse maria +kiri (sub-res) +juliet starling +lucina (spring) (fire emblem) +angelia (girls' frontline) +yurigasaki lulu +tokai teio (beyond the horizon) (umamusume) +sophitia alexandra +wild geese +gibraltar (apex legends) +buront +yin (darker than black) +julia (fire emblem) +elise (fire emblem) +tsukishima maira +omega alpha +venom (marvel) +seiran (touhou) +suzukaze (kancolle) +camieux +hoso-inu +big man (splatoon) +lycanroc (dusk) +yu (bosshi) +miyanaga saki +clefairy +takatsuki yayoi +nekomata okayu +nakabeni yua +navi +fujiwara no mokou +saren (princess connect!) +shihou matsuri +tanba akari +kuma kai ni (kancolle) +tokugawa matsuri +st ar-15 (mod3) (girls' frontline) +date kanta +zafira +altair ibn la-ahad +kuroo tetsurou +kenmochi touya +shishiro botan (4th costume) +kogami akira +whitney (pokemon) +kunkun +saotome ranma +cap'n cuttlefish +yozora mel (2nd costume) +sebastian michaelis +jasmine (pokemon) +evelynn (league of legends) +himejima gyoumei +yagami hikari +fubuki atsuya +meimei (p&d) +spicy nun (diva) +cure cosmo +eldegoss +yuri (doki doki literature club) +kasumi (princess connect!) +fukae (kancolle) +usada pekora (4th costume) +pandemonica (helltaker) +l-elf +miyashita ai +kinoshita hideyoshi +elisabeth blanctorche +octotrooper +surtr (arknights) +red (among us) +dromarch (xenoblade) +cynthia (fire emblem) +mieu (tales) +princess tutu (character) +yuki miku (2020) +jaffar (fire emblem) +toyosatomimi no miko +oozora subaru (hololive summer 2019) +kome-kome (precure) +nakamura yuri +kirschtaria wodime +golduck +lamia hygieia +impostor (among us) +suzuno fuusuke +i-26 (azur lane) +tiffy (nottytiffy) +reines el-melloi archisorte +shiraishi yoshitake +galleon (granblue fantasy) +elesa (pokemon) +yak (kemono friends) +sonoda chiyoko +kirino (blue archive) +sairenji haruna +yasaka mahiro +aiz wallenstein +nanami kento +boudica (third ascension) (fate) +smol ame +shikabane itsuka +yumi sayaka +originium slug (arknights) +vivlos (umamusume) +ivan karelin +mina (blue archive) +tsurumaru tsuyoshi (umamusume) +setsuna f. seiei +panty (psg) +jean-jacques leroy +nezumi (no.6) +mudrock (elite ii) (arknights) +yuki miku (2018) +hakamada hinata +gilles de rais (caster) (fate) +koizumi hanayo +shaula (re:zero) +miyamae shiho (jack dempa) +kushima kamome +akitsu maru (kancolle) +azusa (cookie) +himmel (sousou no frieren) +kyouyama anna +kamigishi akari +midorikawa nao +mankanshoku matarou +theresa apocalypse (luna kindred) +nikki (swapnote) +makaino ririmu +tachibana hibiki (symphogear) +kodiak bear (kemono friends) +zac (league of legends) +tsukamoto tenma +ken masters +grea (shingeki no bahamut) +mito ikumi +tatsumaki +kel (headspace) (omori) +milky rose +rita rossweisse (artemis) +craig tucker +aoi kyosuke +kagamihara sakura +washington (kancolle) +zeta (granblue fantasy) +muginami +kaga sumire +ashe ubert +alice zuberg +nise maou kanizeru +ump9 (mod3) (girls' frontline) +ace trainer (pokemon) +kyouno madoka +fuuma kotarou (fate) +aimaina +hyuuga (aoki hagane no arpeggio) +kamishirasawa keine (hakutaku) +manaka laala (young) +lafter frankland +arjuna (fate) +sucy manbavaran +iori junpei +shikimori (kawaii dake ja nai) +hu tao (genshin impact) +poppy (league of legends) +haruhara haruko +kurukuru (sekai seifuku) +nice nature (umamusume) +sophie pulone +hachiouji naoto +i-201 (kancolle) +cosplay pikachu +jenny (pokemon) +inui sana +sofia valmer +nuernberg (azur lane) +daitou (kancolle) +lisa (ponyo) +yatagarasu (tsukumo sana) +satou jun +demon pillar (fate) +mad hatter (alice in wonderland) +velvet crowe +leif (fire emblem) +ewen egeburg +nohara rin +mizuno youko +yang guifei (fate) +tone (kancolle) +kurata tome +meowstic (male) +frieren +tatsuta kai ni (kancolle) +ciel sacred +dusk (everything is a miracle) (arknights) +genos +melusine (swimsuit ruler) (first ascension) (fate) +hamanami (kancolle) +childhood friend-chan (ramchi) +fukasaku aoi +kagura mea +kawakami princess (umamusume) +umeki otoha +hirume of heavenly incense +okada akane +rouge the bat +usuta sumire +hildr (fate) +tsurumaki kokoro +charizard +drifblim +maestrale (kancolle) +touwa erio +roberta (arknights) +kinu (kancolle) +nitocris (fate) +makinohara shouko +rudeus greyrat +aquila yuna +kasuga ichiban +akatsuki (kancolle) +queen aldra +matsuda yasuke +jingburger +shez (female) (fire emblem) +hubert von vestra +master asia +helena (kancolle) +sonshoukou +shimada minami +kojima emi +lucas lee +aggron +taihou (muse) (azur lane) +mint adenade +roy mustang +bardiche (axe form) +athena cykes +shamisen (suzumiya haruhi) +kotori (cheer squad) (blue archive) +isuzu hana +yoshioka chie +tome of the night sky +kroos the keen glint (arknights) +japan (hetalia) +sakaguchi karina +ohno kanako +admiral hipper (azur lane) +medusa (rider) (fate) +utage (summer flowers) (arknights) +lappland (arknights) +hibiscus the purifier (arknights) +aldra (queen's blade) +zorome (darling in the franxx) +pearl (splatoon) +sims (azur lane) +shiranui flare (3rd costume) +rukawa kaede +pinecone (arknights) +sonia nevermind +murosaki miyo +sigurd (fire emblem) +toudou erena +shikinami (kancolle) +sai (naruto) +vash the stampede +gold city (umamusume) +shellder +gundam aerial +ronove (umineko) +kagaya you +akigumo (kancolle) +princess (sekaiju) +utaha (blue archive) +hikawa sayo +fujibayashi ryou +paula (mother 2) +kokoro (hakui koyori) +noss (rariatto) +lupusregina beta +ibaraki douji (swimsuit lancer) (first ascension) (fate) +pinoko +momosuzu nene (2nd costume) +natsume takashi +salazzle +yuuki rito +matou sakura (fate/extra) +arona's sensei doodle (blue archive) +cramorant +yang nari +savage (arknights) +buizel +yomotsu hisami +reset kalar +sylvain jose gautier +hood (azur lane) +gastrodon (west) +zara (kancolle) +shizuko (blue archive) +kusakabe yuuki (to heart 2) +mikasa ackerman +lan hikari (mega man) +yashiro kizuku +chando (ado) +sawada manami +yumeoi kakeru +jeanne d'arc alter santa lily (fate) +lavinia whateley (fate) +admiral suwabe +inui toko +tohno shiki +clownpiece +hori yuko +vikala (blooming summer wallflower) (granblue fantasy) +julia (idolmaster) +jigglypuff +haruna (aoki hagane no arpeggio) +sakamata chloe +little cocon (umamusume) +maki oze +silence suzuka (emerald on the waves) (umamusume) +hoshizora ikuyo +regina (dokidoki! precure) +higuchi kaede (1st costume) +norimaki arale +jacket girl (dipp) +lindy harlaown +mizuki seira +electabuzz +gurren-lagann +gundou mirei +fudou yukimitsu +hokko tarumae (umamusume) +mashiro (nijisanji) +anna (ikeuchi tanuma) +ogiso setsuna +azul ashengrotto +hoshikawa sara +pyrrha nikos +kamijou touma +frankenstein's monster +deerling +yami yuugi +sword maiden +magical mirai miku (2018) +artemis (sailor moon) +tachibana jun'ichi +sagara sousuke +ushiromiya kinzou +cure macaron +pachirisu +adelbert steiner +audino +ootori chihaya +ishida uryuu +azhdaha (genshin impact) +tamada tamaki +sinder (vtuber) +kurotsuchi nemu +amou kanade +kaga ai +matikanefukukitaru (umamusume) +naoi ayato +nakiri ayame (1st costume) +zz gundam +taikogane sadamune +piers nivans +iws 2000 (girls' frontline) +illyasviel von einzbern (swimsuit archer) +sakura (fire emblem) +kondou taeko +mizuki yukikaze +illumi zoldyck +metroid (creature) +dark dream +anthuria +medic (sekaiju) +black-haired demon girl (shimmer) +lwmmg (girls' frontline) +kamui (gintama) +ram (re:zero) +ahagon umiko +grim reaper +angel girl (shimmer) +tanuki (ame to kimi to) +tsukino usagi +tachibana rui +mogami kai ni (kancolle) +wooper +nakahara chuuya +miyagi ryouta +luke pearce (tears of themis) +yamada yui +shinkai kanata +koyomi (shironeko project) +mima (touhou) +futagawa fumi +marceline abadeer +mikazuki yozora +pururut +dif (difman) +remilia scarlet +takozonesu +fulgur ovid +kan'u (koihime musou) +beatrice (umineko) +minamoto no raikou (fate) +lunalu (granblue fantasy) +doctor (arknights) +nefertari vivi +dusk (arknights) +matsumoto sarina +gotoh futari +blue angel +new jersey (exhilarating steps!) (azur lane) +venom (guilty gear) +liza (pokemon) +kal'tsit (arknights) +shikanoin heizou +toyohara etsuko +punishing bird +sessyoin kiara (lily) +furen e lustario +tibetan fox (kemono friends) +halara nightmare +luca kaneshiro +murasaki shion (1st costume) +toono minagi +fukada ichika +satou kazuma +marina (splatoon) +cornet espoir +hansel (granblue fantasy) +strider hiryuu +takeya yuki +illuso +impa +nakano azusa +hasekura rei +hope estheim +gible +dp-12 (girls' frontline) +amane kanata (1st costume) +unlovely (happinesscharge precure!) +g36c (girls' frontline) +kagiyama hina +crimson avenger (elsword) +ishigami yuu +komi shouko +nezha (the legend of luoxiaohei) +usami sumireko +koguma (super cub) +tuka luna marceau +kaga (kancolle) +niles (fire emblem) +cure lovely +otako (galko) +raito-kun (uenomigi) +kokkoro (ceremonial) (princess connect!) +koakuma +dido (anxious bisque doll) (azur lane) +purah +lucifer (shingeki no bahamut) +gotenks +freddy fazbear +okapi (kemono friends) +marco bodt +yamanbagiri kunihiro +edward teach (fate) +matsuda arisa +priscilla (fire emblem) +iris (takunomi) +funny valentine +nisshin (kancolle) +princess peach +akira ferrari +ursula hartmann +rina atherina +kaedehara kazuha +gine +takahara ayumi +eternity larva +kurumi (kantoku) +todoroki shouto +female tourist c (arknights) +mordred (fate/apocrypha) +delthea (fire emblem) +chongyun (genshin impact) +saotome genma (panda) +tsuruya +kureiji ollie +chong yue (arknights) +whismur +komano aunn +tenma tsukasa +haruka (senran kagura) +miyamoto frederica +suou mikoto (school rumble) +roma (kancolle) +sola-ui nuada-re sophia-ri +pkp (girls' frontline) +gabi braun +yoyohara tsukasa +shiratsuyu kai ni (kancolle) +hanzo (overwatch) +fami (chainsaw man) +saotome genma +leo/need miku +jigen (cookie) +chito (shoujo shuumatsu ryokou) +erinys (fire emblem) +akiyama mizuki +vill-v +takebe saori +goofy +hummy (suite precure) +aoba kozue +kuroki rei +ten'inkou korin +nero claudius (bride) (fate) +bond (spy x family) +saiga-12 (girls' frontline) +yuma (ebisujima misato) +black-headed ibis (kemono friends) +ines fujin (umamusume) +shiranui flare +all might +rengoku kyoujurou +sasha (haguhagu) +barbara gordon +ise (kancolle) +yune (ikoku meiro no croisee) +artoria pendragon (alter swimsuit rider) (first ascension) (fate) +xelloss +dancer (ragnarok online) +caiman (dorohedoro) +dratini +kson +akatsuki miho +warrior (final fantasy) +mifune miyu +yagami hayate +sakura miko (1st costume) +ghost miku (project voltage) +commander (girls' frontline) (madcore) +lee hoon +enkidu (fate) +lunch (bad) (dragon ball) +eva beatrice +minamino kanade +asuna (blue archive) +unzan +shiteyan'yo +lucia: crimson abyss (punishing: gray raven) +pa-san +corsola +mikleo (tales) +hosokawa kanako +pidge gunderson +itou nobue +konno makoto +aoki lapis +atom (tetsuwan atom) +okita souji alter (fate) +rita rossweisse +sakimori toji +oono aya +kevin kaslana +lapis (fire emblem) +illustrious (azur lane) +canada (hetalia) +shirakiin ririchiyo +yorra villeneuve +tokiwa midori +chiyo (ane naru mono) +hare (blue archive) +pt imp group +jeanne d'arc (summer) (granblue fantasy) +rockruff +yae kasumi +zoya petrovna vecheslova +qingque (honkai: star rail) +hanazawa kana +note-chan +denji (chainsaw man) +yae sakura (gyakushinn miko) +tora (xenoblade 2) +protagonist (smtv) +kido tsubomi +kakyouin chieri +jeanne d'arc (girl from orleans) (fate) +sendou erika +luna: laurel (punishing: gray raven) +red pikmin +nekomata okayu (3rd costume) +usa-chan (idolmaster) +shikibe ayaka +alexander (fate) +tosen jordan (umamusume) +micchan (ohisashiburi) +saniwa (touken ranbu) +sigurd (fate) +scraggy +filia ul copt +z46 (azur lane) +takamiya rion +ai-chan (tawawa) +mitake ran +ayame (gundam build divers) +shez (fire emblem) +silence (arknights) +yukico-tan +ryuujou (kancolle) +siyudi (cookie) +ijuuin hokuto +hato kenjirou +kasumi (blue archive) +nikkari aoe +todo yurika +atlanta (kancolle) +scarlet (nikke) +iris (pokemon) +nara shikamaru +tony stark +barghest (swimsuit archer) (fate) +surcouf (azur lane) +tikoh +roland (project moon) +dubwool +satou mari +barry (pokemon) +yukihana lamy (3rd costume) +daidou ayumu +nacchan (ohisashiburi) +kurosu aroma +knight princess annelotte +omaru polka +morgana (persona 5) +lelei la lalena +takanashi hikari +panther chameleon (kemono friends) +piyotan (girls und panzer) +rachel gardner +patamon +saginomiya isumi +utage (arknights) +noda (angel beats!) +northern ocean princess +elisia valfelto +roboco-san +koto (kyousougiga) +matsumae ohana +johan andersen +spiritomb +shirogane kei +star guardian lux +musashi kai ni (kancolle) +jeff andonuts +murasame (kancolle) +tiamat (fate) +western parotia (kemono friends) +angel of light nanael +machi komacine +koyuki (bunny) (blue archive) +scylla (azur lane) +shamir nevrand +soap (modern warfare 2) +maihama ayumu +kuro the divine heir +segawa onpu +yuigahama yui +nowareno (higashi shino) +neru (blue archive) +kon (bleach) +lahmu (fate) +konohana lucia +park moo-hyun +forte stollen +amemiya nazuna (1st costume) +monoko +shizuru viola +ne-class heavy cruiser +saihara shuichi +yokune ruko +cure pine +utsumi sakura +arsalan (housamo) +elena (street fighter) +shana +ralts +soda kazuichi +rumi (blue archive) +march 7th (honkai: star rail) +gilgamesh (fate) +kikuzuki (kancolle) +misha arsellec lune +kiryu coco (3rd costume) +takigawa miu +hop (pokemon) +renown (azur lane) +meme (me!me!me!) +shijou mitsue +skeledirge +celia kumani entory +sela (23) +genji (overwatch) +utsumi erice (swimsuit avenger) +kanzaki kaori +awakened miki +myrrh (fire emblem) +nanashi mumei (new year) +eyjafjalla (arknights) +miyao miya +titi-chan (nezumi inu) +hamada kiyo +jessie (pokemon) +nina (thief) (fire emblem) +fujimiya kaori +theresa apocalypse (valkyrie pledge) +amano tora +lopunny +talonflame +moona hoshinova +haruka (blue archive) +avatar (pso2) +bai (granblue fantasy) +luna child +menat +panpour +laby (elsword) +yukimin (yukihana lamy) +suzumiya haruhi (young) +kakita (92m) +leo (mafuyu) +kita ikuyo +oriana thomason +komeiji koishi +tamaki (princess connect!) +oberon (fate) +skiploom +clara (honkai: star rail) +dandelion (girls' frontline) +dhalsim +koyanskaya (fate) +hoto mocha +ferry (granblue fantasy) +cloud retainer (genshin impact) +nishimiya shouko +ephraim (fire emblem) +shiomi kotone +kuriboh +bianka durandal ataegina +floette +miriam (bloodstained) +fubuki shirou +chrome dokuro +bald eagle (kemono friends) +eurasian eagle owl (kemono friends) +ekans +nishimori yusa +chiester556 +nanao yuriko +ump40 (girls' frontline) +apple (reverse:1999) +sakurai yumeko +adepta sororitas +shirley fenette +tamamo no mae (fate/extra) +kamiya kaoru +akari (blue archive) +secre swallowtail +asakura nemu +homeko +marth (fire emblem awakening) +belle (disney) +amagi rinne +yuuri (shoujo shuumatsu ryokou) +riesz +modeus (helltaker) +kiriha (tsugumomo) +yamashiro (kancolle) +cheshire cat (alice in wonderland) +ononoki yotsugi +minato mio +ushimi ichigo +lorelei (pokemon) +mismagius +kamen rider double +haniyasushin keiki +strea (sao) +cubone +cupitan (granblue fantasy) +yuuki nao +kurochijo +monoe +hassan of serenity (fate) +riku (kingdom hearts) +ron weasley +seta kaoru +takakura himari +ushiromiya kyrie +ishtar (fire emblem) +hakata toushirou +inaba meguru +hiwatashi nazuna +hayanami (kancolle) +shino hajime +chevalier d'eon (maid knight) (fate) +katou umi +aoba kokona +honshou aru +olive laurentia +evil twin lil-la +claude von riegan +blooper (mario) +lucoa (maidragon) +peter strasser (azur lane) +sakomizu haruka +makina nakajima +matsuno jyushimatsu +wario +hawlucha +kazama iroha (1st costume) +oda kippoushi (fate) +ars almal +matou zouken +space ishtar (first ascension) (fate) +marron (dragon ball) +nakahara misaki +musashi (azur lane) +kaho (blue archive) +toadette +hong meiling +sawatari makoto +rui (dream c club) +wriggle nightbug +moe (swimsuit) (blue archive) +orihara izaya +sten mkii (girls' frontline) +ootori tatta +kitasan black (umamusume) +tamamo cat (second ascension) (fate) +wilderness bandit risty +kanna kamui +kintoki (sakura miko) +ayase honoka +poppi qtpi (xenoblade) +mature (kof) +pumpkaboo +allenby beardsley +shisui kiki +minakata hizuru +sneasler +sakamata chloe (jirai kei) +victorious (azur lane) +artoria pendragon (lancer alter) (fate) +riolu +inubashiri momiji (wolf) +inne sulistya robin +finana ryugu +aijou karen +tibbers +tachibana shiro (idolmaster) +koga (pokemon) +felt (re:zero) +kurusu kanako +umikaze (kancolle) +saren (real) (princess connect!) +kashuu kiyomitsu +admiral (warship girls r) +sakurakouji luna +puchiko +chi-chi (dragon ball) +magical mirai miku (2021) +fujimaru ritsuka (male) (decisive battle chaldea uniform) +venus park (umamusume) +sof (blue archive) +blanche (pokemon) +shinobu (ninin ga shinobuden) +beanstalk (gift uncompleted) (arknights) +sorami kanata +lucina (fire emblem) +chijou noko +sol badguy +charlotte pudding +toramaru shou (tiger) +princess serenity +quilava +ellen (touhou) +eldridge (azur lane) +furutani himawari +moomintroll +gangut (kancolle) +g'raha tia +shirogane naoto +yona (akatsuki no yona) +azmaria hendric +yamada hifumi +konomori kanon +u-olga marie +kirigaya suguha +executioner (girls' frontline) +yamasachihiko (housamo) +kokona (blue archive) +kamui gakupo +hs2000 (girls' frontline) +ogin (girls und panzer) +enemy vessel (warship girls r) +choukai (azur lane) +rebecca (one piece) +okuyama saori +kuma (persona 4) +natori (kancolle) +saotome shino (shino to ren) +rayquaza +kizuna ai (a.i. games) +plana (blue archive) +lanz (xenoblade) +thrud (fate) +mettaton +momosuzu nene (old design) +tomari mari +matsubayashi souta +riko (made in abyss) +xinyan (genshin impact) +kokubunji koyori +metallica (majo to hyakkihei) +myucel foalan +mudrock (obsidian) (arknights) +masurao 2 (sekaiju) +kalpas (honkai impact) +yukinoshita haruno +jeanne d'arc (granblue fantasy) +bronya zaychik (herrscher of truth) +fuyumi jun +wakaba hinata +okita j. souji (third ascension) (fate) +theresa apocalypse (lunar vow: crimson love) +angelina (summer flower) (arknights) +tsukimiya ayu +ash (fire emblem) +kawashiro mitori +chiaki kurihara +mega lopunny +kase tomoka +merlin (fate/prototype) (second ascension) +jinno megumi +braixen +noa (blue archive) +bicute bunnies miku +amakura mayu +sukeban (smg) (blue archive) +lycanroc (midnight) +fallenshadow +otabek altin +vaporeon +adeleine +hoshimi junna +silver wolf (honkai: star rail) +matsuno karamatsu +byleth (female) (fire emblem) +yagyuu (senran kagura) +sweden (hetalia) +otonose kanade +kasen kanesada +shoebill (kemono friends) +mito mashiro +isuzu ren +shirouzu mairu +takano miyo +airfield princess +narancia ghirga +puru-see (hoshizuki (seigetsu)) +stiyl magnus +wiccan +shameimaru aya +zen'in maki +uzumaki himawari +panette (fire emblem) +lust (fma) +deborah (dq5) +urabe mikoto +inoue orihime +ikaros +simon (ttgl) +kama (third ascension) (fate) +yuuki yuuna +vei (vtuber) +caitlin (pokemon) +kufei +garfield (character) +mary cochran +kiryuuin aoi +hydreigon +madaraki fran +cure mermaid +slowbro +aqua (konosuba) +siegfried (fate) +ashe (league of legends) +kyurem +hiyori (blue archive) +rolling bubbles +corviknight +yu mei-ren (first ascension) (fate) +katsuragi misato +taokaka +dorothy (princess principal) +yuuno scrya +tsuruta himeko +fujibayashi sheena +harusame (kancolle) +bushidou (sekaiju) +taketatsu ayana +waver velvet +zuifeng tenkai +wang yujia +yasaka kanako +whip (kof) +kureiji ollie (1st costume) +camilla (spring) (fire emblem) +data (mega man) +iwato kasumi +iijima yun +lil-la (yu-gi-oh!) +excadrill +houzouin oniwaka +aphelios +guts (berserk) +irizaki mei +leo (fire emblem) +xiao (genshin impact) +king k. rool +kouhai-chan (douki-chan) +emiya kiritsugu +mai (dragon ball) +sono midoriko +artorias the abysswalker +eas (fresh precure!) +himari (blue archive) +aoba (kancolle) +hoshizora rin +akari (princess connect!) +eris greyrat +littorio (kancolle) +hatsune (summer) (princess connect!) +teemo +lambda-11 +kiryu tsukasa (idolmaster) +makarov (girls' frontline) +aiba asagi +nyanko-sensei +tsuchiya (girls und panzer) +steve (minecraft) +ran straherz +soundwave (transformers) +agent (girls' frontline) +mitsuru (darling in the franxx) +prinz eugen (kancolle) +morgan le fay (queen of winter) (fate) +pawmot +deino (pokemon) +gazer (monster girl encyclopedia) +gotoh hitori (octopus) +t.m. opera o (umamusume) +moogle +aizono manami +superboy +aoki kei +donquixote doflamingo +sky striker ace - raye +kallen kaslana (sixth serenade) +julius caesar (fate) +eliza (tekken) +sussurro (summer flower) (arknights) +yamamoto keigo +astolfo (memories at trifas) (fate) +allister (pokemon) +mikuma (kancolle) +taiyou (kancolle) +kamio reiji (yua) +brock (pokemon) +juvia lockser +rackam (granblue fantasy) +hagimura suzu +kemomimi-chan (naga u) +u-47 (azur lane) +anastasia (fate) +tsuchimiya kagura +lisbeth (sao-alo) +aoyama blue mountain +karyl (princess connect!) +yuki aine +dokugamine riruka +omaru polka (1st costume) +elaine (pokemon) +boudica (fate) +meloetta (aria) +fubuki (kancolle) +astraea (sora no otoshimono) +geronimo (fate) +folinic (arknights) +azki (hololive) +jirou kyouka +ladydevimon +tentomon +98se-tan +sonsaku hakufu +beenic +otegine +jeanne d'arc (swimsuit archer) (second ascension) (fate) +tsutsukakushi tsukiko +shigaraki tomura +kirima syaro +miraidon +beruka (fire emblem) +cu chulainn alter (third ascension) (fate) +shima (pepekekeko) +kusanagi nene +mokuren (kunoichi tsubaki no mune no uchi) +male protagonist (pokemon go) +sessyoin kiara +zabaniyya (housamo) +super tama musume +asahina momoko +enforcer (arknights) +mizuki kotori (yu-gi-oh!) +pikachu +sorcerer (fantasy earth zero) +hanamura teruteru +fighter (dq3) +ping myu ring (tandohark) +sylphy (amaburi) +cattleya (queen's blade) +otonashi saya +popukar (arknights) +tenma saki +platinum (arknights) +mokota mememe +boryeon (last origin) +geo stelar (mega man) +sarutobi asuma +aa-12 (girls' frontline) +kagura hikari +bloom (irys) +zeri (league of legends) +yat sen (azur lane) +odysseus (fate) +cure melody +izumi masamune +mechanic (ragnarok online) +cure rouge +sombra (overwatch) +sister cleaire +elizabeth bathory (fate/extra ccc) +kitakami (kancolle) +noihara himari +duskull +shuten douji (festival outfit) (fate) +touma kazusa +roto (kanae) +ueda suzuho +senri tsurubami +oka asahi +sukoya kana +hibiki (kancolle) +altera moontail +don quixote (project moon) +alder (pokemon) +sekine shiori +mimori (blue archive) +heiwajima shizuo +diluc (red dead of night) (genshin impact) +nakagawa nana +percival (granblue fantasy) +vf-1j +urakaze (kancolle) +harry potter +shiromiya mimi +izuna (gouma reifuden izuna) +fuji kiseki (umamusume) +fern (sousou no frieren) +haze (arknights) +katou asuka +suzutsuki (kancolle) +sakuma ritsu +polteageist +tsukumo sana +kitagawa marin +hau (pokemon) +agano (kancolle) +milla maxwell +formidable (azur lane) +mutsu (snail) +lilique kadoka lipati +kashimoto riko +jacq (pokemon) +kirishima eijirou +ichigo (cookie) +suzuki (girls und panzer) +emperor penguin (kemono friends) +sinon +backbeard +nove (nanoha) +nishida satono +death-sensei (mori calliope) +whislash (arknights) +ban hada +yagyuu kyuubei +azura (fire emblem) +nessen (live a hero) +perlica (arknights) +matou shinji +bottle miku +yashahime (momotarou densetsu) +heshikiri hasebe +tamamo no mae (jk) (fate) +kohaku (dr. stone) +belarus (hetalia) +optimus prime +susuwatari +erina pendleton +wallace (pokemon) +renekton +katana man (chainsaw man) +selkie (fire emblem) +dedue molinaro +ooyodo (kancolle) +ramina (baallore) +hijiri ageha +space ishtar (fate) +yamashiro takane +matsuhime mujina +othinus +katsura hinagiku +cecilia alcott +cecilia schariac +bruce wayne +termichan (not-a-bot) +wynn (yu-gi-oh!) +label girl (dipp) +penthesilea (fate) +yuzuhara konomi +murasaki shion (magical girl maid) +primarina +lacey (pokemon) +cream the rabbit +minami kotori (bird) +vyn richter (tears of themis) +sachiel (evangelion) +wakamo (blue archive) +sion eltnam atlasia +mari (dream c club) +hisoka morow +erza scarlet +illyasviel von einzbern +hero (dq8) +yamada aoi +daitaku helios (umamusume) +swordsman (ragnarok online) +takizawa asuka +rune knight (ragnarok online) +ishtar (swimsuit rider) (fate) +beautifly +banette +fusou (azur lane) +yatogami fuma +katara +asagiri shiori +fujioka haruhi +ayase eli +rogue (x-men) +sirius (scorching-hot seirios) (azur lane) +satonaka chie +mona (pact of stars and moon) (genshin impact) +kaitou jeanne +ikaruga luca +mikado (blazblue) +verxina (umamusume) +zapdos +shoukaku (sororal wings) (azur lane) +cure custard +rock lee +atago (kancolle) +shimushu (kancolle) +silver fox (kemono friends) +doodle sensei (blue archive) +minamoto shizuka +kashiwagi azusa +spider-man (miles morales) +iori sei +yang guifei (second ascension) (fate) +niijima makoto +alice liddell (american mcgee's alice) +forrest (fire emblem) +protean assassin melona +shirogane noel (summer 2020) +leonie pinelli +anjou naruko +mila babicheva +kusakabe misao +hojo sophy +munakata atsumi +mega gallade +momoe nagisa +nicola (granblue fantasy) +lucas (pokemon) +sakura miko (4th costume) +kizaki ren +cerestia of life +wilhelmina braunschweig ingenohl friedeburg +flying miku (project voltage) +floating fortress (kancolle) +g11 (girls' frontline) +adell (disgaea) +shindou hikaru +firewatch (arknights) +sogiita gunha +sin sack +basil (faraway) (omori) +alisa ilinichina amiella +nonomi (blue archive) +may (guilty gear) +kumbhira (granblue fantasy) +i-401 (kancolle) +ayane (swimsuit) (blue archive) +moby (elsword) +grimmjow jaegerjaquez +sensei (blue archive) +cresselia +momiji (blue archive) +ange (princess principal) +hisau maiya +mishaguji +aurochs (kemono friends) +toujou nozomi +mejiro mcqueen (end of sky) (umamusume) +ludger will kresnik +kokkoro (princess connect!) +eleanor hume +sejuani +ara haan +sakra devanam (elsword) +asuka (senran kagura) +teireida mai +colette brunel +sonya (kill me baby) +sunohara ayaka +urbosa +jin kisaragi +primrose azelhart +thief (final fantasy) +mage (dungeon and fighter) +mineta minoru +shylily (1st costume) +sonny brisko +shijou takane +ludicolo +kris (pokemon) +endou mamoru +rae taylor +akagi shigeru +montpelier (azur lane) +kisaragi shintarou +cham cham +kiyohime (fate) +sistine fibel +tamanami (kancolle) +lynn minmay +amano yukiteru +brynhildr (fate) +dsr-50 (girls' frontline) +nekonyaa (girls und panzer) +shuppet +toujou aya +ruan mei (honkai: star rail) +sena (xenoblade) +shikinami kai ni (kancolle) +yamanami keisuke (fate) +gorou (darling in the franxx) +yae sakura (flame sakitama) +serena (yu-gi-oh!) +blair (soul eater) +magus (chrono trigger) +mikudayoo +kukuri (mahoujin guruguru) +aika granzchesta +dio brando +zuikaku (the wind's true name) (azur lane) +stern the destructor +sessyoin kiara (swimsuit mooncancer) (first ascension) +holstein friesian cattle (kemono friends) +lyria (granblue fantasy) +spirit blossom kindred +suzuka utako +ouroboros (girls' frontline) +miguel o'hara +mazaki anzu +saigyou ayakashi +kyoumachi seika +honma himawari +negev (girls' frontline) +mejiro ryan (umamusume) +yuudachi (azur lane) +australian devil (kemono friends) +ui (blue archive) +mistorene callus +prinz eugen (final lap) (azur lane) +budew +kururugi suzaku +yotsuyu goe brutus +rosetta (punishing: gray raven) +mutsu-no-kami yoshiyuki +yukong (honkai: star rail) +kefka palazzo +megumi (girls und panzer) +sekka yufu +tatara kogasa +yamabuki saya +moonlight flower +kaniko (tsukumo sana) +inugami korone (1st costume) +eruruu +satellizer el bridget +rope (arknights) +winda (yu-gi-oh!) +sophia f shirring +ark royal (kancolle) +anubis (monster girl encyclopedia) +kittan bachika +high elf archer (goblin slayer!) +kiritani haruka +alear (female) (fire emblem) +asaka karin +eguchi sera +kaveh (genshin impact) +kousaka umi +taihou (temptation on the sea breeze) (azur lane) +laios thorden +mako (azuumori) +koromaru (persona) +shiroko (cycling) (blue archive) +minfilia warde +azuma lim +sokrates (touhou) +shingen seiji +peter b parker +metal crab (arknights) +nohara shinnosuke +titania (sao) +juliana (pokemon) +takaishi takeru +rukuriri (girls und panzer) +fukube tamaki +wolf link +adora (she-ra) +abigail williams (swimsuit foreigner) (fate) +illyasviel von einzbern (beast style) +miniwa tsumiki +yorktown (azur lane) +nishikata +hilda valentine goneril (summer) +lincoro +kel (faraway) (omori) +tang wutong (douluo dalu) +pino (jashin-chan dropkick) +suzushiro haruka +miyawaki sana +ahri (league of legends) +shirayuki (arknights) +fii-tan +kazami mizuho +mona (warioware) +admiral graf spee (azur lane) +eiko carol +uchi emiri +florence nightingale (chaldea lifesavers) (fate) +lockon stratos +tsunomaki watame +sakihata rimi +kirishima kai ni (kancolle) +elly (touhou) +yoo mina +rizu-kyun +akali (legacy) +tenkawa nayuta +takao (aoki hagane no arpeggio) +leona kingscholar +waluigi +gastly +okumura rin +eternal sailor moon +arctic fox (kemono friends) +hanamura yousuke +vileplume +nakiri erina +akiyama yoshiko +cheren (pokemon) +urotsuki +saria (the law) (arknights) +shishou (cookie) +yukina (yu yu hakusho) +shigure ui (1st costume) (vtuber) +shionne (tales) +uruha rushia (3rd costume) +clarine (fire emblem) +fuyou kaede +miyazen sakura +koiwai yotsuba +kaya (blue archive) +meiko (inuarashi) +feldt grace +ceobe (unfettered) (arknights) +marie antoinette (third ascension) (fate) +tatsugiri +fu hua (valkyrie accipiter) +lilina (fire emblem) +dialga +mizutani eri +bronya zaychik (silverwing: n-ex) +corticarte apa lagranges +kumano (kancolle) +archie (pokemon) +hoshino miyako (wataten) +heinrike prinzessin zu sayn-wittgenstein +dark jeanne +takeda harumi (shiromanta) +diantha (granblue fantasy) +lucia (pangya) +futaba anzu +mage (ragnarok online) +yumemi riamu +seaplane tender princess +m4a1 (mod3) (girls' frontline) +kunashiri (kancolle) +pepperoni (girls und panzer) +minagi mikoto +mp40 (girls' frontline) +virginia knights +monica kruszewski +layer (mega man) +aina ardebit +ken marinaris +liru +young link +otonashi yuzuru +avrora (azur lane) +monika weisswind +foongus +elizabeth f. beurling +yoshinoya (hidamari sketch) +onjouji toki +minazuki (kancolle) +ujimatsu chiya +mutsuki kai ni (kancolle) +drang (granblue fantasy) +minase iori +zyra +kamen rider 1 +eevee +elina (queen's blade) +hiradaira chisaki +twintail-chan (white-stew) +fighter (granblue fantasy) +cherubi +kafka (arknights) +akasaka mamoru +shingyoku (male) +aurora e. juutilainen +aerith gainsborough +kamekichi +taihou (enraptured companion) (azur lane) +sakura futaba +bianca (dq5) +lain paterson (1st costume) +ashleigh reid +charlotte (madoka magica) +cure diamond +mimori (swimsuit) (blue archive) +kusunoki shio +rockhopper penguin (kemono friends) +irui guneden +risky boots +higashikata daiya +amane (dream c club) +higashi setsuna +nidoqueen +ophelia (fire emblem) +itou chika +bremerton (day-off date) (azur lane) +anna nishikinomiya +small-clawed otter (kemono friends) +oshino shinobu +kaleido ruby +kohinata miho +hero (dq1) +myrtle (arknights) +himemori luna (1st costume) +shirayuki mizore +tsushima (kancolle) +ancient destroyer oni +pomu rainpuff +cuilein-anbar (genshin impact) +glaceon +mint (dewprism) +slowpoke +nana asta deviluke +shiori novella +reaper (overwatch) +aihara mei +cure bloom +onodera kosaki +maria (umineko) +clarisse (granblue fantasy) +angra mainyu (fate) +vice-versa (skullgirls) +sightseer (pokemon) +ines (arknights) +ogasawara rinko +blade (honkai: star rail) +makoto (street fighter) +nowa (queen's blade) +european hare (kemono friends) +majikina mina +susanna hopkins +shimura nana +rom (neptunia) +senkawa minato +marine nemo (fate) +juna crawford +kars (jojo) +jing ke (fate) +uchiha madara +mafumafu +yami sukehiro +hashimoto fumie +vatista +shinigami (ghost) (rain code) +gloom (irys) +kos-mos +sunkern +oricorio (pom-pom) +shizuki hitomi +miyu edelfelt +hozuki momiji +freyjadour falenas +aoi sora (pairan) +yoshikawa chinatsu +tachibana marika +zangief +wilhelmina carmel +mostima (spellbreaker) (arknights) +haruka (new year) (blue archive) +hisuian typhlosion +hiyou (kancolle) +rosetta (granblue fantasy) +kotori (blue archive) +quincy (nu carnival) +m16a1 (boss) (girls' frontline) +guzma (pokemon) +homu (honkai impact) +boa hancock +terebi-chan +cheng xiaoshi +roon (muse) (azur lane) +mizuhara chizuru +puru two +nero claudius (swimsuit caster) (fate) +shizuku murasaki +platelet (hataraku saibou) +nakajima (girls und panzer) +lysandre (pokemon) +majora (entity) +kida masaomi +lisa (a sobriquet under shade) (genshin impact) +futami mami +miyafuji miina +tsuyuri kanao +medusa (shingeki no bahamut) +baba konomi +yamada asaemon sagiri +juliona trans +shishigou kairi +togashi yuuta +kamen rider ooo +ni-class destroyer +napoleon bonaparte (fate) +wild tiger +reiji (gundam bf) +penance (arknights) +gregor (tsurunoka) +dazai osamu (bungou stray dogs) +ibuki suika +misaka mikoto +battleship water oni +cellien (kemono friends) +zara (azur lane) +ichinose minori +maeda toushirou +lyney (genshin impact) +sakuragi hinako +shinki (touhou) +fox child (doitsuken) +chiyoda momo +azki (4th costume) (hololive) +therion (octopath traveler) +tsunomaki watame (1st costume) +nian (unfettered freedom) (arknights) +hinamori momo +takao (azur lane) +power girl +chuchu (show by rock!!) +bataan (azur lane) +tanaka (chainsaw man) +sprigatito +kamen rider revi +kiana kaslana (knight moonbeam) +mori nagayoshi (fate) +dark precure +sendai (kancolle) +conte di cavour (kancolle) +kate (shadows house) +hoshiguma (arknights) +hoshimiya kate +tio plato +scarfy +kyukkyu-kun +tusk (stand) +kuramitsu mihoshi +bayonetta +wurmple +hydaelyn +danua +sakuma rei (ensemble stars!) +watson amelia (1st costume) +tisshu (karutamo) +ishizu ishtar +tsunashi kaoru +cutie honey (character) +tadano genji +kon (fate) +eriko (summer) (princess connect!) +stufful +violet evergarden +negi springfield +marie rose (devilish servant against the splashing waves) +justine (persona 5) +li syaoran +fengxi (the legend of luoxiaohei) +takoluka +son of droid (mechanical buddy universe) +iwado anna +kuroda kunika +puck (re:zero) +megumin +mega lucario +talos (housamo) +sylphiette (mushoku tensei) +doronjo +yakumo mitama +oshawott +gourgeist +saren (christmas) (princess connect!) +hoshi ryoma +kawajiri shinobu +konoe a. mercury +utsugi noriyuki +princess (7th dragon) +sazabi +blanc (neptunia) +chouun shiryuu +siebold (pokemon) +cure coral +mimino kurumi +misaki mei +yoshida ayumi +azusa (swimsuit) (blue archive) +hanakuma chifuyu +nui sociere +beam (chainsaw man) +atalanta (fate) +wishiwashi (school) +9a-91 (girls' frontline) +habetrot (fate) +garma zabi +bulbasaur +tsukuyomi ai +kurusu natsume +barbariccia +hong meiling (panda) +torkoal +sadaharu +villager (animal crossing) +coconut (nekopara) +kochou kanae +35p (sakura miko) +chitose (kancolle) +me-tan +lina inverse +adaman (pokemon) +rena lanford +wadanohara +heavyrain (arknights) +ronye arabel +motoba kirie +koopa troopa +hoshino ruby +kirara (genshin impact) +poke kid (pokemon) +suou pavlichenko +katsuragi ace (umamusume) +asanaka yomogi +towa monaca +machop +musashi (kancolle) +marnie (pokemon) +crystalfly (genshin impact) +annette fantine dominic +servbot (mega man) +baltimore (after-school ace) (azur lane) +natsu dragneel +poniko +yao fueifuei +tingyun (honkai: star rail) +fujimaru ritsuka (male) (true ether chaldea uniform) +eelektross +irene (voyage of feathers) (arknights) +blanka +hinata natsumi +orfevre (umamusume) +arcane caitlyn +octillery +mei mei (jujutsu kaisen) +edna (tales) +dango-chan (4shi) +cthugha (nyaruko-san) +gonzalez (machita chima) +pamiat merkuria (azur lane) +onsoku no sonic +wakamatsu hirotaka +gebura (project moon) +yoshida hirofumi +toki (bunny) (blue archive) +charlotte (seiken densetsu 3) +eto (tokyo ghoul) +mantyke +dark haruka +sebastian piyodore +viki (suikoden) +code: battle seraph (elsword) +musubi +geoffroy's cat (kemono friends) +momota kaito +jude mathis +amane kanata +independence (azur lane) +mimi (princess connect!) +valerie (pokemon) +minccino +girl holding a cat (kancolle) +igawa sakura +tohru (maidragon) +kazakami saaya +cynthia (pokemon) +hellagur (arknights) +ling (arknights) +reigen arataka +ermes costello +usugumo (kancolle) +kubiwa (kutan) +manticore (monster girl encyclopedia) +kagamine len (append) +satou hina (kamisama ni natta hi) +douki-chan (douki-chan) +iono (pokemon) +elf (houtengeki) +nancy lee +misaka imouto 10032's cat +p90 (girls' frontline) +kos-mos ver. 4 +selen tatsuki (2nd costume) +kisara (tales) +abigail williams (emerald float) (fate) +kuzuha (nijisanji) +tsukuyomi shirabe +namazu +mecha-fiora +testament (guilty gear) +miho (last origin) +yuzu (maid) (blue archive) +shizuka (queen's blade) +bede (pokemon) +amity blight +ignatz victor +black gold saw +swimmer (pokemon) +ultima (fft) +nier (granblue fantasy) +zigzagoon +miyuki (kancolle) +elekid +emma sheen +ashigara kai ni (kancolle) +kuja +morisawa chiaki +yuunagi tsubasa +ki (druaga) +tendou akane +pyro (tf2) +natsuki rin +reinhard van astrea +emma woods +amemiya nazuna +potato (pui pui molcar) +yuffie kisaragi +xiao wu (douluo dalu) +fuuka reventon +nena trinity +kiyohime (swimsuit lancer) (fate) +pit (kid icarus) +call (mighty no. 9) +leona heidern +henry (fire emblem) +kokkoro (real) (princess connect!) +sv-98 (girls' frontline) +matsukaze tenma +sagat +fuka-chan +toon zelda +whitesmith (ragnarok online) +croissant (arknights) +murakumo (senran kagura) +alice carroll +k2 (girls' frontline) +sayori (doki doki literature club) +arcee +cheese-kun +minna-dietlinde wilcke +shuten douji (halloween caster) (fate) +asahina mikuru (adult) +enderman +pina (sao) +kawamura reo +utsugi kotoko +komekko +lux (league of legends) +sakura miko (3rd costume) +stelle (honkai: star rail) +poliwrath +akatsuki (log horizon) +komiyama kotomi +nekomiya nono +isurugi futaba +shibuya kanon +kirishima (kancolle) +winry rockbell +ryoma (fire emblem) +sakurazaki setsuna +sakurasawa sumi +romg +yurizaki mira +thoma (genshin impact) +graf eisen +tooyama rin +loki (marvel) +jedah dohma +miyako (blue archive) +mina tepes +kuzunoha (blue archive) +marufuji ryou +lee ji-eun +phantom (arknights) +m4a1 (girls' frontline) +myoudouin itsuki +kitten (gravity daze) +louise francoise le blanc de la valliere +hirano aya +scaramouche (cat) (genshin impact) +mejiro mcqueen (umamusume) +vera (punishing: gray raven) +blaze the cat +giroro +micha jawkan +logix ficsario +devilot de deathsatan ix +tang keke +aurea juniper +aru (blue archive) +ilya (princess connect!) +basil (omori) +i-400 (kancolle) +houshou marine +eun (elsword) +scout (tf2) +tsujino akari +satou lilly +ump9 (girls' frontline) +tsunomaki watame (3rd costume) +tomimi (silent night) (arknights) +super-shorty (girls' frontline) +riven (league of legends) +hyoudou issei +touhoku kiritan +latias +abigail williams (swimsuit foreigner) (third ascension) (fate) +bloop (gawr gura) +bakugou mitsuki +kakuzu (naruto) +jeanne d'arc (ruler) (fate) +shirokane rinko +katsuragi (kancolle) +female trainer (umamusume) +dateko +zenobia (fate) +toon link +yuri petrov +vulpix +takenouchi sora +shaddiq zenelli +theodor bachstein +myoukou (kancolle) +son gohan (future) +angelica (project moon) +mash kyrielight +archer (ragnarok online) +koharu (blue archive) +jeanne d'arc (third ascension) (fate) +void princess (elsword) +petra gurin (1st costume) +touhoku zunko +akai suzaku +koshimizu sachiko +super sailor saturn +amatori chika +roboco-san (1st costume) +altaria +fujino shizuru +shihouin yoruichi +ex albio +suzukaze aoba +haruno haruka +wakaouji ichigo +rose (street fighter) +reisalin stout +jekyll and hyde (fate) +izanagi (persona 4) +manjuu (azur lane) +snowball (overwatch) +luo tianyi +linde (fire emblem) +penguin 3-gou +kurusu tomari +komori met +pauline (mario) +takatsuki ichika +shiro (no game no life) +galko +i-203 (kancolle) +kunihiro hajime +melissa kinrenka +sabitsuki +zero two (kirby) +koshimizu toru +yang lee +bellows (suisei no gargantia) +taisa (cookie) +kudo shinobu +diyusi (cookie) +xayah +meloco kyoran +kasuga (sengoku basara) +santa alter +raven (tales) +idia shroud +ookuma satomi +cu chulainn (second ascension) (fate) +shiranui mai +fuwawa abyssgard (1st costume) +pam-pam (precure) +laffey (azur lane) +hiiro (alchemy stars) +kagura (gintama) +candice (pokemon) +minami kotori +washington (azur lane) +angel (evangelion) +sanji (one piece) +tifa lockhart +zundamon +saber alter (ver. shinjuku 1999) (fate) +chi-class torpedo cruiser +warwick +chobi (akchu) +shimazu yoshino +kingprotea (fate) +azusawa kohane +kokonoe tsubaki +locke cole +anthony (madoka magica) +kokoro (darling in the franxx) +ho-oh +porygon +devola +yato (arknights) +algerie (azur lane) +sekibanki +taranza +spot (arknights) +marowak +goshiki agiri +frankie foster +obi-wan kenobi +kino (kino no tabi) +kasane teto (sv) +okabe rintarou +ichinose uruha +emelie (cyancapsule) +mochida arisa +gorgon (fate) +yokoyama nao +itou ittousai (sengoku bushouki muramasa) +kray foresight +eri (boku no hero academia) +lady avalon (second ascension) (fate) +air groove (umamusume) +serge (chrono cross) +sakuragi hanamichi +warrior of light (ff1) +large-spotted genet (kemono friends) +zara (poolside coincidence) (azur lane) +kobayakawa miyuki +female sensei (blue archive) +ciel (elsword) +hanako (jibaku shounen hanako-kun) +jeanne d'arc (fate) +ryuugamine mikado +pardofelis (honkai impact) +yuri briar +vanilluxe +hana macchia +yumizuka satsuki +yukimi koume +san (mononoke hime) +galarian moltres +suzunari shizuku +happy chaos +yanagi kiyora +sakura hime +takimoto hifumi +eiden (nu carnival) +kafuu chino +magical mirai miku (2022) +dorothy west +le malin (listless lapin) (azur lane) +surtr (colorful wonderland) (arknights) +maribelle (fire emblem) +viper (valorant) +joutouguu mayumi +konori mii +afuro terumi +seeu +utatane piko +darkness (konosuba) +ryomou shimei +malenia goddess of rot +sideroca (light breeze) (arknights) +nidoran +super sailor venus +yuni (princess connect!) +videl +hanako (blue archive) +shu yamino +maya (kancolle) +little bel (azur lane) +escavalier +collei (genshin impact) +angelina (arknights) +reticulated giraffe (kemono friends) +yamana akane +light valkyrie (p&d) +kozakura marry +hephaestus (housamo) +scathacha (granblue fantasy) +sakurai rihoko +erica hartmann +unsinkable sam +iori (swimsuit) (blue archive) +siro (dennou shoujo youtuber siro) +amane misa +x (mega man) +tsukino mito (1st costume) +vermillion akiha +cordelia glauca +momose rio +rem galeu +frederica bernkastel +hassan of the cursed arm (fate) +zero (drag-on dragoon) +texas the omertosa (arknights) +araragi karen +sakuya (sister princess) +hashimoto nyaa +siegfried (granblue fantasy) +james buchanan barnes +velma dace dinkley +matatagi hayato +cornelia li britannia +tamaki ako +bagpipe (arknights) +kayle (league of legends) +mighty yukiko +yuri (project moon) +skadi the corrupting heart (arknights) +yuki miku (2011) +son gohan +sakuma jirou +haruna (new year) (blue archive) +ember (selen tatsuki) +vikala (granblue fantasy) +hoshino fumina +carmilla (fate) +baobhan sith (second ascension) (fate) +ryuushen +caracal (kemono friends) +lotte jansson +byleth (male) (fire emblem) +ryuubi gentoku +elaina (majo no tabitabi) +zdrada (helltaker) +yanqing (honkai: star rail) +shirogane noel (school uniform) +fumizuki kai ni (kancolle) +athrun zala +kirito (sao-ggo) +medb (fate) +ninomae ina'nis (5th costume) +missouri (warship girls r) +yukimura aoi +amano soraha +akatsuki kai ni (kancolle) +nina (breath of fire ii) +cheryl (pokemon) +aisha clanclan +saotome ako +tokino sora +hinomaru (kotoba) +ayase fuuka +isaac foster +izumi reina +thea (nekojira) +jin (xenoblade) +caeda (fire emblem) +estelle bright +rorisakyubasu-chan (mochiyuki) +ashelia b'nargin dalmasca +luna platz (mega man) +narusawa ryouka +lily (granblue fantasy) +amiya (guard) (arknights) +konpaku youmu +toga himiko +yashiro momoka +corrin (fire emblem) +conte di cavour nuovo (kancolle) +type 79 (girls' frontline) +vestia zeta (1st costume) +suou amane +itoshiki nozomu +jellal fernandes +blue rose (tiger & bunny) +fuutou shizune +perfumer (arknights) +stella vermillion +osakabehime (swimsuit archer) (second ascension) (fate) +cure soleil +hatsushimo kai ni (kancolle) +damian wayne +sakata kintoki (fate) +chrono harlaown +himeyuri sango +ohishi izumi +amulet heart +noibat +tsurugi (swimsuit) (blue archive) + (robot) (blue archive) +nakagawa natsuki +hibari kyouya +yamashiro (summer offensive?) (azur lane) +pannacotta fugo +eric cartman +mizushima asa +kuronuma sawako +kintsuba (shiranui flare) +fox devil (chainsaw man) +kronshtadt (azur lane) +commander (nikke) +puui (grandia) +justina follower (blue archive) +kagami kira +niya (blue archive) +noir (nikke) +nagumo haruya +teruzuki (kancolle) +mitsuba greyvalley +bahamut (final fantasy) +iori rinko +caligula (fate) +hiryuu (azur lane) +galarian ponyta +loba (apex legends) +naruse shiroha +satou sakie +tyranitar +wanda maximoff +kimidori emiri +shoto (vtuber) +saeki sayaka +sei shounagon (fate) +kirijou mitsuru +bloody marie (skullgirls) +mostima (arknights) +marona (phantom brave) +christophe giacometti +starscourge radahn +st ar-15 (girls' frontline) +nausicaa +electivire +lucifero (guilty gear) +kanzuki karin +avicebron (fate) +chat noir +manaka laala +cherche (fire emblem) +chara (undertale) +scizor +veteran mercenary echidna +mikado shiina +ayla (chrono trigger) +ushiromiya rosa +joker (dc) +seele (honkai: star rail) +shimohara sadako +lisia (pokemon) +draculina (last origin) +tsuchimikado motoharu +enemy naval mine (kancolle) +gary oak +zessica wong +jeremiah gottwald +ukraine (hetalia) +ethan (arknights) +jungle pocket (umamusume) +li shuwen (young) (fate) +boko (girls und panzer) +artemis of the blue +misaka worst +sanka rea +yumeno himiko +isaac clarke +primula +shadow (nerissa ravencroft) +mochizuki momiji +yuubari (kancolle) +scathach (piercing bunny) (fate) +zorua +chikujouin magane +admiral graf spee (peaceful daily life) (azur lane) +essex (warship girls r) +kira yoshikage +shidou mariya +minagi koharu +tan yang (kancolle) +xiao dianshi +paul (pokemon) +chilchuck +muji-muji daruma (genshin impact) +mata hari (fate) +alear (male) (fire emblem) +maaryan (to heart) +crimvael +viktor nikiforov +ishtar (fate) +sturm (granblue fantasy) +snow white (mahoiku) +youko (girls und panzer) +blanca (fate) +politoed +hisanuma sayu +tidus +kazano hiori +kyon +vajra (granblue fantasy) +sen (granblue fantasy) +ichigo hitofuri +quina quen +serika (swimsuit) (blue archive) +kuroki tomoko +charlotte hazellink +cure lemonade +sengoku kamuri +walter white +chinese white dolphin (kemono friends) +patchouli knowledge +dog (shiba inu) (kemono friends) +aris (blue archive) +biko pegasus (umamusume) +quattro bajeena +aurorus +shikishima mirei +nori (hidamari sketch) +iskandar (fate) +hakuryuu (azur lane) +lyn (blade & soul) +janna (league of legends) +lalah sune +treecko +kurokoma saki +pirotess +richelieu (kancolle) +suzuhara touji +kido saori +yaia (granblue fantasy) +kousaka tamaki +flora (fire emblem) +dark magician girl +atem +minato aqua (5th costume) +medli +kiana kaslana +paseri (cookie) +chypre (heartcatch precure!) +ayane (blue archive) +charlotta (granblue fantasy) +ryuujou kai ni (kancolle) +kasuga ayumu +g3 (girls' frontline) +bagpipe (queen no. 1) (arknights) +cure summer +amano maya +kurokawa chiaki +viviana (arknights) +doma umaru +nate mitotsudaira +marius von hagen (tears of themis) +hanazuki (azur lane) +peroro (blue archive) +hungary (hetalia) +helianthus (girls' frontline) +ultimate madoka +soda (nikke) +hong lu (project moon) +anzai romi +florence nightingale (trick or treatment) (fate) +sovetskaya belorussiya (azur lane) +hinoshita kaho +smug nun (diva) +sirius (azure horizons) (azur lane) +serizawa akane +owari (azur lane) +black lady +zain (omaru polka) +sidon +banjo (banjo-kazooie) +epona +uranami (kancolle) +rir-chan +yuzuki roa +sakino asuka +yuna (ff10) +surskit +billy kane +silver (pokemon) +djeeta (granblue fantasy) +ignis scientia +bronya zaychik (wolf's dawn) +mint (arknights) +mishou mai +morgan le fay (fate) +natalia luzu kimlasca lanvaldear +scotch (meitantei conan) +sakamoto ryouma (fate) +nishizumi shiho +parvati (fate) +momo velia deviluke +asuna (sao) +aino megumi +carol malus dienheim +phara suyuf +fiz (fizintine) +bitey (arknights) +flamie speeddraw +kurame +kiriya aoi +dragonite +assassin (fate/zero) +uehara ayumu +narita brian (umamusume) +piranha plant +meltryllis (third ascension) (fate) +kenzaki makoto +kyururu (kemono friends) +wuxian (the legend of luoxiaohei) +paladin (sekaiju) +moose (kemono friends) +aloe (quiz magic academy) +ghetsis (pokemon) +sukonbu (shirakami fubuki) +merlin (fate/prototype) +kanda yuu +sumeragi aika +hina (blue archive) +sara (gundam build divers) +ruru amour +kuroshio kai ni (kancolle) +hoshikawa lily +annie (league of legends) +sonya (fire emblem) +todoroki hajime +magneton +sanaki kirsch altina +ootori emu +kashino (azur lane) +poison miku (project voltage) +cinque (nanoha) +yuuhi riri +ayin (project moon) +ghost (modern warfare 2) +viral (ttgl) +kamukura izuru +lickitung +majin buu +zoroark +yomako +aegislash +sailor neptune +roll caskett (mega man) +grass wonder (umamusume) +mia (fire emblem) +okusawa misaki +itoshi rin +specter the unchained (arknights) +grima (fire emblem) +poipole +kawashima ami +yukikaze kai ni (kancolle) +belldandy +komaki manaka +naraka (nijisanji) +prinz heinrich (azur lane) +xu fu (fate) +bronya zaychik +tokisaki mio +olga marie animusphere +predator (character) +isabelle (shadowverse) +anya (spy x family) +yukishiro honoka +luluko +caspar von bergliez +tenshouin eichi +anchovy (girls und panzer) +shyvana +eye (okame nin) +cress albane +misogi (princess connect!) +nakano miku +corrin (male) (fire emblem) +centiskorch +producer (idolmaster anime) +aomine daiki +rillaboom +usagi-san +narukami yuu +kamiyama shiki +tsurumi chiriko +iona (aoki hagane no arpeggio) +ayanokouji rem +asagiri youko +inaba mob (touhou) +kaguya luna +nekko (momosuzu nene) +raymond (animal crossing) +evil eye sigma +taikoubou +kunizuka yayoi +tenochtitlan (fate) +alphen (tales) +luo xiaohei +ibuki douji (fate) +arataki itto +glasses nun (diva) +thancred waters +ishida shouya +kusakabe maron +musharna +surge (pokemon) +kihel heim +shadowheart (baldur's gate) +kitaooji satsuki +taion (xenoblade) +king crimson (stand) +sara (granblue fantasy) +isshiki iroha +irisu fuyumi +adam (honkai impact) +blue oak +presea combatir +sakuranomiya maika +fi (zelda) +hero (dq3) +angel devil (chainsaw man) +loran cehack +meganium +stone free +sora (kingdom hearts) +gagaga girl +blemishine (arknights) +cure aqua +tachibana momoka +spain (hetalia) +rossiu adai +sumire (blue archive) +electro emilia +cody travers +osakabehime (fate) +cait sith (ff7) +yabuki kana +yorumi rena +kamisato ayato +daki (kimetsu no yaiba) +sitonai (fate) +ak-12 (quiet azure) (girls' frontline) +duryodhana (fate) +konngara (touhou) +kuwata leon +kashiyuka +asukagawa chise +otonashi haruna +iris (mega man) +yomi (p&d) +isokaze (azur lane) +paldean wooper +saigyouji yuyuko (living) +bache (azur lane) +ashikaga chachamaru +lillia (league of legends) +mp7 (girls' frontline) +octane (apex legends) +marina (blue archive) +watatsuki no toyohime +cinderace +tanigawa kanna +lal'c mellk mal +puniru (puniru wa kawaii slime) +rem (re:zero) +momohime +magni dezmond +tsukuyo (blue archive) +naruse mio +akita neru +bubba (watson amelia) +patrat +elizabeth bathory (second ascension) (fate) +zidane tribal +manectric +takao (kancolle) +otto apocalypse +scarlet witch +yulong (journey to the west) +solaria (ebonyxh) +chloe (school festival) (princess connect!) +dunbine +leiur darahim +mouse girl (yuuki (yuyuki000)) +serval (kemono friends) +fukase +emma verde +nameless bard (genshin impact) +nanbu kaguya +fukumaru koito +something (omori) +heanna sumire +sakura hibiki +kaban (kemono friends) +taneshima popura +tomoe gozen (fate) +kimeemaru +nonowa +ichika (1ssakawaguchi) +mishima kazuya +owada mondo +ushigome rimi +the baddest evelynn +bubbles (ppg) +daida +enemy lifebuoy (kancolle) +takanashi souta +kuchiki byakuya +oryou (fate) +suzuran (lostlands flowering) (arknights) +yoshida kazumi +uzuki sayaka +shingyoku (touhou) +lady avalon (fate) +ch'en the holungday (elite ii) (arknights) +xp home-tan +decapre +arin +jean (genshin impact) +toudou aoi (jujutsu kaisen) +isonami (kancolle) +sakura miko (school uniform) +akagi (paradise amaryllis) (azur lane) +sakurada shiro +neuroi +frye (splatoon) +wu zetian (swimsuit caster) (fate) +curren chan (sakutsuki ma cherie) (umamusume) +felicia hardy +kuririn +mizuki (vtuber) +porygon2 +cure mint +mitaka asa +001 (darling in the franxx) +reuniclus +akashi kaoru +nika nanaura +li shuwen (fate) +selesia upitiria +kitsune spirit (doitsuken) +cheria barnes +ichijou ayaka +seto midori +rivier (kuzuyu) +celebi +kuwabara kazuma +mipha +audrey hall +batgirl +sakura ayane +suzuki sayaka +tonelico (fate) +excellen browning +capybara (kemono friends) +lute (fire emblem) +bronya zaychik (haxxor bunny) +asterios (fate) +claudia hortensia +bernadetta von varley +nippaku zanmu +cure rosetta +sumia (fire emblem) +yoshida masaki +hatsune miku (vocaloid4) +blue delmo +altina orion +m4 sopmod ii (mod3) (girls' frontline) +inokuma youko +sorceress (dragon's crown) +konoe subaru +fujimaru ritsuka (male) (mage's association uniform) +jessica rabbit +emet-selch +kizuna ai +higokumaru +elisabeth von wettin +quetzalcoatl (fate) +togo ai +hoshino ichika (project sekai) +petra johanna lagerkvist +happiny +common raccoon (kemono friends) +david martinez +wa-class transport ship +risty (queen's blade) +menace (queen's blade) +re-class battleship +fumizuki (kancolle) +nagase minato +fushiguro touji +chroche latel pastalie +columbina (genshin impact) +sadida +myne (honzuki no gekokujou) +seofon (granblue fantasy) +space yoko +haunter +kinshi no ane +aruruu +itsuki shu +roromiya karuta +cirno +aoki reika +bayleef +bastion (overwatch) +mewo +hyakumantenbara salome (1st costume) +eustace (granblue fantasy) +catria (fire emblem) +sasaki saku (1st costume) +gotoh hitori (tsuchinoko) +hatsuzuki (kancolle) +heroine (dq4) +hirose sumire +antonio salieri (fate) +yellow diamond (houseki no kuni) +cai lin (doupo cangqiong) +futaba hotaru +judd (splatoon) +brief (psg) +inumaki toge +poland (hetalia) +p-chan +arima kana +blind girl (popopoka) +keith goodman +viper (toxic rabbit) (nikke) +kuhouin murasaki +oda nobunaga (fate) +ahiru (princess tutu) +sucrose (genshin impact) +zebstrika +tsunashi hajime +hisamura natsuki +rogue (ragnarok online) +fluttershy +sakamata chloe (1st costume) +guy cecil +nia (blade) (xenoblade) +misora (princess connect!) +eris (konosuba) +akihiro altland +sina (pokemon) +aoi kimi +link +regis altare +sophie twilight +sachi (sao) +lene (fire emblem) +nitocris (third ascension) (fate) +seffyna +kaneda shoutarou (akira) +felyne +nachi (kancolle) +siesta (zero no tsukaima) +otter spirit (touhou) +cecilia (shiro seijo to kuro bokushi) +hyodo rena +nagisa (blue archive) +milk (cookie) +shez (male) (fire emblem) +okita souji alter (swimsuit saber) (fate) +panda meiling (seki (red shine)) +margaretha sorin +rose (pokemon) +tsurugi minko +ryuuzaki umi +skarmory +night angel (last origin) +chou-10cm-hou-chan (suzutsuki's) +niimi sora +saten ruiko +unused character (kill me baby) +tamasaka makoto +yuzuki choco (1st costume) +hagakure tooru +levi the slasher +stella unibell +aki shizuha +beast boy (dc) +flamebringer (arknights) +le malin (sleepy sunday) (azur lane) +iris (asteroid ill) +vlad iii (fate/apocrypha) +otonashi meru +mysta rias (1st costume) +osanai (shashaki) +francesca lucchini +dizzy (guilty gear) +cynthia riddle +zekrom +venti (genshin impact) +hayasaka mirei +aussa (yu-gi-oh!) +escalayer +suzumiya akane +akuta hinako +nami (league of legends) +serina (nurse) (blue archive) +lilligant +kiana kaslana (herrscher of flamescion) +issun +akizuki ryo +carbuncle (final fantasy) +meowstic +jonathan joestar +domon kasshu +lisa silverman +ayunda risu (1st costume) +orimoto rika +carleen (alchemy stars) +sanallite (tsukumo sana) +august von parseval (the conquered unhulde) (azur lane) +hugh (pokemon) +ponyta +garou (one-punch man) +constance von nuvelle +ousaka nanami +ky kiske +nagao kagetora (fate) +astrologian (final fantasy) +shizuka joestar +lee (arknights) +hacka doll 1 +tamamo cat (fate) +salama (amaburi) +seiya kou +mori calliope (1st costume) +yamashiro kai ni (kancolle) +terror (azur lane) +yui (angel beats!) +comfey +aoyagi touya +chise (swimsuit) (blue archive) +torgal (ff16) +adeptus astartes +kaiboukan no. 4 (kancolle) +akimoto komachi +aria pokoteng +kodama izayoi +neru (bunny) (blue archive) +marshadow +asuna (bunny) (blue archive) +henrietta (gunslinger girl) +isabella valentine +erica fontaine +oyama mihari +rin (blue archive) +yazawa nico +yoko littner +abyssal crane princess +tenryuu (kancolle) +medicham +faust (project moon) +framme (fire emblem) +takanashi kiara +aida kensuke +shizuru (summer) (princess connect!) +rx boss +adagumo no saragimaru +bushidou 2 (sekaiju) +boo tao (genshin impact) +zhongli (genshin impact) +hoto cocoa +arf +nanashi mumei (3rd costume) +magnemite +dire wolf (kemono friends) +supergirl +toono mizuki +lilith aensland +okano kei +flabebe +hieda no akyuu +zange +saimon tamaki +sailor cosmos +sano manjirou +hiiragi kagami +cherno alpha +lilith (saikin yatotta maid ga ayashii) +cure honey +pansage +d.o.c. health drone +super creek (umamusume) +ulquiorra cifer +reunion soldier (arknights) +saber lion +lieza (arc the lad) +carl clover +red mage +ushiwakamaru (swimsuit assassin) (first ascension) (fate) +sandygast +shidou irina +fujiwara no mokou (young) +kamen rider build +irida (pokemon) +funada ui +usaslug (tsukumo sana) +midare toushirou (kiwame) +getter-1 +galvantula +perrine h. clostermann +kido jou +leonidas (fate) +caulifla +petra macneary +pneuma (xenoblade) +krieg (skullgirls) +hinoa +satsuki kai ni (kancolle) +saren (summer) (princess connect!) +camus (dq11) +josie rizal +type 97 (peony) (girls' frontline) +cucco +prisma illya +gilzaren iii +litwick +isekai joucho +katano sukune +jack frost +liliya (kaetzchen) +hime granzchesta +johnathan mar +ryoumen sukuna (jujutsu kaisen) +bazett fraga mcremitz +tougou mimori +leonmitchelli galette des rois +shokudaikiri mitsutada +takakura shouma +pekomon (usada pekora) +echidna (re:zero) +etna (kuzuyu) +amatsuka megumi (gj-bu) +aragaki ayase +nadia la arwall +cardigan (arknights) +dorothea arnault +cagalli yula athha +rororina fryxell +lava (arknights) +ishigaki (kancolle) +poison (final fight) +kuki shinobu +aketa mikoto +staryu +shizuka rin +kasugano sakura +hosomi shizuko +helltaker (character) +braveman +moriyama shiemi +ajax (azur lane) +tachibana taki +little boy commander (azur lane) +fnc (girls' frontline) +poliwag +beowulf (fate) +hamaguchi ayame +sara valestein +aile (mega man zx) +kiran (fire emblem) +touyama nao +adagaki aki +ekko (ejami) +gene (pso2) +yukoku roberu +ooi (kancolle) +zero (zero kara hajimeru mahou no sho) +eula (genshin impact) +flower girl (yuuhagi (amaretto-no-natsu)) +okazaki yasuha +mitarai shouta +rachnera arachnera +kiki (majo no takkyuubin) +michishio (kancolle) +reinforce +golurk +atlantic puffin (kemono friends) +akane (bunny) (blue archive) +phalaenopsis (msa (fary white)) +yang guifei (first ascension) (fate) +tsushima yoshiko +solrock +tategami aoi +caelus (honkai: star rail) +decidueye +relm arrowny +team rocket grunt +pa-15 (girls' frontline) +kasumi (azur lane) +igarashi futaba (shiromanta) +dragon kid +winning ticket (umamusume) +kukui (pokemon) +skadi (waverider) (arknights) +yoneme mei +artoria caster (swimsuit) (first ascension) (fate) +yamaguchi kisaragi +kujo holy +anisphia wynn palettia +firion +takao (d-frag!) +maam +nozaki umetarou +roll (mega man) +jessica albert +la pucelle (mahoiku) +hero (dq11) +kobayakawa sena +charlotte izoard +gabriel (granblue fantasy) +mount lady +hirano toushirou +bad end happy +majima goro +marui mitsuba +nanami (suikoden) +elin +nelson (kancolle) +hinata kaho +santa claus (chainsaw man) +izayoi (blazblue) +sakura ritsuki +el condor pasa (umamusume) +ankha (animal crossing) +cinnamoroll +thorns (arknights) +tiki (adult) (fire emblem) +marina ismail +momoi azuki +haramura nodoka +sengo muramasa (touken ranbu) +aster arcadia +outis (project moon) +hello kitty (character) +komadori renge +amaretto (girls und panzer) +goliath doll +gawr gura (3rd costume) +ueno (ueno-san wa bukiyou) +crownslayer (arknights) +shiver (splatoon) +king hassan (fate) +hanna-justina marseille +rimuru tempest +alpha (punishing: gray raven) +miyamoto musashi (first ascension) (fate) +dark magician +winged kuriboh +oogaki chiaki +s.a.t.8 (girls' frontline) +kanae (nijisanji) +hoshii miki +romero (zombie land saga) +gladion (pokemon) +mononobe alice +jack krauser +sakurai touko +euryale (third ascension) (fate) +kanzaki h. aria +pekomama +bruno bucciarati +hongryeon (last origin) +amelie planchard +kikuri (touhou) +fuuchouin kazuki +erika (pokemon) +bowsette +dimension witch (elsword) +tobisawa misaki +quote (doukutsu monogatari) +furutaka (kancolle) +suicune +marill +arctic hare (kemono friends) +director chimera (spy x family) +jett (valorant) +dsr-50 (highest bid) (girls' frontline) +cavall the 2nd +incineroar +kabuto daigo +dorothea arnault (summer) +snorlax +naoko-san +twintelle (arms) +nakiri ayame +usada pekora (1st costume) +fubuki (blue archive) +franziska von karma +razor (genshin impact) +mahira (granblue fantasy) +kid (chrono cross) +platinum (shimmering dew) (arknights) +watson amelia +gregor (project moon) +senou natsuru +ground miku (project voltage) +madobe nanami +yamamoto akira +kangaskhan +ryougi mana +izanami (persona) +sky striker ace - roze +kaga (everlasting killing stone) (azur lane) +k/da ahri +chouzetsusaikawa tenshi-chan +antispiral nia +jill warrick +le malin (mercredi at the secret base) (azur lane) +kuromi +kanroji mitsuri +nyatasha nyanners +demon (monster girl encyclopedia) +komi shuuko +monk (fft) +maruzensky (blasting off summer night) (umamusume) +helena (azur lane) +minato aqua (hololive summer 2019) +magikarp +yotsuya miko +kon kanaho +takanashi touka +mori calliope (3rd costume) +m1 garand (girls' frontline) +tohno shiki (2) +nekomiya ryuu +african wild dog (kemono friends) +alice (megami tensei) +itsuka kotori +io (granblue fantasy) +asuka hina +passenger (arknights) +if (neptunia) +solosis +cra (wakfu) +lana's mother (pokemon) +es (xblaze) +estinien varlineau +kirishima touka +bambietta basterbine +souya (kancolle) +brigitte (overwatch) +terra branford +faputa +caroline (persona 5) +baobhan sith (first ascension) (fate) +grecale (kancolle) +uki violeta +white tiger (kemono friends) +bailu (honkai: star rail) +ling yao +buggy the clown +ryougi shiki +luca (yu-gi-oh!) +ha-class destroyer +hacka doll 2 +saki (swimsuit) (blue archive) +sushang (honkai: star rail) +komeiji satori +newcastle (azur lane) +hakase fuyuki +clair lasbard +takayama maria +kumamon +abe nana +ki-sikil (yu-gi-oh!) +spinarak +wolfgang amadeus mozart (fate) +sniper (tf2) +todoroki touya +ooshio (kancolle) +asclepius (fate) +kanbaru suruga +houshou marine (1st costume) +horikawa kunihiro +matsunaga ryo +athena glory +mona (genshin impact) +seras victoria +kar98k (girls' frontline) +morisawa yuu +marie antoinette (swimsuit caster) (second ascension) (fate) +shinomiya karen +euphemia li britannia +takami chika +gentoo penguin (kemono friends) +yorigami shion +jk-chan (oouso) +kolin +naegino sora +shirogane noel (3rd costume) +karibuchi takami +yanfei (genshin impact) +failure penguin +tenzin (arknights) +rfb (girls' frontline) +sora ginko +roll.exe (mega man) +elan ceres +blastoise +kaitou kid +takao (school romanza) (azur lane) +akashi seijuurou +asuna (sao-alo) +botan (clannad) +empoleon +seiren (suite precure) +joker (cookie) +fenrir (shingeki no bahamut) +mareep +charlotte (fire emblem) +wiz (konosuba) +stunfisk +donald duck +anakin skywalker +melusine (first ascension) (fate) +aisaki emiru +philena ivy +narita top road (umamusume) +otomachi una +kagami sumika +tanaka mamimi +kazuno leah +kaburagi kaede +saizo (fire emblem) +lancelot (fate/zero) +axl low +sannomiya shiho +mysterious heroine xx (fate) +azula +abra +alice (sinoalice) +flour (cookie) +servine +juzumaru tsunetsugu +junko (touhou) +error musume +oma kokichi +tendou maya +tamaki kotatsu +akebi komichi +tsurumi tokushirou +dabi (boku no hero academia) +helios (sailor moon) +unohana retsu +milotic +mem-mem (precure) +gyro zeppeli +fumi (nijisanji) +intrepid (azur lane) +raiden shogun +rokudou mukuro +king (nadia) +ikusaba daisuke +leila malcal +melantha (arknights) +taira no kagekiyo (fate) +wu zetian (first ascension) (fate) +fu xuan (honkai: star rail) +atago (summer march) (azur lane) +ssrb (shishiro botan) +medusa (rider) (third ascension) (fate) +lunatone +irisviel von einzbern (angel's song) +amiya (arknights) +yoisaki kanade +tokitou muichirou +arihara nanami +asashimo kai ni (kancolle) +abigail williams (swimsuit foreigner) (second ascension) (fate) +maru-yu (kancolle) +hanazawa teruki +oyama mahiro +lillipup +himesaka noa +shameimaru aya (crow) +oohoshi awai +vienna (vtuber) +hots (gundam suisei no majo) +oumae kumiko +hinokuma ran +shirayuki hime +hinase (cookie) +tachibana hinano (vtuber) +shirayuki tomoe +tailblue +schwertkreuz +kotegawa yui +riki (xenoblade) +amaha masane +protagonist (tokimemo gs3) +satake minako +kagura suzu (.live) +lilka eleniak +ninomae ina'nis (1st costume) +saitama (one-punch man) +tendou nabiki +ayanami (niconico) (azur lane) +cure march +malariya +ots-14 (sangria succulent) (girls' frontline) +yunomiya agari +sentret +amemori sayo +muk +shirasaka koume +tamamo cross (umamusume) +shoukaku kai ni (kancolle) +clara (girls und panzer) +meloetta +laplace (nikke) +aono miki +vinsmoke reiju +franky franklin +uzui tengen +itadori yuuji +raimon natsumi +shounan (kancolle) +bepo +sabi (pokemon) +scathach (swimsuit assassin) (fate) +elira pendora (1st costume) +cherrim +sommie (fire emblem) +katy (pokemon) +red (pokemon) +rimuru tempest (slime) +binah (project moon) +rozalin +hadou nejire +w (arknights) +kirino ranmaru +kaiboukan no. 30 (kancolle) +helel ben shalem +witch mercy +tomotake yoshino +nakiri ayame (5th costume) +linkle +la+ darknesss (1st costume) +toxtricity +gojou satoru +baymax +bea (pokemon) +mine (blue archive) +gwen stacy +kuronami (lvi) +nero claudius (fate/extra) +regu (made in abyss) +uzuki kou +flint (girls und panzer) +rika (touhou) +ultimecia +kongou (aoki hagane no arpeggio) +melusine (ibuki notsu) +haiyi +aino heart +hastur (nyaruko-san) +blanc (nikke) +suou patra +lich (monster girl encyclopedia) +paracelsus +watson amelia (3rd costume) +mori calliope (6th costume) +meiko (vocaloid) +leviathan (mega man) +shirakami fubuki (6th costume) +elbing (azur lane) +add (fate) +momoi (blue archive) +inohara masato +briar (league of legends) +duca degli abruzzi (azur lane) +log-mi (tonpuu) +gohan beast +james moriarty (archer) (fate) +camilla (fire emblem) +saegusa wakaba +pina korata +izumi konata +howl (howl no ugoku shiro) +hoshina utau +kayamori ruka +yukari (blue archive) +utane uta +florence nightingale (santa) (fate) +shia flatpaddy +minea (dq4) +tamamo no mae (swimsuit lancer) (second ascension) (fate) +tatsugiri (curly) +tongkkangi (streamer) +hk416 (girls' frontline) +yae miko (fox) +fujimaru ritsuka (female) (decisive battle chaldea uniform) +minion 1 (zannen onna-kanbu black general-san) +karin (naruto) +brown bear (kemono friends) +hijikata-san (m.m) +arabian oryx (kemono friends) +horsea +kroos (arknights) +ansel (arknights) +daybit sem void +flandre scarlet +spark (pokemon) +honda sora +kanaria +vivi ornitier +bradamante (fate) +mirage farina jenius +shiro-chan (mignon) +adult neptune +mini-ikamusume +makima (chainsaw man) +kouzuki yuniko +wakui rumi +mint blancmanche +ibuki fuuko +elhaym van houten +cosmog +rei (pokemon) +alena (dq4) +minazuki kyouko +kaine (nier) +kathryne keyron +glameow +ingrid (capcom) +kamishiro rui +lavenza (persona 5) +ningguang (genshin impact) +heixiu +suomi (midsummer pixie) (girls' frontline) +poyoyo (nakiri ayame) +chihiro (blue archive) +suou momoko +trish (devil may cry) +jashin-chan +croagunk +corrin (female) (summer) (fire emblem) +shindou kei (ef) +misaki (blue archive) +wii fit trainer (female) +pollux (fate) +cure beauty +brendan (pokemon) +kiyama hiroto +snom +salamence +libeccio (kancolle) +yagi toshinori +mash kyrielight (swimsuit of perpetual summer) +cleopatra (fate) +takemaru (housamo) +star platinum +oginome ringo +vf-1a +happy meek (umamusume) +miyake shinobu +fjorm (fire emblem) +gladiia (arknights) +kotoka torahime +black knight (granblue fantasy) +celty sturluson +quagsire +fu hua (taixuan impression) +akechi gorou +aizen kunitoshi +shiina mahiru (otonari no tenshi-sama) +obstagoon +watatsuki no yorihime +hino akane (idolmaster) +hayase ruriko (yua) +nightmare (arknights) +ichikawa hinana +strelizia +shiroko terror (blue archive) +omegamon +hatsume mei +cyber punked wattson +kagayaki homare +dera mochimazzui +shi huang di (fate) +chacha (fate) +aoi (blue archive) +amiya (newsgirl) (arknights) +shanna (fire emblem) +wally (pokemon) +toki ayano +carnelian (shimmering dew) (arknights) +toshinou kyouko +hoopa +gray (fate) +hasegawa kobato +goodra +medb (swimsuit saber) (fate) +larcei (fire emblem) +gundula rall +lucifer (umineko) +isuzu kai ni (kancolle) +last order (toaru majutsu no index) +pixiv red +orihara mairu +yowane haku +ezo brown bear (kemono friends) +angel (kof) +ishida yamato +tani takeshi (character) +cinia pacifica +gabriel tenma white +nagatomi hasumi +nao (kuzuyu) +leander (azur lane) +gangut (azur lane) +kairi (kingdom hearts) +homura (senran kagura) +tachibana kanade +meira (touhou) +okazaki ushio +becky blackbell +princess (princess principal) +echidna (p&d) +fujimaru ritsuka (female) (chaldea combat uniform) +yuuma (renkin san-kyuu magical pokaan) +manaka ao +king (snk) +trung nhi (fate) +azuki (nekopara) +shione (niliu chahui) +beeswax (arknights) +choso (jujutsu kaisen) +kuroe shizuku +belfast (shopping with the head maid) (azur lane) +ylgr (fire emblem) +queen draco (fate) +minato tomoka +raven (honkai impact) +tachimukai yuuki +shibuya rin +yuzuki yukari (shizuku) +kuroki tsutomu +juni (street fighter) +yukihana lamy (1st costume) +lora (xenoblade) +dusclops +midna +iizunamaru megumu +lucy heartfilia +25-ji miku +yagami iori +yuzuriha (jigokuraku) +pola (seaside coincidence) (azur lane) +dark elven forest ranger +mamiya (kancolle) +zarya (overwatch) +yaten kou +sendou aichi +mitsugashira enoko +kozume kenma +deadpool +shauntal (pokemon) +yandere-chan (ramchi) +octoling +captain of the royal guard elina +shimizu kiyoko +renown (warship girls r) +ikuno dictus (umamusume) +soldier: 76 (overwatch) +ratna petit +pochita (chainsaw man) +mephistopheles (fate) +arceus +asakura yume +audrey burne +haruna kai ni (kancolle) +himekaidou hatate +iroha (blue archive) +felix argyle +mylene jenius +shiraori +super creek (chiffon ribbon mummy) (umamusume) +april (arknights) +izuna (blue archive) +yumeno uta +minoto +agnes digital (lovely jiangshi) (umamusume) +duramente (umamusume) +gum (jsr) +castor (fate) +mikumo guynemer +ohr (blue archive) +motoori shiro +aya brea +luluco +rokurou rangetsu +nodoka (blue archive) +katsuki yuuri +noshiro (azur lane) +eustass kid +fiona frost +cow girl (goblin slayer!) +daisy (dq) +luxio +larvitar +cappy (mario) +tashigi +oshida (girls und panzer) +ruggie bucchi +aquila (azur lane) +koborii (amaburi) +takemi tae +hayasaka ai +warabeda meijii +soga no tojiko +genocider shou +platinum the trinity +de ruyter (kancolle) +otogari adonis +rino (princess connect!) +iyami +shiroko (swimsuit) (blue archive) +hk416 (midnight evangelion) (girls' frontline) +cyan (show by rock!!) +scavenger (arknights) +akamatsu kaede +janus (azur lane) +mars (pokemon) +candela (pokemon) +vf-25 +hiita the fire charmer +drake (nikke) +otosaka yuu +naganami kai ni (kancolle) +chiester45 +mayl sakurai (mega man) +buratei marii +natu +merlin prismriver +gokotai +alchemist (girls' frontline) +glados +nero claudius (bride) (second ascension) (fate) +meliodas +cheetah (kemono friends) +black leopard (kemono friends) +sobble +nosepass +murasaki shikibu (swimsuit rider) (fate) +elpeo puru +suzuka hime +jeanne d'arc alter (avenger) (first ascension) (fate) +oreki houtarou +gallade +bomb devil (chainsaw man) +yi sang (project moon) +musujime awaki +seolla schweizer +johnny (guilty gear) +paracelsus (fate) +bili girl 22 +araki nonoka (karaage bou) +kamiko kana +takayama haruka +barbara (genshin impact) +jervis (kancolle) +kaenbyou rin +ashiya douman (third ascension) (fate) +kukulkan (fate) +alucard (castlevania) +fujimaru ritsuka (female) (royal brand) +chibi miku +anise tatlin +theresa apocalypse (sakura rondo) +haguro (kancolle) +little girl admiral (kancolle) +mysta rias +izumi-no-kami kanesada +hacka doll 3 +kawakaze kai ni (kancolle) +cleveland (azur lane) +eusine (pokemon) +bisharp +akino (princess connect!) +astra militarum +hatsuyuki (kancolle) +carmilla (swimsuit rider) (third ascension) (fate) +sarah bryant +nakatani iku +yachi hitoka +sawakita eiji +neon (nikke) +kingdra +perroccino (fuwamoco) +victini +yuni (yuni channel) +anis (nikke) +piyoko (uruha rushia) +japanese wolf (kemono friends) +ako (blue archive) +iron tager +bowser jr. +yozakura (senran kagura) +princess bonnibel bubblegum +pyra (pro swimmer) (xenoblade) +hyakuya yuuichirou +diamond (houseki no kuni) +kawajiri kosaku +ninomae ina'nis (new year) +knight (dungeon and fighter) +mary stuart +popoi +bell cranel +akiyama hayato +arunira +theodore riddle +miyu (swimsuit) (blue archive) +suride miku +takamura yui +wolf spirit (touhou) +rotom +phoenix wright +kudou fuyuka +celica (fire emblem) +fushimi gaku +gunner (sekaiju) +ebina nana +ametsukana yago +kaga nazuna +destroyer water oni +asutora-chan +shiba miyuki +sig mcx (girls' frontline) +goomba +pride (fma) +kujaku mai +ilyana (fire emblem) +petra gurin +half slime-chan +falling devil (chainsaw man) +giant pangolin (kemono friends) +cellval +kanade izuru +chingling +ike eveland +twice (boku no hero academia) +alolan raichu +naga the serpent +twisted fate +minegumo (kancolle) +dragon slayer ornstein +murakami (girls und panzer) +morgan le fay (water princess) (fate) +quanxi (chainsaw man) +tashkent (kancolle) +sniper (ragnarok online) +chaika trabant +iron princess ymir +arakune +miria marigold mackenzie +stan marsh +zima (arknights) +aleksandra i. pokryshkin +ace trappola +rosa (masterpiece) (arknights) +fusou kai ni (kancolle) +ceres fauna (jirai kei) +thomas edison (fate) +naruko (naruto) +espeon +shikibe mayu +angol mois +murasaki shion (5th costume) +laura matsuda +abukuma (kancolle) +belphegor (umineko) +pola (kancolle) +ahsoka tano +sakamata chloe (new year) +makidera kaede +kaidou minami +sonohara anri +kishinami hakuno (male) +wakaba (kancolle) +takao (beach rhapsody) (azur lane) +kasodani kyouko +shego +hinatsuru ai +ebi frion (natsuiro matsuri) +abomasnow +north carolina (the heart's desire) (azur lane) +creator (ragnarok online) +barbatos (genshin impact) +jin musou +foch (azur lane) +kishido temma +zeong +mejiro haruhiko +shroomish +akali +can (honkai impact) +nyarlathotep (nyaruko-san) +ursula (fire emblem) +diola (granblue fantasy) +aia amare +contender (girls' frontline) +rapunzel (nikke) +bruno (pokemon) +helena kai (kancolle) +rin (inuyasha) +inami mahiru +ots-12 (girls' frontline) +ryuu (breath of fire v) +chihaya anon +hinazuki kayo +thresh (league of legends) +doudanuki masakuni +alice (nikke) +nia (xenoblade) +mutsu kai ni (kancolle) +guncannon +argalia (project moon) +vi (league of legends) +mdr (girls' frontline) +nagae iku +run elsie jewelria +kuon (utawarerumono) +sakurada jun +shuu (inazuma eleven) +lucifer (helltaker) +arung samudra (cessa) +taurus mask +azusa (blue archive) +ovelia atkascha +pokemon ranger (pokemon) +suzuka gozen (fate) +unicorn (azur lane) +murata himeko (battle storm) +psychic miku (project voltage) +oikawa shizuku +neo universe (umamusume) +priscilla barielle +salome (one piece) +darumaka +dan heng (imbibitor lunae) (honkai: star rail) +saileach (arknights) +mary (ib) +saotome alto +hakurei reimu +remy (elsword) +otonashi kiruko +tsukimi eiko +gravel (arknights) +oda nobunaga (sengoku collection) +inari one (umamusume) +kurumi (lycoris recoil) +chai xianghua +hasegawa chisame +greedent +slavya-chan +leia rolando +little mac +tamura yuri +reed the flame shadow (arknights) +len (tsukihime) +milly ashford +ganmo (takane lui) +reno (ff7) +hua cheng +cure scarlet +otonashi kotori +doremy sweet +yukari (princess connect!) +souseiseki +adol christin +otosaka ayumi +yamada elf +hachijou (kancolle) +kurumi momoka +nero claudius (bride) (third ascension) (fate) +thorfinn +android 17 +tanaka mako +osakabehime (swimsuit archer) (fate) +suzuya kai ni (kancolle) +lio fotia +chun-li +froakie +himawari-san (character) +youngster (pokemon) +pig (kemono friends) +tiffa adill +matsu (kancolle) +eiscue (ice) +hilbert (pokemon) +alolan exeggutor +noelle (kfc) (genshin impact) +popuko +cure precious +lordgenome +xp-tan +doujima ryoutarou +emile elman +sekishiro mico +anne (shingeki no bahamut) +medb (swimsuit saber) (second ascension) (fate) +joltik +lavolpe (yagisaka seto) +saegusa kii +cecil harvey +monster hunter (character) +sakine meiko +harpie lady +sumeragi shion +kiryuuin satsuki +sento isuzu +kagami taiga +bb (fate) +suzuna (princess connect!) +hakui koyori (summer) +buzzwole +tiese schtrinen +ibuki douji (second ascension) (fate) +south dakota kai (kancolle) +chii aruel +retoree (show by rock!!) +raiden mei (lightning empress) +northern water princess +kamiya nao +lilith (machikado mazoku) +tsukino mito +ookanehira (touken ranbu) +leonard mitchell +henriette mystere +saionji sekai +mai (pokemon) +medicine melancholy +silvally +kurosawa dia +intrepid (kancolle) +bokukawauso +roadhog (overwatch) +togepi +felix hugo fraldarius +yakumo yukari (young) +princess athena +lifeline (apex legends) +sunny gurlukovich +zangyaku-san +astesia (arknights) +amy sorel +unryuu (kancolle) +leon (pokemon) +millia rage +matsuno osomatsu +plymouth (azur lane) +rapidash +yunaka (fire emblem) +fu hua +nargacuga +nakano nino +nino (arakawa) +nishikitaitei-chan +mankanshoku sukuyo +bismarck (warship girls r) +franka (arknights) +asta (black clover) +amon (lord of the mysteries) +pidgey +emma august +ushiromiya natsuhi +mysterious heroine x alter (fate) +m14 (girls' frontline) +florence nightingale (third ascension) (fate) +jiji (majo no takkyuubin) +inuzuka kiba +himemiya chikane +wynaut +oboro (taimanin asagi) +mei (overwatch) +kibutsuji muzan +alina (girls und panzer) +aoi (princess connect!) +reiga mieru +kasukabe tsumugi +wobbuffet +pola (azur lane) +ayanami kai ni (kancolle) +lumen (arknights) +elysia de lute ima +armored aircraft carrier oni +nero claudius (modern costume of crimson) (fate) +caenis (swimsuit rider) (fate) +knight (hollow knight) +marinette dupain-cheng +chinatsu (blue archive) +rakka (haibane) +octoling boy +sasaki kanna (kaedeko) +drowzee +shiraishi tsumugi +kumacy +mikko (girls und panzer) +miyafuji yoshika +yusa (angel beats!) +schezo wegey +makoto (princess connect!) +rachel alucard +charlotte corday (swimsuit caster) (fate) +is (kamen rider 01) +fuurin asumi +prussia (hetalia) +reinhardtzar +rama (fate) +hori kyouko +riliane lucifen d'autriche +chevalier d'eon (fate) +sakaki yuuya +aug (girls' frontline) +black cat (marvel) +nanami lucia +handa roco +kinomoto sakura +mercedes (odin sphere) +yubel +revolver ocelot +ayasaki hayate +kreide (arknights) +maruyama aya +batsubyou +shennong (housamo) +mononobe alice (1st costume) +faust (guilty gear) +bidoof +haru urara (umamusume) +yomi (senran kagura) +kazusa (blue archive) +lutecia alpine +seo yuzuki +prompto argentum +stalker (ragnarok online) +emerald (pokemon) +ryu lion +yui (ceremonial) (princess connect!) +enomoto takane +eclair (kiddy grade) +horizon ariadust +emori miku +shenron (dragon ball) +meowstic (female) +onodera karen +souryuu (kancolle) +mifune shioriko +rosemi lovelock (1st costume) +juufuutei raden +melon-chan +jasmine (disney) +misono karin +taillow +kohinata miku +minato aqua +tokino sora (casual) +eremes guile +barghest (fate) +urameshi yusuke +northampton (kancolle) +prince of lorasia +kousaka kyousuke +ecclesia (yu-gi-oh!) +helena blavatsky (fate) +miyauchi renge +sagisawa fumika +toxtricity (amped) +kuromine chiharu +sigilyph +mika (genshin impact) +ichika (blue archive) +gardenia (pokemon) +elizabeth bathory (first ascension) (fate) +berserker (fate/zero) +tenshinhan +ishimaru kiyotaka +sailor uranus +azumarill +satou sei +ado (utaite) +ogata chieri +manya (dq4) +star guardian soraka +oleana (pokemon) +uesaka sumire +madoka aguri +krabby +tearju lunatique +hiiragi tsukasa +elbe (azur lane) +mage (7th dragon) +mg4 (girls' frontline) +christie (doa) +rindou mikoto +suzuran (arknights) +misumi nagisa +phoenix (tales) +vanillite +beelzebub (helltaker) +lisbeth (sao) +oomiya shinobu +aozaki touko +hiryuu (kancolle) +peach maki +arven (pokemon) +chimecho +lune zoldark +sirius (azur lane) +prosciutto +akai haato (1st costume) +ibaraki douji (swimsuit lancer) (fate) +cure peach +tsugumi (guilty crown) +otogibara era +talulah (arknights) +goh (pokemon) +ae-3803 +tokisaki asaba +bullet bill +sakata gintoki +nerine +wattson (apex legends) +tomoe mami +yamai yuzuru +normad +poi (last origin) +pokobee +firelight ekko +shimakaze-kun +hecatia lapislazuli (earth) +strength (black rock shooter) +brown kemomimi-chan (krr) +nagara (azur lane) +le triomphant (azur lane) +petelgeuse romaneeconti +super sailor mars +voltorb +paya (zelda) +patricia martin +marie antoinette (alter) (fate) +volo (pokemon) +hishi amazon (umamusume) +gengetsu (touhou) +shidou hikaru +jean bart (private apres midi) (azur lane) +natasha (sekai seifuku) +akaza akane +chikage (sister princess) +lime (saber j) +aozaki aoko +robin (fire emblem) +whispy woods +konoha (kagerou project) +edomae lunar +revy (black lagoon) +tonelico (first ascension) (fate) +god gundam +io (pso2) +sui-feng +suzuya (azur lane) +tsumugi (princess connect!) +akashi (kancolle) +uzumaki boruto +fumizuki (azur lane) +bellezza felutia +tateyama ayano +andou (girls und panzer) +princess leia organa solo +inaba tewi +bug miku (project voltage) +suzuki hana +iseya shiki +eunectes (forgemaster) (arknights) +otokura yuuki +sakasaki natsume +zacian (crowned) +shimotsuki potofu +inia sestina +cloud strife +tapu koko +2b (nier:automata) +princess of moonbrook +lelouch vi britannia +talho yuuki +cherrim (sunshine) +elena trafalgar +jindai komaki +nanase riku +terazaki kaoru +billy russell (dickfish) +rozaliya olenyeva +ibaraki douji (fate) +warspite (azur lane) +japanese crested ibis (kemono friends) +galo thymos +vladilena millize +mickey krog +kawasumi mai +kotama (blue archive) +shirakawa nanaka +azuma tokaku +clodsire +mega absol +galarian zigzagoon +nessie (respawn) +kaeya (genshin impact) +brown long-eared bat (kemono friends) +yamada tae +luka (mon-musu quest!) +weapon merchant cattleya +chou-10cm-hou-chan (teruzuki's) +focalors (genshin impact) +archer (pokemon) +viola (majo no ie) +rabirin (precure) +mega mawile +mori calliope (streetwear) +parasoul (skullgirls) +lexington (cv-16) (warship girls r) +ushiromiya krauss +nakayama festa (umamusume) +monarch (azur lane) +gwen (league of legends) +minion 2 (zannen onna-kanbu black general-san) +theresa apocalypse (celestial hymn) +ryugasaki rene (3rd costume) +hori masayuki +shizune (naruto) +shin'you (kancolle) +oikura sodachi +akita toushirou +eraser head (boku no hero academia) +grim aloe +la+ darknesss +taki (soulcalibur) +queen of hatred +kagerou kai ni (kancolle) +dog (mixed breed) (kemono friends) +rurudo lion +tatsuta (kancolle) +yamato (one piece) +hatterene +nakatsu shizuru +bartholomew roberts (fate) +davi (dokidoki! precure) +pixiv-tan +code: empress (elsword) +anita king +deepcolor (arknights) +satono diamond (umamusume) +tomoe gozen (traveling outfit) (fate) +kirito +ebi-chan (gawr gura) +todoroki yachiyo +zamazenta +papyrus (undertale) +shiranui flare (old design) +toudou itsumi +morty (pokemon) +leon s. kennedy +amagi-chan (azur lane) +kitazawa hagumi +jouga maya +beta (inazuma eleven) +yamato (kancolle) +calne ca +bismarck (beacon of the iron blood) (azur lane) +white serval (kemono friends) +aikawa (dorohedoro) +zakuro (rariatto) +v (devil may cry) +galaktika +lu xueqi (zhu xian) +morpeko (full) +megaman.exe +patricia thompson +lumineon +enma-chan +jessica (granblue fantasy) +poppi (xenoblade) +canti +kisume +fujimaru ritsuka (male) (tropical summer) +kaonashi +yua serufu +fia the deathbed companion +azami (kagerou project) +andira (summer) (granblue fantasy) +timerra (fire emblem) +kaneki ken +pod (nier:automata) +raspberyl +namikaze minato +watanabe no tsuna (fate) +tojo kirumi +svd (girls' frontline) +shiki (yuureidoushi (yuurei6214)) +meursault (project moon) +kokonoe (blazblue) +katagiri himeko +kotoura haruka +emily stewart +himura kenshin +moriya suwako +hatsuharu (kancolle) +alleyne (queen's blade) +teto (nausicaa) +kazama souta +klonoa +starfire +chris (konosuba) +cacnea +inner moka +magallan (arknights) +gogo tomago +nakano yotsuba +falco lombardi +irie miyuki +kurumi noah +tamaki ui +super sailor moon +sun wukong +oryou (girls und panzer) +francis drake (fate) +ako (track) (blue archive) +elesis (elsword) +cirno-nee +himejima akeno +mogami (kancolle) +ushio (kancolle) +kamiki mirai +chris redfield +ninetales +minori (senran kagura) +sona (league of legends) +carcano m91/38 (girls' frontline) +goldenglow (arknights) +nikaidou shinku +akershus fortress (oshiro project) +kanzaki hideri +alice cartelet +shiomi syuko +robin (arknights) +shidare hotaru +casca (berserk) +yamakaze (kancolle) +bradamante (third ascension) (fate) +hibiki (cheer squad) (blue archive) +kokutou mikiya +glorious (azur lane) +nakano itsuki +gothitelle +bat-eared fox (kemono friends) +ana (overwatch) +yamashiro (azur lane) +april o'neil +gepard landau +project bunny +south dakota (azur lane) +mugino shizuri +aizawa yuuichi +aegir (iron blood's dragon maid) (azur lane) +edelgard von hresvelg +chouun +tobiichi origami +achilles (fate) +arsene lupin iii +kaburagi t. kotetsu +suo sango (1st costume) +angelise reiter +gloom (pokemon) +ninja (final fantasy) +weavile +sousou +lunasa prismriver +rodney (warship girls r) +ereshkigal (third ascension) (fate) +ganaha hibiki +akiyama mio +takao (full throttle charmer) (azur lane) +hermit purple +hojo karen +sadamori himeka +tapris chisaki sugarbell +matangomu-chan +jumpy dumpty +nanashi mumei (1st costume) +kinugawa sana +hanazono shizuma +ribbon (kirby) +female seth (street fighter) +miyamori aoi +alcremie +nyotengu +nanashi mumei +sabo (one piece) +usada pekora +debidebi debiru +nijimura okuyasu +kawakaze (azur lane) +penny (pokemon) +nagare ryoma +mirai (kemono friends) +raiden (metal gear) +zhongli (archon) (genshin impact) +wamuu +drew (pokemon) +albacore (azur lane) +huohuo (honkai: star rail) +koharu (swimsuit) (blue archive) +african penguin (kemono friends) +astolfo (fate) +tanned cirno +bangalore (apex legends) +enraku tsubakura +sunao (wokada) +shitodo aoji +touhoku itako +balrog (street fighter) +protagonist (crave saga) +astolfo (saber) (fate) +anila (summer) (granblue fantasy) +kama (second ascension) (fate) +mayu (vocaloid) +masaki aeka jurai +sailor mars +momiji (binbougami ga!) +yahagi kai ni (kancolle) +octoling player character +racing miku (2022) +team magma grunt +moeta kaoruko +blackbuck (kemono friends) +nagato yuuki +female titan +shamu meruruusa +henya the genius +kasugano urara (yes! precure 5) +hibiki (blue archive) +grookey +ro635 (mod3) (girls' frontline) +yuuki (sao) +nephenee (fire emblem) +atsuko (blue archive) +schwarz (arknights) +schneider (reverse:1999) +sakishima hikari +emil castagnier +shirayuki (kancolle) +egawa kusumi +darjeeling (girls und panzer) +yumeko (touhou) +kurapika +hisuian decidueye +kurosaki kazui +raichu +girl (mokufuu) +takagi (tansuke) +kurumi (touhou) +cyril brooklyn +cinderella (sinoalice) +matoi (pso2) +craft lawrence +lead white (tsurunoka) +symonne (tales) +nago +t-head trainer +yuusha (maoyuu) +donkey (kemono friends) +lovely labrynth of the silver castle +aizawa sakuya +meltryllis (swimsuit lancer) (fate) +tracer (overwatch) +kobo kanaeru (1st costume) +hidaka ai +gotoh hitori +erio mondial +protagonist 4 (housamo) +hanazono yurine +yamanobe tomo +mythra (radiant beach) (xenoblade) +kindred (league of legends) +chiba mamoru +ichikawa kyoutarou +penpen +saitou hajime (fate) +tajikarao (housamo) +altera the santa (fate) +adventurer (ff11) +mother (yoru mac) +razia +syndra +ana coppola +hiei (azur lane) +abigail williams (swimsuit foreigner) (first ascension) (fate) +marx (kirby) +banana (girls' frontline) +asch (tales) +semiramis (fate) +hachimiya meguru +grani (arknights) +izumi noa +seliph (fire emblem) +kokubunji suou +harle (chrono cross) +tamamo no mae (swimsuit lancer) (third ascension) (fate) +medusa (lancer) (fate) +yukinoshita yukino +qubeley +c.c. lemon (character) +torrent (elden ring) +sakuramori kaori +escha malier +hoshino ai (oshi no ko) +queen of sunlight gwynevere +queen of hearts (alice in wonderland) +gm (mobile suit) +cure flamingo +xion (kingdom hearts) +sinbad (magi) +nikaidou chizuru +cairngorm (houseki no kuni) +godzilla (monsterverse) +lunch (dragon ball) +vivid bad squad miku +yumehara nozomi +golem (pokemon) +ashiya douman (second ascension) (fate) +rio (blue archive) +black heart (neptunia) +racing miku +mian (dream c club) +kaga (azur lane) +saki (ar tonelico) +etna (disgaea) +hina misora +narusegawa naru +lagann +tippy (gochiusa) +anko (gochiusa) +sasaki saku +gino weinberg +li'l judd (splatoon) +hirasawa shizuku +trance terra branford +angela (project moon) +ifrit (arknights) +feiqizi (fkey) +yoshi +tseng +vigna (casual vacation) (arknights) +springfield (girls' frontline) +tequila (arknights) +hiburi (kancolle) +abyss mage (genshin impact) +ein (cowboy bebop) +master (vocaloid) +kidou yuuto +okkotsu yuuta +dobermann (arknights) +inkling boy +murasaki shion +nenohi (kancolle) +sato shin +zatsune miku +rita mordio +hermes (kino no tabi) +vergil (devil may cry) +mini nobu (fate) +destroyer (girls' frontline) +reina prowler +adachi rei +kuroshio (kancolle) +khezu +linne +reno (reno bunnino) (azur lane) +ivysaur +hakurei reimu (pc-98) +illustrious (muse) (azur lane) +kuonji alice +rin (yu-gi-oh!) +cure yum-yum +larva tiamat (fate) +pon de lion +skorpion (girls' frontline) +priestess (goblin slayer!) +wakiyama tamami +nora cat +multi (to heart) +long island (azur lane) +andy bogard +sabrith ebonclaw +oerba yun fang +azusagawa sakuta +makoto (minami-ke) +gaia (ff14) +tachikawa mimi +shiina yuika +shirahoshi +tanaka hime +cernunnos (fate) +yuki miku +alicia florence +jenny wakeman +naoe riki +klara (pokemon) +harime nui +tayelle ebonclaw +narciso anasui +amagi kai (kancolle) +m4 sopmod ii (girls' frontline) +indigo (arknights) +fubukihime +mitty (made in abyss) +ichinose tokiya +seto kousuke +nandaba naota +tokitarou (fate) +volley-bu-chan (tawawa) +hol horse +kobayakawa rinko +arjuna alter (fate) +natalia (idolmaster) +reimu endou +inamori mika +leonhardt (arknights) +princess zelda +himegami aisa +armored titan +groudon +peter parker +laura bodewig +matou kariya +fujiwara no iyozane +swire (arknights) +aoi yusuke +kamihira mao +asahina hiyori +dark sun gwyndolin +ingo (pokemon) +zoe (league of legends) +seele vollerei (herrscher of rebirth) +female mage (dungeon and fighter) +kumashiro maya +arcanine +tsuchiya ako +evangelyne (wakfu) +raihan (pokemon) +yoshii akihisa +august von parseval (azur lane) +twilight sparkle +bellossom +himekawa (shashaki) +leone abbacchio +kakura kurumi +unzen (azur lane) +gertrud barkhorn +tomoe gozen (swimsuit saber) (first ascension) (fate) +eliwood (fire emblem) +ittoki otoya +shiitake (love live! sunshine!!) +shameimaru aya (newsboy) +justice (helltaker) +matsuura nanase +suiseiseki +kanbara satomi +asakaze (kancolle) +shanks (one piece) +graf zeppelin (beachside urd) (azur lane) +chibita +triandra (fire emblem) +kusakabe mei +texas (winter messenger) (arknights) +gasai yuno +ak-12 (girls' frontline) +leviathan (skullgirls) +matsukaze (kancolle) +joseph joestar (old) +vox akuma +oda nobunaga (swimsuit berserker) (second ascension) (fate) +yakumo beni +liv (punishing: gray raven) +giant armadillo (kemono friends) +yuki miku (2012) +she-hulk +doduo +elma (maidragon) +kagamine rin (roshin yuukai/hard rkmix) +frostnova (arknights) +hatakaze (kancolle) +ichihara nina +nishikata chii +magearna +nocchi (perfume) +shinonome akito +shantae +suzuhara lulu +aubrey (headspace) (omori) +coomer (meme) +kronie (ouro kronii) +asia argento +zuikaku kai ni (kancolle) +hijikata toushirou +nanika (azumi inori) +hassel (pokemon) +renata alekseevna tsvetaeva +kirigiri kyoko +kiryuu moeka +roxie (pokemon) +lord knight (ragnarok online) +hayase mitsuki +mega gardevoir +meracle chamlotte +mimikyu +kanna (cookie) +pyramid head +flower (vocaloid4) +mercy (overwatch) +hizamaru (touken ranbu) +g28 (girls' frontline) +oboro (kancolle) +jason todd +king halo (umamusume) +sanageyama uzu +topaz (honkai: star rail) +mulberry (arknights) +sagimori arata +wa2000 (date in the snow) (girls' frontline) +kuroba kaito +maho (princess connect!) +yamper +black cellien (kemono friends) +marie antoinette (fate) +helma lennartz +mononobe no futo +shinomiya himawari +meltryllis (swimsuit lancer) (third ascension) (fate) +koyanskaya (assassin) (first ascension) (fate) +illustrious (morning star of love and hope) (azur lane) +morpeko +fairy leviathan (mega man) +kogure ruka +hk416 (black kitty's gift) (girls' frontline) +asai miki +kukuri (mawaru) +inugami korone +yu mei-ren (fate) +heather mason +mikisugi aikurou +shiho (yuuhagi (amaretto-no-natsu)) +anosillus ii +tohno akiha +seraphina (disgaea) +yamabushi kunihiro +i-19 (azur lane) +ozen +rengoku (fate) +klin (girls' frontline) +pyra (xenoblade) +miyamoto musashi (fate) +nezha (fate) +peri (fire emblem) +inuyasha (character) +lakitu +christa renz +federica n. doglio +king (one-punch man) +choukai kai ni (kancolle) +yagoo +edward newgate +nanobana kinako +nishi kinuyo +teana lanster +onix +miles morales +owain (fire emblem) +fubuki (azur lane) +nijou aki +noelle silva +ende (chihuri) +jinako carigiri +lucario +senshi (dungeon meshi) +live twin ki-sikil +kageyama shigeo +kiawe (pokemon) +gramophone miku +kazami youka (yokochu) +pix (league of legends) +air defense princess +finneon +pronghorn (kemono friends) +alicia melchiott +kuraue hinata +greavard +kuzuryu fuyuhiko +zeta (summer) (granblue fantasy) +akai shuuichi +mimi-chan +reed (arknights) +harmonie (arknights) +ooto ai +momoi airi +kikuchi makoto +jum-p +moira (nijisanji) +nearl the radiant knight (arknights) +suzumura sango +cyndaquil +hizaki gamma +tokoyami fumikage +graves (league of legends) +kai'sa +mysterious heroine x (fate) +watabe koharu +kokkoro (new year) (princess connect!) +iida tenya +amane kanata (nurse) +antarcticite +producer (idolmaster cinderella girls anime) +anis (sparkling summer) (nikke) +valkyrie police academy student (blue archive) +jingwei (bird) +mizuki (arknights) +sawatari izumi +diego brando +sango (inuyasha) +sunohara mei +kuroe (madoka magica) +fu hua (azure empyrea) +senpai-san (douki-chan) +honchkrow +wendy marvell +yuzuki yukari (vocaloid4) +m.o.m.o. +tauros +space ishtar (third ascension) (fate) +red pyro (tf2) +malayan tapir (kemono friends) +deemo (character) +emily stock +perrin (pokemon) +kinomoto touya +kuromiya raika +drossel von flugel +natsume kyousuke +ninomae ina'nis (2nd costume) +veronica (dq11) +makoto (genshin impact) +shinguji korekiyo +hiyama kiyoteru +anetai toyone +tsunemori akane +bhima (fate) +iri flina +khiara (personal ami) +mikazuki augus +combat medic ziegler +futaba aoi (vividred operation) +griffith (berserk) +eimi (swimsuit) (blue archive) +yagen toushirou +dehya (genshin impact) +sig (puyopuyo) +atou haruki +exiled warrior leina +s-head commander +hoshimachi suisei (1st costume) +nidalee +kagari atsuko +mizushima saki +skadi (elite ii) (arknights) +white rhinoceros (kemono friends) +hanazono tae +daring tact (umamusume) +tsukahara hibiki +senji muramasa (fate) +pram (phantom kingdom) +akitsu maru kai (kancolle) +sakawa (kancolle) +kurata mashiro +usa mimi +cu chulainn (fate/prototype) +rudy (ikeuchi tanuma) +c-ms (girls' frontline) +porno (dohna dohna) +mikono suzushiro +female fighter (morino donguri) +kiana kaslana (herrscher of the void) +kaname tatsuya +suzuki rion +heles (summer) (granblue fantasy) +kannabi no mikoto +zed (league of legends) +joshua bright +priest (dq3) +morino ichigo +kusunoki midori +ibuki maya +ja'far (magi) +oora kanako +horaki hikari +ulrich von hutten (azur lane) +yui (sao) +momozono love +hanada kirame +inui sajuna +mitsuki felicia +houshou marine (3rd costume) +ryuubi +valkyrie (fate) +aang +fukube satoshi +leilan (p&d) +vsk-94 (girls' frontline) +charlotte katakuri +alm (fire emblem) +master nemesis +symboli kris s (umamusume) +morishima haruka +guizhong (genshin impact) +kurusu kimihito +tora tentei +cinnamon (nekopara) +scaramouche (genshin impact) +arcane vi +mano aloe +onozuka komachi +snorunt +goomy +tamamo no mae (swimsuit lancer) (fate) +sajo yukimi +wu zetian (fate) +bili girl 33 +siberian chipmunk (kemono friends) +celesteela +zinogre +ruukoto +alicia renato (yashiro sousaku) +alice (queen's gate) +kaneshiya sitara +kirishima romin +seelie (genshin impact) +five-seven (girls' frontline) +sanya (kuzuha) +flannery (pokemon) +jester (dq3) +krul tepes +takayama sayoko +matsuwa (kancolle) +iwatani naofumi +spyke (splatoon) +9s (nier:automata) +sephiroth +absinthe (arknights) +hallessena +rosa farrell +nakatsukasa tsubaki +princess king boo +star guardian ahri +scarecrow (girls' frontline) +iron man +implacable (shepherd of the "lost") (azur lane) +saionji reimi +ayase arisa +rukkhadevata (genshin impact) +bibi (tokoyami towa) +date masamune (sengoku basara) +takamaki anne +shinano toushirou +yune (fire emblem) +kiryu coco +cindy aurum +sakurai aoi +luna (shadowverse) +gager (girls' frontline) +emiya alter +kiyal bachika +yamato aki +vanilla ice +saber +tabris-xx +marisa (street fighter) +aiko (renkin san-kyuu magical pokaan) +yozora mel (1st costume) +alita +marco polo (azur lane) +manaka non +emil (nier) +fumino tamaki +suzuka gozen (swimsuit rider) (fate) +komichi aya +moa (show by rock!!) +van gogh (fate) +elster (signalis) +iino miko +fletchling +fiammetta (arknights) +chiya (urara meirochou) +rosa (pokemon) +serio (to heart) +viper (nikke) +leafa +nanasaki ai +k/da akali +nachoneko +jeanne d'arc alter (ver. shinjuku 1999) (fate) +giovanna (guilty gear) +texas (willpower) (arknights) +nier (young) +ciel alencon +giant otter (kemono friends) (kuro (kurojill)) +kusanagi kyou +ranma-chan +agumon +oomuro nadeshiko +orochi (fire emblem) +the world +archer (summer casual) (fate) +haruka karibu +hyuuga hanabi +sochie heim +amamiya hibiya +flint (pokemon) +fukuji mihoko +airi (blue archive) +arsene (persona 5) +sunohara youhei +mysterious ranmaru x (fate) +mihama chiyo's father +doraemon (character) +frankenstein's monster (swimsuit saber) (fate) +ingrid brandl galatea (summer) +watarase jun +cheese (sonic) +ogami tamaki +totoki airi +sceptile +clorinde (genshin impact) +fatina +ashido mina +acerola (pokemon) +metagross +chiester410 +sonic the hedgehog +inubashiri momiji +matsuyuki atsumu +ushizaki urumi +tama (kancolle) +tentacool +artem wing (tears of themis) +maki (camp) (blue archive) +mochizuki hijiri +senri akane +aida taketo +durga (fate) +kurohebi +sengoku shinobu +ranka lee +yaotome urushi +tiphereth a (project moon) +nymph (sora no otoshimono) +zubat +leopard (kemono friends) +hinomoto oniko +mococo abyssgard +karin (blue archive) +southern ocean war princess +mimura kanako +chrom (fire emblem) +beldum +koga norio +yagami light +edan (sparrowl) +frankenstein's monster (swimsuit saber) (first ascension) (fate) +igarashi kyou (eroe) +kadabra +98-tan +shouhou (kancolle) +fei rune +itsuwa +yamato-no-kami yasusada +wiglett +soldier (dq3) +hagoromo gitsune +kimura natsuki +lunafreya nox fleuret +chespin +hisako (angel beats!) +nekomusume +ishmael (project moon) +jaune arc +matsushima michiru +misako (kunio-kun) +wolf (league of legends) +timido cute +watanuki banri +katyusha (girls und panzer) +saruei (vtuber) +himemiya tori +grimer +skwovet +perseus (unfamiliar duties) (azur lane) +a2 (nier:automata) +pancham +nibutani shinka +valkyrie (p&d) +nun bora +yoshimi (blue archive) +kimberly jackson +nijigaoka mashiro +ooi kai ni (kancolle) +pieck finger +anya alstreim +rilliona (yu-gi-oh!) +woobat +cure butterfly +arare kai ni (kancolle) +rensouhou-chan +toudou shimako +kumano (azur lane) +lobo (fate) +noctowl +tarnished (elden ring) +usami renko +ashitaka +tanya degurechaff +aether foundation employee +hawawa-chan (shiro kuma shake) +sienna (henken) +akagi towa +hitmonchan +mr. squeaks (hakos baelz) +iwakura lain +thor (marvel) +reki (haibane) +mela (pokemon) +carmine (pokemon) +thunder (girls' frontline) +kale (dragon ball) +pichu +luis cammy +matsumi yuu +erebus (azur lane) +sinistea +dobrynya nikitich (second ascension) (fate) +princess hilda +amami haruka +jellicent +izutsumi +carbuncle (puyopuyo) +baiken +prinny +kaiou michiru +momomeno (7th dragon) +ortfine fredericka von eylstadt +teddiursa +leblanc (league of legends) +langley (kancolle) +orochimaru (naruto) +yuna (biya (1024)) +lenalee lee +projekt red (arknights) +thanatos (hades) +asteion +ethan (pokemon) +beatrix (granblue fantasy) +drizzile +regigigas +mass production eva +noire (neptunia) +sero hanta +chen +rydia (ff4) +bardiche (zanber form) +ksvk (girls' frontline) +miriel (fire emblem) +luma (mario) +fubuzilla (shirakami fubuki) +ninja (ragnarok online) +southern tamandua (kemono friends) +sakura laurel (umamusume) +coo (kirby) +porygon-z +goldenglow (maiden for the bright night) (arknights) +kuo shenlin + (blue archive) +mobius (honkai impact) +shaymin (land) +2k-tan +rpk-16 (renate) (girls' frontline) +tonami yuma +hina (genshin impact) +akazawa izumi +levi (shingeki no kyojin) +rulue (puyopuyo) +ashigara (kancolle) +anegasaki nene +takagi saya +sakuragi matsuri +kamishiro seren +juli (street fighter) +laura kinney +fate testarossa +matterhorn (arknights) +takamachi nanoha (aggressor mode) +bb (swimsuit mooncancer) (fate) +amatsukaze (kancolle) +shin (dorohedoro) +ezreal +rengar +lily white +kaito (vocaloid) +shinano (dreams of the hazy moon) (azur lane) +ardbert hylfyst +utsugi uyu +celine (fire emblem) +izumi koushirou +orie (under night in-birth) +waddle dee +ringorou (idolmaster) +saltwater crocodile (kemono friends) +hoshino hinata +morikubo nono +hasaha +nameless king +kurumi (blue archive) +kitchen dragonmaid +matsubara kaoru +leavanny +light hello (umamusume) +daiba nana +succubus (disgaea) +alessandra susu +mahito (jujutsu kaisen) +shimada chiyo +nao (mabinogi) +sheva alomar +balalaika (black lagoon) +mankanshoku barazou +silfa (to heart) +yae sakura (darkbolt jonin) +nini yuuna +wakabayashi tomoka +caren hortensia (amor caren) (third ascension) +sybilla +irisviel von einzbern +squigly (skullgirls) +xi gundam +kousetsu samonji +arashio (kancolle) +sophie (howl no ugoku shiro) +iowa (pacific) +yuezheng ling +sailor moon +moblit berner +fuiba fuyu +ashen one (dark souls 3) +nagatsuki sanae +io (princess connect!) +akiyama yukari +kohaku (tsukihime) +monomi (danganronpa) +indra (arknights) +slime (genshin impact) +morgan (fire emblem) +matoba risa +team plasma grunt +botan (yu yu hakusho) +jougasaki rika +aoi nagisa +kitajima kaede +tokoyami towa (5th costume) +gajeel redfox +kasane teto +yuki miku (2022) +kobuchizawa shirase +winona (pokemon) +zenyatta (overwatch) +kikyou (inuyasha) +surtr's golem (arknights) +merry nightmare +snowsant (arknights) +noel (sora no method) +froggy nun (diva) +sunday silence (racehorse) +itoshiki rin +kazari jun +astarotte ygvar +ooama no ake no mitori +double (skullgirls) +cursola +darkrai +shauna (pokemon) +arbok +kizuna akari (a.i. voice) +finn the human +commander shepard (female) +triela +phantom (happinesscharge precure!) +elio (pokemon) +shiina mayuri +minami rena +sakura (usashiro mani) +arisa (shadowverse) +yamano remon +metera (granblue fantasy) +fan la norne +frankenstein's monster (swimsuit saber) (second ascension) (fate) +grim (azur lane) +starly +shiina (angel beats!) +irene (kanniiepan) +callie (splatoon) +kamen rider kuuga +laevatein (fire emblem) +usano mimi +hakamichi shizune +anna (sennen sensou aigis) +inoue takina +t'au +cornea (asteroid ill) +atago hiroe +sada (pokemon) +fujimura taiga +sakurai shin'ichi +mofurun (mahou girls precure!) +captain america +serizawa asahi +claire harvey +m590 (girls' frontline) +ayla (punishing: gray raven) +ishikawa luna +stheno (third ascension) (fate) +tainaka ritsu +seel +team skull grunt +izumi kouhei +claudette (queen's blade) +necro (guilty gear) +aurora sya lis kaymin +kinugasa (kancolle) +scrafty +sailor venus +charmander +hamazura shiage +chen (cat) +lymle lemuri phi +cure macherie +articuno +manon legrand +flaaffy +sita vilosa +ichiyanagi yumihiko +girlfriend (houkago play) +kinuhata saiai +honolulu (kancolle) +robo (chrono trigger) +kishibe (chainsaw man) +izumi mei +kirlia +ortlinde (fate) +kirika towa alma +yashiro nene +lucy (elfen lied) +akizuki ritsuko +fraux +saionji kotoka +sengoku nadeko +fujimaru ritsuka (male) (polar chaldea uniform) +saionji mary +hinomori shizuku +index (toaru majutsu no index) +veyle (fire emblem) +kecleon +artoria pendragon (swimsuit archer) (first ascension) (fate) +claus (mother 3) +origami cyclone +li sushang +kain highwind +blue poison (arknights) +marie (girls und panzer) +nyubara reona +sakurauchi riko +gouf +kazuno sarah +chikuma (azur lane) +miyagawa takane +cherino (blue archive) +natsu megumi +draven +lois lane +okumura yukio +uchiha obito +lafiel +kasumigaoka utaha +sayla mass +shiratsuyu (kancolle) +ayachi nene +oribe tsubasa +kobayakawa sae +frederic chopin +chimchar +ellen baker +tamamo no mae (sable mage) (fate) +hoshi syoko +persica (girls' frontline) +diarmuid ua duibhne (lancer) (fate) +maria campbell +stephanie dora +hisui (tsukihime) +yae miko +integra hellsing +shiina kokomi +vivio +sazaki kaoruko +rakeru (dokidoki! precure) +yuugiri (zombie land saga) +yokoyama ishimi +kuzunoha raidou +andou tazusa +killy +sasaki ran +corrin (female) (adrift) (fire emblem) +nakaseko kaori +eirika (fire emblem) +nepgear +moe (blue archive) +gipsy danger +reiner braun +fiona belli +succubus (ragnarok online) +cornelia (umineko) +hozuki kaede +larva tiamat (third ascension) (fate) +add (elsword) +akechi kokoro +wander (shadow of the colossus) +amane kanata (3rd costume) +95-tan +seki hiromi +madotsuki +lidelle (puyopuyo) +minase inori +miyoshi sana +ico (character) +nishikino maki +kamen rider kabuto +lilith (yamibou) +chtholly nota seniorious +martha (swimsuit ruler) (second ascension) (fate) +guvava +mannosuke +meyrin hawke +aladdin (magi) +aegir (golden dragon among auspicious clouds) (azur lane) +natsuiro matsuri (1st costume) +hoshimachi suisei (school uniform) +brera sterne +miki sayaka +fletcher (kancolle) +plumeria (pokemon) +klaus von reinhertz +beatrice (re:zero) +togetic +mew (pokemon) +nanami touko +katsushika hokusai (traveling outfit) (fate) +tokonome mamori +wa2000 (girls' frontline) +backbeako +laphicet (tales) +jax (the amazing digital circus) +melina (elden ring) +sena airi +ophelia phamrsolone +oboro (fire emblem) +magical mirai miku +mary (nikke) +tropius +donkey kong +fir (fire emblem) +spartacus (fate) +commander (last origin) +chou-10cm-hou-chan (hatsuzuki's) +hikigaya komachi +shirase maki +ghislaine dedoldia +bismarck (kancolle) +yuzu (blue archive) +camerupt +hippopotamus (kemono friends) +johnston (kancolle) +hatsune miku (append) +vistake +ash crimson +feena (grandia) +neuro-sama +tails (sonic) +tamamo cat (first ascension) (fate) +senketsu +kamen rider ryuki +towa herschel +kougami shin'ya +hk416 (starry cocoon) (girls' frontline) +clefable +mitsuzuri ayako +latifa fleuranza +android 18 +carrot (one piece) +reinhardt (fire emblem) +kusanagi motoko +employee (project moon) +lachesis (fire emblem) +buttercup (ppg) +nakoruru +mountain (arknights) +kurosaki ruri +shiroko (reku) +roux louka +exeggutor +nian (arknights) +ange katrina (2nd costume) +ulrich von hutten (mayhem maid) (azur lane) +jacques de molay (foreigner) (fate) +kratos aurion +yuzuriha (under night in-birth) +glorious (pungent plum) (azur lane) +faris scherwiz +barok van zieks +reno (azur lane) +kobo kanaeru +hakos baelz +meredy (tales) +oscar francois de jarjayes +madame ping (genshin impact) +yamada ryo +ooba minori +kalina (girls' frontline) +belfast (azur lane) +aura (sousou no frieren) +maou beluzel +sivir +raising heart (accel mode) +kiriko (overwatch) +eyjafjalla (summer flower) (arknights) +tezuka rin +toyama kasumi +makkachin +akira (orenchi no maidosan) +ike eveland (1st costume) +makise kurisu +rice shower (umamusume) +dark hunter +ichijou seiya +professor (ragnarok online) +oosuki mamako +xiao (project moon) +dewott +makigumo (kancolle) +anna (frozen) +cofagrigus +trafalgar law +tiphereth b (project moon) +matsuoka gou +himeko (honkai: star rail) +mankanshoku mako +usuzumi hatsumi +anezaki mamori +lenneth valkyrie +kaenbyou rin (cat) +bsapricot (vtuber) +kuranami shiki +zhong lanzhu +wartortle +eclair martinozzi +tennouboshi uzume +houchou toushirou +jirai-chan (masayo) +guel jeturk +netzach (project moon) +zaku +lucie (millie parfait) +louise halevy +souryuu asuka langley +sneasel +zone-tan +samurott +gumi +fuura kafuka +saigusa haruka +kazuha's friend (genshin impact) +blaze (arknights) +orihime (tanabata) +pelipper +goldeen +matara okina +ciel phantomhive +ritsu (kemurikusa) +lexington (warship girls r) +katsushika hokusai (third ascension) (fate) +lana (hyrule warriors) +kongou (kancolle) +alina (arknights) +blonde dog girl (ri-net) +claire redfield +gogeta +minato yukina +houndoom +yamagumo (kancolle) +waha +rinpoo chuan +kamikoshi sorawo +murakumo kai ni (kancolle) +farfetch'd +constanze amalie von braunschbank-albrechtsberger +alpaca suri (kemono friends) +argo the rat +bullet (blazblue) +marshall d. teach +bison (arknights) +felicia (fire emblem) +sado (kancolle) +higuchi madoka +sasaki chie +konpaku youki +toudou shion +yukizome chisa +funami yui +so dakki +alisa boskonovich +ange katrina +akuma nihmune +watamate +gavial (arknights) +ozora akari +shigure ui (young) (vtuber) +kasuga mirai +jyushimatsu's girlfriend +vice (kof) +eliza (skullgirls) +koromon +barbara (summertime sparkle) (genshin impact) +sagiri (kancolle) +ookami ryouko +taiki shuttle (umamusume) +echidna (queen's blade) +zooey (summer) (granblue fantasy) +kawashima momo +lucky beast (kemono friends) +atsushi toushirou +igglybuff +raiden shogun (magatsu mitake narukami no mikoto) +jeralt reus eisner +kotozume yukari +javelin (azur lane) +shiranui (nakiri ayame) +azura cecillia +hirose yasuho +shigure asa +shiro (sewayaki kitsune no senko-san) +mr. game & watch +ezio auditore da firenze +neeko (league of legends) +ooshio kai ni (kancolle) +solid snake +susannah manatt +elis (touhou) +mandragora (arknights) +southern ocean oni +kagurazaka asuna +hornet (azur lane) +sue (grandia) +cure yell +sakurako (blue archive) +kurosawa ruby +plushmallow +lorem (mazohaha) +kotonoha aoi +axel (kingdom hearts) +vyrn (granblue fantasy) +millhiore f. biscotti +hero (headspace) (omori) +ivy (fire emblem) +marie (splatoon) +delutaya +lancelot (granblue fantasy) +fujibayashi kyou +tezcatlipoca (fate) +mao-chan (uramakaron) +giovanni (pokemon) +yami bakura +anemone (eureka seven) +delphox +henrietta de tristain +yashiro (kancolle) +hakase fuyuki (1st costume) +taihou (forbidden feast) (azur lane) +lawrence (shiro seijo to kuro bokushi) +yamagishi fuuka +g36 (girls' frontline) +luna-p +marnie (summer 2021) (pokemon) +black hanekawa +lillie (pokemon) +kumada masaru +bardiche (assault form) +monika (doki doki literature club) +kushida kikyou +akame (akame ga kill!) +death (entity) +nel zelpher +ooji mochizou +steelix +agnes tachyon (lunatic lab) (umamusume) +shiratori aira (ensemble stars!) +yoshikawa yuuko +chikuma (kancolle) +firo (tate no yuusha no nariagari) +ellee-chan +kagami kuro +hatsune miku (nt) +judau ashta +kazuhira miller +ramlethal valentine +micaiah (fire emblem) +uzumaki naruto +kotohime (touhou) +akashi (azur lane) +hornet (kancolle) +rashinban musume +swirlix +cure magical +wingull +dragon yukano +white pikmin +kaori (princess connect!) +dola (nijisanji) +klein moretti +izmir +elma leivonen +chloe (fire emblem) +fire keeper +lize helesta (4th costume) +ms. fortune (skullgirls) +asbel lhant +manuela casagranda +shulk (xenoblade) +crewmate (among us) +ohnuma kurumi +sakura miko (old design) +metis (persona) +magical mirai miku (2019) +star nun (diva) +nurse nemo (fate) +cheshire cat (monster girl encyclopedia) +jakob (fire emblem) +japanese otter (kemono friends) +yoshida yuuko (machikado mazoku) +rika (pokemon) +kasumi (kancolle) +little sister (navigavi) +umikaze kai ni (kancolle) +purple sister +yamai kaguya +okita souji (fate) +jibril (no game no life) +bianca (punishing: gray raven) +re mii +eri (pokemon) +hange zoe +koyanskaya (foreigner) (first ascension) (fate) +azuma (azur lane) +marlene wallace +tojou michiru +izuna (swimsuit) (blue archive) +okita sougo +calamity jane (fate) +akaboshi koume +jiraiya (naruto) +fuse (apex legends) +koshirae tsurugi +gundam calibarn +gretel (sinoalice) +metal sonic +patty fleur +lusamine (pokemon) +earth-chan +natsuki (doki doki literature club) +geeta (pokemon) +misaka kaori +jinno hikari +ak-alfa (girls' frontline) +hapi (fire emblem) +mao (darker than black) +harmony's clownfish (splatoon) +sophia (granblue fantasy) +hanae (christmas) (blue archive) +histoire +yawata maru (kancolle) +pekora (jashin-chan dropkick) +pompompurin +ryoshu (project moon) +quistis trepe +kobayashi (maidragon) +ookurikara +koga koharu +masaki gaillard +charlemagne (fate) +lucy steel +kirasaka sayaka +kiryu coco (dragon) +northern goshawk (kemono friends) +fukuyama mai +kaela kovalskia (1st costume) +twitter-san (character) +noah (xenoblade) +swanna +inugami korone (2nd costume) +hishi akebono (umamusume) +rance +osaki tenka +fairy (kancolle) +reu (cookie) +shiromiya asuka +yamanin zephyr (umamusume) +kaela kovalskia +zun +tenkajin chiyari +mikage reo +cham-p +ogasawara haruka +olivia (fire emblem) +bradamante (first ascension) (fate) +cerberus (shingeki no bahamut) +kobushi abiru +mizuki (kutan) +lithuania (hetalia) +maribel (dq7) +gallon (vampire) +miyazaki nodoka +prier +ikamusume +tatsumi kanji +hinatsuki mikan +wanderer (ragnarok online) +nihongou (touken ranbu) +millions knives +kiyoh bachika +mesprit +hikawa iona +ameth (princess connect!) +swampert +yakumo (nu carnival) +waver velvet (sensha otoko) +komasan +tohsaka rin (fate/extra) +azuma hazuki +happy (fairy tail) +natsuki karin +ppk (girls' frontline) +baobhan sith (swimsuit pretender) (first ascension) (fate) +scirocco (kancolle) +tanikaze (kancolle) +kasumi kai ni (kancolle) +yoshimura thi mai +tana (fire emblem) +churuya +zeke yeager +florina (fire emblem) +matsubara kanon +tamamura gunzo +estellise sidos heurassein +common bottlenose dolphin (kemono friends) +alicia (granblue fantasy) +xiaomu +freedom gundam +tamura hiyori +shining (silent night) (arknights) +kaijin hime do-s +ibis douglas +janine (pokemon) +motoori kosuzu +asahina mikuru +futaba aoi (naomi) +allen walker +kyonko +haru estia +rennala queen of the full moon +perth (kancolle) +hitmontop +eila ilmatar juutilainen +nana (ice climber) +hijiri byakuren +amemura ramuda +sena mikoto +miyamizu mitsuha +hitou nami +kurama (yu yu hakusho) +pandoria (xenoblade) +killua zoldyck +lin (breath of fire) +arnval +rias gremory +yozakura tama +mihama chiyo +sailor star fighter +amagi (kancolle) +mash kyrielight (senpai killer outfit) +azazel (helltaker) +yomiko readman +lycanroc (midday) +meroune lorelei +tamura manami +darmanitan +master chief +kurogane tama +niko (blue archive) +mizuno ai +houshou marine (nun) +okita j. souji (fate) +louise (touhou) +leena (chrono cross) +yagami makino +takanashi yomi +architect (girls' frontline) +wolverine +fin e ld si laffinty +vane (granblue fantasy) +ta-class battleship +cure blossom +tear grants +super sailor chibi moon +koshigaya komari +kakizaki megu +hatsune miku (vocaloid3) +kudamaki tsukasa +calyrex +k.s.miracle (umamusume) +click (arknights) +inuyama mana +aurica nestmile +la pluma (arknights) +taiwan (hetalia) +sugimoto saichi +douma (kimetsu no yaiba) +miyake hinata +mineva lao zabi +kugimiya rie +usopp +milim nava +palla (fire emblem) +sawaguchi mai +nitocris (swimsuit assassin) (fate) +pleinair +kurosaki ichigo +albert wesker +kurumi erika +the yuudachi-like creature +liechtenstein (hetalia) +enlightened byleth (female) +ankimo (tokino sora) +michishio kai ni (kancolle) +dovahkiin +jake the dog +gordie (pokemon) +karma (league of legends) +archer (disgaea) +roberta (black lagoon) +hisuian zorua +saitou chiwa +shuro (blue archive) +aurora (elite ii) (arknights) +houshou marine (6th costume) +diluc (genshin impact) +penelo +lurantis +murrue ramius +anceril sacred +komaki ikuno +pecorine (princess connect!) +suzi q +hirasawa ui +yuna (sao) +inaba haneru (animare) +uchiha sasuke +toxicroak +yuki onna (nurarihyon no mago) +dark knight (final fantasy) +cz75 (girls' frontline) +protagonist 3 (housamo) +sawamura spencer eriri +sagae haruki +fischl (genshin impact) +lamia (punishing: gray raven) +tressa colzione +pinky pop hepburn +rebecca (cyberpunk) +kudou shin'ichi +hiro (darling in the franxx) +simon belmont +shinazugawa genya +ty lee +colonel sanders +diona (genshin impact) +katsuragi (senran kagura) +meerkat (kemono friends) +kochou shinobu +hiroi kikuri +tmp (girls' frontline) +typhlosion +amakusa shirou (fate) +gotou toushirou +pilot (titanfall 2) +mirage (apex legends) +ouma shuu +kawakami sadayo +ali baba saluja +misaki (princess connect!) +chigiri hyoma +ro-class destroyer +chesed (project moon) +shiranui kai ni (kancolle) +satanichia kurumizawa mcdowell +udin (kureiji ollie) +yuki miku (2016) +himemori luna +kishitani shinra +skuld (aa megami-sama) +marida cruz +michael kaiser +komugi (lee) +fletcher mk ii (kancolle) +yuri sakazaki +yuisis (granblue fantasy) +priscilla the crossbreed +kin'iro ryotei (umamusume) +yoimiya (genshin impact) +emiya shirou +deoxys (normal) +commandant teste (kancolle) +kfp employee (takanashi kiara) +tendo teru +ishikawa goemon xiii +hinata (blue archive) +infernape +nero claudius (fate) +rein (futagohime) +evangeline a.k. mcdowell +hanasaki tsubomi +osana najimi (komi-san wa komyushou desu) +taiga takeru +jubei (blazblue) +yuuhi kurenai +fiora (league of legends) +pinsir +berserker (granblue fantasy) +princess daisy +vanilla h +natsume rin +idunn (fire emblem) +kiso (kancolle) +belgium (hetalia) +caro ru lushe +takase mizuki +hamo (dog) +pink mercy +fusou (kancolle) +kohibari kurumi +valkyrie (vnd) +crow (la+ darknesss) +toriningen +g-spring goddess (ishiyumi) +meruem +burgh (pokemon) +rum (girls und panzer) +killer queen +sawsbuck +monk (sekaiju) +swablu +crow armbrust +shirosaki hana +demon cleric +takamori haruka +hornet (hollow knight) +carol (skullgirls) +aranea highwind +girlfriend (friday night funkin') +goblin slayer +millie parfait +yamato maya +tenochtitlan (first ascension) (fate) +genis sage +yang guifei (third ascension) (fate) +wakazato haruna +mlynar (arknights) +captain (kemono friends) +fujiki maka +roxas +yuuki chihiro +iori (blue archive) +sister cleaire (1st costume) +lala (monster musume) +namazuo toushirou +he-class light cruiser +yukoku kiriko +saejima kiyomi +yanma +nanjo hikaru +sekiutsu maria tarou +hatoba tsugu +grimsley (pokemon) +little boy admiral (kancolle) +wraith (apex legends) +kuonji ukyou +kamishirasawa keine +mazinger z (mecha) +giratina (altered) +entei +herlock sholmes +miyamura miyako +agito (nanoha) +lisianthus +olivier mira armstrong +shinjou akane +fenrir (last origin) +sakuma mayu +fate testarossa (original form) +gokuhara gonta +nyto (girls' frontline) +shinonome ena +yayoi (kancolle) +nekki basara +ulti (one piece) +susato mikotoba +okinami (kancolle) +nightwing +graveler +hatsushimo (kancolle) +edgar roni figaro +nu-13 +yowai totoko +virtuosa (arknights) +ajna (indivisible) +akai haato +raven branwen +nekoha shizuku +hamburger-chan (hundredburger) +arima miyako +hina (swimsuit) (blue archive) +mainz (azur lane) +pallas (arknights) +mimori suzuko +asou yuuko +cubchoo +aki (girls und panzer) +k5 (girls' frontline) +noire (fire emblem) +millium orion +helena douglas +hitomi (doa) +one (cevio) +rurima (cookie) +zuko +musha miko tomoe +risotto nero +alma armas +hakozaki serika +hero's son (dq5) +ushiromiya jessica +arita haruyuki +lloyd irving +air shakur (umamusume) +kagamine rin (append) +xander (fire emblem) +arch bishop (ragnarok online) +shinko windy (umamusume) +onitsuka natsumi +omori (omori) +dick grayson +solaire of astora +prinz eugen (symphonic fate) (azur lane) +fennel (pokemon) +ventus (kingdom hearts) +lass (pokemon) +anne boonchuy +matsuno chifuyu +saratoga (kancolle) +theodore (persona) +shiodome miuna +hinazuki ririna +hiei kai ni (kancolle) +porco galliard +ui (swimsuit) (blue archive) +jururu +gaogaigar +rx-78-2 +sunazuka akira +sphinx (toaru majutsu no index) +jaguarman (fate) +molcar +young zelda +sakuraba neku +oozora subaru +tsurugi (blue archive) +amakusa juuza +smoliv +tira misu +narumi tsuyu +elfriend (shiranui flare) +mash kyrielight (ortenaus) +agatsuma kaede +shampoo (ranma 1/2) +ibuki (azur lane) +silky anteater (kemono friends) +koyanskaya (assassin) (second ascension) (fate) +azusagawa tsukino +pooh +zeraora +mizuki nana +maya fey +rio rollins +ikeda chizuru +hanemiya kazutora +kinu (azur lane) +memcho +yamazaki sousuke +kabuto (pokemon) +kurusugawa himeko +ushiromiya george +agravain (fate) +ogasawara sachiko +king cobra (kemono friends) +britomart (fate) +koharu rikka +brassius (pokemon) +momo (gundam build divers) +artoria pendragon (lancer alter) (royal icing) (fate) +flonne (fallen angel) +teruyof +kousaka china +bremerton (relaxation consultation) (azur lane) +ooyama (angel beats!) +implacable (azur lane) +ibuki (blue archive) +boota (ttgl) +koshigaya natsumi +hoshiguma yuugi +cardboard box gundam +00 qan[t] +lancelot (fate/grand order) +tatsumiya mana +konami kirie +mashu (control) +nicole demara +cleo everlastin +silvervale +kagemori michiru +okunoda miyoi +ainz ooal gown +cosmo (chainsaw man) +mochizuki chiyome (fate) +bunny nun (diva) +tieria erde +ijichi nijika +tokiha mai +himekawa yuki +yuudachi kai ni (kancolle) +venat (ff14) +amami rantaro +ushio kai ni (kancolle) +victreebel +janus (kancolle) +ikuchan kaoru (character) +sekai (cevio) +vinegar doppio +roselia (pokemon) +hero (dq5) +ogami shirou +fediel (granblue fantasy) +tsujimoto natsumi +kagari (rewrite) +kashiwazaki sena +nine-colored deer +kazamatsuri fuuka +prospera mercury +mutou hana +minase rio +mohammed avdol +tokoyami towa (1st costume) +magical sapphire +nasus +gon freecss +nina (girls und panzer) +toxtricity (low key) +transformed ditto +lyn (summer) (fire emblem) +smart falcon (umamusume) +darius (league of legends) +abigail williams (fate) +hammann (rebellious summer) (azur lane) +kamina (ttgl) +minamoto no raikou (swimsuit lancer) (third ascension) (fate) +braviary +minamoto no raikou (swimsuit lancer) (fate) +ishikirimaru +houjou satoshi +alhaitham (genshin impact) +alcremie (vanilla cream) +jennifer walters +sheffield (azur lane) +tsukumo sana (1st costume) +nitori aiichirou +garchomp +rim (kamitsubaki studio) +kagura (azumanga daioh) +drayden (pokemon) +ultraman +maryland (kancolle) +hulkling +will (pokemon) +orimura ichika +miyamoto musashi (swimsuit berserker) (third ascension) (fate) +adachi sakura +morrighan +ralf jones +widowmaker (overwatch) +sawai natsuha +konjiki no yami +ikeda kana +gengar +abukuma kai ni (kancolle) +touka (utawarerumono) +sirfetch'd +paul bunyan (third ascension) (fate) +piro +naka (kancolle) +sovetskaya rossiya (the lackadaisical lookout) (azur lane) +shinano (moonlit chrome) (azur lane) +shigure (kancolle) +nekota tsuna +neviril (simoun) +izumi ako +gullinbursti (housamo) +inugami korone (4th costume) +geegee (granblue fantasy) +tsuyuzaki mahiru +togami byakuya +sasaki haise +tokisaki kurumi +cell (dragon ball) +georgia (azur lane) +diavolo +jumpluff +hack (apex legends) +fuyutsuki (kancolle) +cerebella (skullgirls) +leaf (pokemon) +hilichurl (genshin impact) +bokoblin +hachikuji mayoi +momo (kancolle) +yagami kou +asahina mafuyu +lucca ashtear +kitagou fumika +maria cadenzavna eve +andou ringo +homulilly +reisen udongein inaba +theresa (arknights) +skyla (pokemon) +sakura megumi +ryu (street fighter) +rhea (fire emblem) +koyuki (blue archive) +pozyomka (arknights) +papika (flip flappers) +nogi sonoko +mizumoto yukari +totsuka saika +i-47 (kancolle) +jewelry bonney +ump45 (girls' frontline) +rossweisse +m16a1 (girls' frontline) +feng (skullgirls) +samejima mamimi +fio germi +katalina (granblue fantasy) +salamander coral +cure etoile +anchorage princess +ringo (touhou) +pholia +penguin 2-gou +dark angel olivia +freminet (genshin impact) +passionlip (third ascension) (fate) +u-511 (kancolle) +iris (konosuba) +frillish (female) +rey (star wars) +racing miku (2013) +annytf +mari (track) (blue archive) +taishakuten (onmyoji) +shinjin-chan (douki-chan) +maiden in black +hikigaya hachiman +riju +clair vaux bernardus +stocking (psg) +shera l. greenwood +sett (league of legends) +suzuran (spring praise) (arknights) +jesus +lilica felchenerow +fudou yuusei +komajirou +chiriko (atlanta) +tomoe hotaru +caenis (swimsuit rider) (first ascension) (fate) +momoka (blue archive) +misaki akeno +elizabeth (persona) +hatori chise +aioi yuuko +nagato kai ni (kancolle) +bamboo memory (umamusume) +bardock +l'arachel (fire emblem) +maintenance musume (kancolle) +cure sword +thompson (girls' frontline) +koraidon +shirakami fubuki (hololive summer 2019) +shinn asuka +nekomata okayu (6th costume) +herta (honkai: star rail) +yuzuki choco +vampy +chuatury panlunch +gundam mk ii +erwin (girls und panzer) +alolan ninetales +bianka durandal ataegina (valkyrie gloria) +saratoga (warship girls r) +korrina (pokemon) +iwanaga kotoko +japanese black bear (kemono friends) +yamifuka-san (hoshi san 3) +lyn (fire emblem) +laura la mer +red blood cell (hataraku saibou) +exeggcute +yonomori kobeni +cure beat +mari (omori) +takanashi kotori +asuka ryou +usalia (disgaea) +enna alouette (1st costume) +haneoka meimi +more more jump! miku +kama (first ascension) (fate) +sena izumi (ensemble stars!) +hero's daughter (dq5) +sakutarou (umineko) +combee +blue pikmin +lucena winter +sterkenburg cranach +araragi koyomi +diana cavendish +quiet (metal gear) +hashiri nio +sacred heart +naked snake +gimmy adai +saga (arknights) +rixia mao +pish +my melody +zygarde +torterra +crypto (apex legends) +akatsuki uni +trapinch +connie springer +incredible ecclesia the virtuous +gorou (genshin impact) +kendou itsuka +shiki natsume +midoriya izuku +mumei (kabaneri) +kishibe rohan +noblesse (elsword) +sandslash +shining (arknights) +max (pokemon) +grizzly mkv (girls' frontline) +yanase miyuki +hunter (ragnarok online) +jintsuu kai ni (kancolle) +giant penguin (kemono friends) +elizabeth bathory (brave) (fate) +cicin mage (genshin impact) +katagiri sanae +yaezawa natori +mutsu (azur lane) +kujikawa rise +sonia strumm (mega man) +giant panda (kemono friends) +tsube aika +nina (breath of fire i) +ogerpon +sakenomi (cookie) +shy guy +kanna (blue archive) +meteora osterreich +serena (sygna suit) (pokemon) +jade curtiss +aircraft carrier water oni +muppo +matsubara yuuna +type 100 (girls' frontline) +flint (arknights) +cure marine +fujimaru ritsuka (female) (brilliant summer) +cygnet (azur lane) +arisen (dragon's dogma) +melusine (genshin impact) +sugimoto reimi +otonashi ryouko +wishiwashi (solo) +little blue whale (kancolle) +oinari-sama (kemono friends) +jun'you (kancolle) +bra (dragon ball) +vice (alchemy stars) +regensburg (azur lane) +spider-gwen +mario +yuuki kei +kaguya madoka +purple pikmin +shirafuji kyouko +wynn the wind charmer +sonoda yuu +rurun rururica +yamagou ayumi +mochizuki anna +wriothesley (genshin impact) +cure wing +shirogane souju +mina (pokemon) +kurokawa akane +tarou tachi +birdy cephon altera +kagamine rin +gilgamesh (immoral biker jacket) (fate) +carmelina (granblue fantasy) +kazama iroha +serina (christmas) (blue archive) +kama (swimsuit avenger) (first ascension) (fate) +sasori (naruto) +hishi miracle (umamusume) +sakakibara satomi +type 80 (girls' frontline) +queen marika the eternal +hige habahiro +egyptian loli (surio) +colossal titan +irelia +aoyama motoko +nowi (fire emblem) +grand archer (elsword) +tomimi (arknights) +sasasegawa sasami +amada ken +abigail williams (third ascension) (fate) +xiaolang +chen hai (azur lane) +african rock python (kemono friends) +nina kosaka (1st costume) +momoi (maid) (blue archive) +qian renxue (douluo dalu) +saratoga mk ii (kancolle) +malos (xenoblade) +friend (nanashi mumei) +ryne waters +endeavor (boku no hero academia) +sandalphon (granblue fantasy) +rapunzel (disney) +cilan (pokemon) +unicorn (the gift of spring) (azur lane) +megurine luka (toeto) +marth (fire emblem) +eileen the crow +takahashi maya +elena olegovna owen +adagumo no yaorochi +tooth fairy (reverse:1999) +honoka (doa) +hootsie (nanashi mumei) +akagi-chan (azur lane) +aru (new year) (blue archive) +machine (nier) +qiqi (genshin impact) +psylocke +toad (mario) +togedemaru +eriko (princess connect!) +aranara (genshin impact) +dipper pines +meltryllis (swimsuit lancer) (first ascension) (fate) +asami sato +sigui (queen's blade) +maria marionette +france (hetalia) +present mic +nina einstein +takamachi nanoha +kaai yuki +mayuzumi fuyuko +matsuoka rin +caesar (girls und panzer) +ursula (takunomi) +edward geraldine +yuuki juudai +akuma homura +mitama (fire emblem) +kromer (project moon) +shimohira reika +shanoa +snow white (nikke) +tamamo no mae (spring casual) (fate) +hazama +yatadera narumi +tashkent (azur lane) +andreana (arknights) +feater (arknights) +u-1196 +harada miyo +narmaya (granblue fantasy) +chane laforet +artoria pendragon (lancer) (fate) +yuegui (genshin impact) +lynette bishop +bounsweet +nathan seymour +tsukinaga leo +sunflora +xiangling (genshin impact) +black delmo +shannon (umineko) +teepo (tales) +schierke (berserk) +curly brace +k/da evelynn +tamamo (fate) +arashio kai ni (kancolle) +emerada (xenogears) +sakuragi mano +coco (eogks) +tangrowth +giratina (origin) +melli (pokemon) +kusunoki tomori +miyamoto musashi (swimsuit berserker) (fate) +hanasato minori +allen avadonia +miwa kasumi +anchorage (azur lane) +fuji aoi +mare bello fiore +shiroe (log horizon) +cyrus albright +remoraid +suzune (senran kagura) +societte (granblue fantasy) +makoto (summer) (princess connect!) +cheshire (azur lane) +nagusa (blue archive) +togame momoko +battleship summer princess +tokoyami towa (jirai kei) +aria (sister princess) +medic 2 (sekaiju) +lampent +temari (naruto) +tionishia +rance (dokidoki! precure) +karma (nakiri ayame) +rio wezley +mouri ran +nishizono mio +ppsh-41 (girls' frontline) +kuchisake-onna +ro-500 (kancolle) +wakana shiki +takabushi kengo +ex-keine +i-14 (kancolle) +bianka durandal ataegina (palatinus equinox) +ohara mari +akari (pokemon) +kishinami (kancolle) +nelliel tu odelschwanck +tamamo no mae (third ascension) (fate) +lapras +alcryst (fire emblem) +yamask +sonozaki mion +cure flora +choi mochimazzui +melusine (second ascension) (fate) +reiuji utsuho (bird) +zenos yae galvus +ayatsuji tsukasa +wendy (wendy's) +lugia +varus +keith (voltron) +gourry gabriev +aura bella fiora +zelos wilder +ekko (league of legends) +tsukuyomi komoe +yorumi rena (1st costume) +chinchou +kawanishi shinobu +sorcerer (ragnarok online) +sinclair (project moon) +renton thurston +alchemist (ragnarok online) +grey wolf (kemono friends) +xianyun (genshin impact) +ilulu (maidragon) +soldier (tf2) +duosion +caren hortensia +mukaido manaka +mikuni oriko +harukara (7th dragon) +mariah (jojo) +nari (cougar1404) +katou keiko +kurikoma komaru +nakano yuka +futaba akane +suo sango +oda nobunaga (koha-ace) +lara croft +llenn (sao) +azuma (soft voice of spring) (azur lane) +hasumi souji (eroe) +curse maker +tallinn (azur lane) +ingrid (taimanin murasaki) +curren chan (umamusume) +judith (tales) +agrias oaks +littorio (azur lane) +ren kougyoku +selphie tilmitt +captain price +waltrud krupinski +mizusawa mao +kiyohime (swimsuit lancer) (first ascension) (fate) +friedrich der grosse (azur lane) +naruse yuu +z1 leberecht maass (azur lane) +dante (limbus company) +seviper +scheherazade (fate) +priapus a. tarou +hifumi (blue archive) +narumiya yume +kieran (pokemon) +kitakami reika +aegis (takunomi) +higashikata josuke +malenia blade of miquella +unicorn gundam banshee +amuro ray +suou tatsuya +hinata hajime +serafall leviathan +hisakawa tetsudou +amaya haruko +arcane jinx +aty (summon night) +martha (swimsuit ruler) (first ascension) (fate) +kaname madoka +high wizard (ragnarok online) +asahina mirai +type 95 (narcissus) (girls' frontline) +fal (girls' frontline) +himeno kanon +jaguar (kemono friends) +kitami yuzu +astel leda +shirogane noel +mio (xenoblade) +rinne (rinrinne) +kahili (pokemon) +endou araya +gumi (v3 megpoid) +spinda +valkyrie (apex legends) +chiyoda (kancolle) +dororo (character) +otouto (92m) +elaine auclair +sophie neuenmuller +ouro kronii (3rd costume) +karina lyle +frankenstein's monster (fate) +tanaka gundham +ivy (sparrowl) +munakata hinano +zelsius +takaomi (orenchi no maidosan) +pachira +novice (ragnarok online) +cure ace +megurine luka (vocaloid4) +takahashi rie +fujiyoshi harumi +kagari ayaka +i-58 (kancolle) +sarashina ruka +akagi miria +yamato suzuran +perona +pierre bichelberger +kuradoberi jam +ruby rose +souchou +hoshimiya mukuro +matsumi kuro +temari (nekomata okayu) +gravel (modeling night) (arknights) +kaname junko +frederica baumann +talon (league of legends) +kiana kaslana (void drifter) +reshiram +m870 (girls' frontline) +monet (one piece) +swellow +rebecca miyamoto +hamakaze (kancolle) +inui shinju +tanaka rie +miyadeguchi mizuchi +chapayev (azur lane) +chocolate misu +florence nightingale (fate) +evil twin ki-sikil +arama (genshin impact) +raffina (puyopuyo) +darry adai +ling xiaoyu +asahina aoi +fortune (last origin) +yagokoro +ghost quartz (houseki no kuni) +elizabeth (bioshock infinite) +umitsuki natsuzou +alabama (azur lane) +ichijou akari +kanamori sayaka +marie rose +andira (granblue fantasy) +leonardo watch +siesta (tantei wa mou shindeiru) +yonezawa natsumi +blue-eyes white dragon +tiki (adult) (summer) (fire emblem) +dido (azur lane) +hatsukano you +tron bonne (mega man) +hatena yousei +mousse (arknights) +aponia (honkai impact) +zelgadiss graywords +miyasaka ryou +aiba yumi +maki (blue archive) +gotou moyoko +robin (dc) +mia fey +okazaki yumemi +kanno naoe +nakagawa kanon +serina (blue archive) +mithrun +shigure (hot spring) (blue archive) +takane lui +five-seven (cruise queen) (girls' frontline) +nishigaki nana +ibuki douji (swimsuit berserker) (fate) +northern italy (hetalia) +katase shima +yuubari kai ni (kancolle) +takodachi (ninomae ina'nis) +elie macdowell +silent magician +drednaw +totooria helmold +c.c. +bellibolt +gunner (final fantasy) +mejiro palmer (umamusume) +layla (mino) +oddish +revali +neko (minato aqua) +lulu (ff10) +takumi (fire emblem) +purrloin +kana (male) (fire emblem) +evelysse (star ocean) +pk (girls' frontline) +tamaki iroha +lunala +hayase misa +white len (tsukihime) +alisa mikhailovna kujou +chougei (kancolle) +pomu rainpuff (1st costume) +snivy +erina (rabi-ribi) +bojji +machina x flayon +matoi ryuuko +sasha waybright +nagatsuki (kancolle) +alvin (tales) +miketsukami soushi +hei (darker than black) +totodile +luvdisc +ch'en (arknights) +leonardo da vinci (swimsuit ruler) (fate) +little blue (guin guin) +bondrewd +tsukishima kei +rice shower (make up vampire!) (umamusume) +tier harribel +gunnthra (fire emblem) +asuka momoko +shimura shinpachi +red toad (mario) +uruha rushia +kokkoro (summer) (princess connect!) +aircraft carrier oni +china (hetalia) +amanda o'neill +roark (pokemon) +arisaka mashiro +ping hai (azur lane) +protagonist 1 (housamo) +diglett +nagato (great fox's respite) (azur lane) +tsukishima hajime +kaga kai ni (kancolle) +flora (dq5) +stark (sousou no frieren) +rimururu +kirisaki chitoge +bandana waddle dee +kanbe kotori +kujou miyako +ogami sakura +eir (fire emblem) +yorda +nitocris (swimsuit assassin) (second ascension) (fate) +staraptor +seox (granblue fantasy) +yuki miku (2013) +rude (ff7) +hoothoot +nora valkyrie +hessian (fate) +alfonse (fire emblem) +popo (ice climber) +formidable (timeless classics) (azur lane) +jabami yumeko +suzume (princess connect!) +nina (breath of fire v) +kokutou azaka +leviathan (umineko) +entoma vasilissa zeta +gavial the invincible (arknights) +shizuko (swimsuit) (blue archive) +yagiri namie +xingqiu (genshin impact) +lana (pokemon) +two (tsu (lovesick1964)) +kipi-san +lapis lazuli (steven universe) +tetra +hyakuya mikaela +nogizaka haruka +freyja wion +uzuki (kancolle) +delibird +dragapult +maya kai ni (kancolle) +chase (pokemon) +jintsuu (kancolle) +irys (irys 1.0) (hololive) +bakugou katsuki +itou kaiji +valentine (skullgirls) +fujimaru ritsuka (female) (mage's association uniform) +ichigaya arisa +eve (elsword) +mr. c.b. (umamusume) +rean schwarzer +kyan reki +haru (kuzuyu) +gunbuster +hyuuga (kancolle) +fae (fire emblem) +agatsuma zenitsu +hiramitsu hinata +dominica s. gentile +yun lee +prince of lan ling (fate) +tosa (azur lane) +kusunoki yukimura +chou-10cm-hou-chan +hk416 (herbal-flavored hard candy) (girls' frontline) +yagyuu juubei (hyakka ryouran) +minase nayuki +hagino chiaki +asama tomo +meryl stryfe +kurono kurumu +black mage +meteorite (arknights) +lamb (league of legends) +frillish (male) +yoroizuka mizore +froslass +anabel (pokemon) +kijin seija +lucia: plume (punishing: gray raven) +spider-man +ebisu (dorohedoro) +bellsprout +maehara shinobu +erwin smith +himemiya anthy +registeel +blue cat (precure) +toudou chise +laharl +maru-yu-san +ouro kronii +kodama fumika +tanaka kotoha +female priest (dungeon and fighter) +yamagami karuta +sasaki makie +nursery rhyme (fate) +katarina claes +billy the kid (fate) +ledo (suisei no gargantia) +suzumi nemo +sae (hidamari sketch) +fine (futagohime) +female admiral (kancolle) +akemi homura +colorado (azur lane) +helena blavatsky (swimsuit archer) (third ascension) (fate) +munna +commander shepard +silver the hedgehog +minami kana +oktavia von seckendorff +klan klein +hifumi (swimsuit) (blue archive) +steve rogers +sawatari mitsuki +takamine noa +ookami mio (3rd costume) +utsumi erice +saijo juri +harada makoto +murasame kai ni (kancolle) +emilico (shadows house) +emiya kiritsugu (assassin) +elma (xenoblade x) +bonanus (genshin impact) +kurosaki mea +uryuu ryuunosuke +eva 02 +toki kureha +yashio rui +matsuno ichimatsu +punk girl (pokemon) +heles +ibara mayaka +tsurumaki maki +nami (one piece) +ro635 (girls' frontline) +vueko +suguri (orange juice) +zato-1 +tamura yukari +rottytops +ogre (granblue fantasy) +kongou iroha +kurata sayuri +kamazuki suzuno +mordred (swimsuit rider) (fate) +florian (pokemon) +kanzaki sumire +shiguma rika +simon blackquill +narita miho +dead master +ping hai (summer vacation) (azur lane) +noumu (boku no hero academia) +lenora (pokemon) +malon +mythra (massive melee) (xenoblade) +nakiri ayame (shrine maiden) +kuroi nanako +sf-a2 miki +mashiro (1st costume) (nijisanji) +raisa pottgen +yoshino (date a live) +ichijou hikaru +purple heart (neptunia) +kay (girls und panzer) +asmodeus (umineko) +kashima (kancolle) +niwamaru (niwarhythm) +sanada akihiko +takanashi kiara (2nd costume) +kiso kai ni (kancolle) +hakuowlo +takane (blue archive) +petilil +selena (punishing: gray raven) +victorica de blois +desco (disgaea) +oliver (vocaloid) +ten'ou haruka +white blood cell (hataraku saibou) +takajo kyoji +absol +mizuhashi parsee +mutsuki (kancolle) +venipede +kon futaba +saturn (pokemon) +sonia (p&d) +momosuzu nene (1st costume) +krystal +tristan (fate) +rotom dex +kokonoe rin +takane manaka +female protagonist (pokemon go) +hanyuu +quinella +chiba kirino +kaizuka inaho +amano nene (vtuber) +goutokuji miyako +jingliu (honkai: star rail) +ichijou ririka +nanakusa nazuna (yofukashi no uta) +akagi ritsuko +kira yamato +mosin-nagant (girls' frontline) +lily black +dark samus +yamuraiha +matt (pokemon) +hachisuka kotetsu +anastasia (idolmaster) +misty (pokemon) +shy (character) +sawamura daichi +batman +cinccino +surcouf (loisirs balneaires) (azur lane) +xia you qing +julia chang +marco (one piece) +fujimaru ritsuka (female) +jeanne d'arc (swimsuit archer) (first ascension) (fate) +golisopod +nozomi (princess connect!) +watson amelia (2nd costume) +noctis lucis caelum +robin hood (fate) +mayu (yuizaki kazuya) +samsung sam +ferdinand von aegir +tachibana arisu +kuramochi meruto +mari (blue archive) +kuga yuuma +amagi (wending waters serene lotus) (azur lane) +imaichi moenai ko +new jersey (azur lane) +nonna (girls und panzer) +kashiwagi tsubasa +beedrill +mega man (character) +narwhal (kemono friends) +millicent (elden ring) +skitty +chie (ishikei) +mallow (pokemon) +winston (overwatch) +otomore (shashaki) +lotad +anubis (houtengeki) +massachusetts (azur lane) +commander (nier:automata) +grusha (pokemon) +seele vollerei (swallowtail phantasm) +ren zotto +shirayuri sakura +ao-chan (ninomae ina'nis) +ranger (ragnarok online) +kuroeda-san +hermione granger +suzu (cookie) +christina (princess connect!) +suicidal girl (hamsterfragment) +iroha (samurai spirits) +michael jackson +courtney (pokemon) +pengy (granblue fantasy) +aether sage (elsword) +cure dream +shiroko (blue archive) +tsubaki (kunoichi tsubaki no mune no uchi) +luciela r. sourcream +listener (inugami korone) +asashimo (kancolle) +bolin +palmon +hecatia lapislazuli (moon) +america (hetalia) +ru-class battleship +nana (kemono friends) +luna (honkai impact) +catwoman +ceobe (arknights) +hanna rudel +yahagi (kancolle) +yuki miku (2021) +mejiro ramonu (umamusume) +honma himawari (1st costume) +goldmary (fire emblem) +pan (dragon ball) +faye (fire emblem) +ryou sakazaki +ningguang (orchid's evening gown) (genshin impact) +imai kana +serval landau +clift +executive mishiro +yellow (among us) +asbestos (arknights) +oda nanami +blue whale (kemono friends) +ushiromiya eva +inukai isuke +k1-b0 +abigail williams (traveling outfit) (fate) +agnes digital (umamusume) +cryska barchenowa +common vampire bat (kemono friends) +cteno (slugbox) +ouroboros (granblue fantasy) +alisa reinford +nemona (pokemon) +honolulu (umbrella girl) (azur lane) +shimakaze (azur lane) +kurusugawa ayaka +cheval grand (umamusume) +furutaka kai ni (kancolle) +frieza +poporing +cassidy (overwatch) +william shakespeare (fate) +hodaka natsumi +ebisu eika +nazrin +musubime yui +mal (malberrybush) +sierra mikain +camyu +kamoi (kancolle) +romulus (fate) +proto man +kadotani anzu +kamijo haruna +quincy (warship girls r) +layla (idolmaster) +mochizuki honami +norway (hetalia) +kujo jolyne +formaggio +prinz eugen (profusion of flowers) (azur lane) +hikawa hina +winter schnee +yoru (chainsaw man) +yamakaze kai ni (kancolle) +smoochum +dorothy haze +sakurada yuuta +admire vega (umamusume) +maebara keiichi +wigglytuff +manaphy +kiba manami +shockwave (transformers) +ambriel (arknights) +shalltear bloodfallen +sora (no game no life) +wendi (nanoha) +blue poison (elite ii) (arknights) +kochiya sanae +nanami (punishing: gray raven) +i-8 (kancolle) +exusiai (arknights) +ando ruruka +inubousaki shian +viola (pokemon) +aeiou (yoako) +buneary +gold ship (umamusume) +cerberus (kemono friends) +zack fair +st. louis (luxurious wheels) (azur lane) +wonder acute (umamusume) +liliruca arde +plains zebra (kemono friends) +takamine midori +velvet scarlatina +leonardo da vinci (fate) +tirpitz (azur lane) +twilight (go! princess precure) +hashibira inosuke +cetoddle +sasaki fuuka +nina (fire emblem) +asakura yoh +shiki haruomi +bonnie (pokemon) +shen (league of legends) +battle bunny riven +nakigitsune +petra gurin (2nd costume) +shiina noriko +rurudo lion (1st costume) +shinano (azur lane) +seaking +ichinose haru +mai (touhou) +yasuri shichika +kaede johan nouvel +houshou marine (marching band) +higashiyama kobeni +flower (vocaloid) +coco adel +himeno (chainsaw man) +yana (chihuri) +rutile (houseki no kuni) +mochizuki (kancolle) +queen of sheba (fate) +himemushi momoyo +fate testarossa (movie 1st form) +airi (queen's blade) +taroumaru (genshin impact) +shirai yuyu +palkia +hathaway noa +lambdadelta +kefla (dragon ball) +olivia (pokemon) +lee-enfield (girls' frontline) +hiiragi shino +canaan (character) +provence (arknights) +themis (ff14) +melusine (swimsuit ruler) (fate) +rhea (summer) (fire emblem) +serperior +yaya (machine-doll) +gris (vertigris) +rikka (holostars) +namine +dark sakura +cottonee +nero claudius (swimsuit caster) (third ascension) (fate) +shiranui (kancolle) +reisen (touhou bougetsushou) +huang baoling +tina armstrong +saiki kusuo +larry (pokemon) +takeba yukari +kumano kai ni (kancolle) +raticate +arashi (kancolle) +oshino ougi +nick wilde +himeji mizuki +yuki miku (2017) +luviagelita edelfelt +richter belmont +lord's blade ciaran +hk416 (mod3) (girls' frontline) +haruna (kancolle) +damian doyle (cyphers) +zero (code geass) +mikazuki munechika +frederica nikola tesla +ayre (armored core 6) +baiguio (zhu xian) +pinkie pie +ninjask +yorigami jo'on +asari nanami +wa2000 (op. manta ray) (girls' frontline) +rabbit yukine +st. louis (azur lane) +guoba (genshin impact) +clarice (idolmaster) +chipp zanuff +koizumi itsuki +lemuen (arknights) +puni (atelier) +usami ichika +karulau +honjou satoru +bulleta +mococo abyssgard (1st costume) +yakumo ran (fox) +gneisenau (nightmarish succubus) (azur lane) +nonaka yuki +kimidori (ico) +altair (re:creators) +artoria pendragon (alter swimsuit rider) (fate) +sazanami kai (kancolle) +shishiou +common dolphin (kemono friends) +ninomae ina'nis +jade (houseki no kuni) +kyubey +su (honkai impact) +kirin r yato (arknights) +momogaa (girls und panzer) +minun +akumi (yoclesh) +kani nayuta +clark still +aoyama sumika +ayunda risu +rosaria (genshin impact) +lila decyrus +madarame harunobu +suitokuin tenmu +ayanami rei +justice task force member (blue archive) +narita taishin (umamusume) +ion (tales) +kuzu suzumi +houshou marine (new year) +arrietty +kitashirakawa chiyuri +sylveon +elfnein +nanase haruka (free!) +moegi emo +momdroid (mechanical buddy universe) +ikebukuro akiha +tachibana miya +problem solver sensei (blue archive) +makihara arina +cure miracle +ashley graves +majin android 21 +haman karn +tina (closers) +huang (granblue fantasy) +kitakami futaba +spike spiegel +yuki miku (2014) +emden (azur lane) +kongou kai ni (kancolle) +houraisan kaguya +ogre (dq10) +lunch (good) (dragon ball) +haqua d'rot herminium +kanzaki ranko +artoria caster (first ascension) (fate) +i-13 (kancolle) +oozora subaru (1st costume) +gorebyss +hershel layton +douki-kun (douki-chan) +miyazono kawori +hazekura mikitaka +whisperain (arknights) +kaf (kamitsubaki studio) +rodney (azur lane) +saber alter +mikura (kancolle) +powered buttercup +lina davis +king (nanatsu no taizai) +arash (fate) +kannagi cocoa +kohaku (yua) +aoandon +koito otonoshin +nessa (pokemon) +haurchefant greystone +stella hoshii +princess of the crystal +ouga saki +shizuna rem misurugi +takamine takane +seto sun +paras +fujimaru ritsuka (female) (polar chaldea uniform) +sage (ragnarok online) +koyanskaya (chinese lostbelt outfit) (fate) +yayoi sakura +nico robin +russia (hetalia) +deutschland (azur lane) +spheal +lucia fex +savanna striped giant slug (kemono friends) +cress (pokemon) +kriemhild gretchen +daiwa scarlet (trifle vacation) (umamusume) +rika (m k) +ninamori eri +white (among us) +momone momo +inazuma (kancolle) +artoria pendragon (swimsuit ruler) (second ascension) (fate) +canyne +aston machan (umamusume) +hiiragi miki +plamja-sama +fuwa (precure) +racing miku (2014) +kyle broflovski +yun yun (doupo cangqiong) +souryuu (azur lane) +sakurada hane +deidara (naruto) +adventurer (ff14) +meowscarada +hoshino aquamarine +venonat +est (fire emblem) +teresa (claymore) +suzuki jun +nanase kureha +furudo erika +godzilla +ikeda chitose +ibarazaki emi +barawa +kawai rie +chloe (princess connect!) +finland (hetalia) +cure fortune +drake (azur lane) +cure gelato +tatara kogasa (umbrella) +hayami kanade +kusuriuri-san +uehara himari +dark link +piccolo +futatsuiwa mamizou (human) +teucer (genshin impact) +venusaur +croconaw +kinu kai ni (kancolle) +munakata kyousuke +yae sakura +fl-chan +morgiana +chen hai (vestibule of wonders) (azur lane) +yokoyama chika +chikuwa (yurucamp) +fujihara (haguhagu) +tsukikawa chili +minamoto no raikou (swimsuit lancer) (second ascension) (fate) +raiden mei (striker fulminata) +aisaka taiga +cyno (genshin impact) +nakahara mizuki +cu chulainn (fate/stay night) +giacomo (pokemon) +kiryuuin ragyou +artoria pendragon (fate) +kamen rider blade +nanaya shiki +gamagoori ira +bachira meguru +zacian +yasuo (league of legends) +marblehead (azur lane) +oda nobunaga (swimsuit berserker) (first ascension) (fate) +lala tramont +alice margatroid +alice wishheart +wonderlands x showtime miku +litten +broly (dragon ball super) +lyrica prismriver +sawaizumi chiyu +flygon +sakurai ayaka (lonely girl ni sakaraenai) +kamen rider decade +magnezone +mikoko (kemomimi oukoku kokuei housou) +ekubo (mob psycho 100) +rinoa heartilly +graf zeppelin (kancolle) +cure egret +morelull +heathcliff (project moon) +fuwafuwa-chan (kamiyoshi rika) +h'aanit (octopath traveler) +albedo (overlord) +kjera (arknights) +etou kanami +okita souji (koha-ace) +murkrow +puni (miku plus) +alia (mega man) +nanakusa hazuki +agent 4 (splatoon) +katharine ohare +shishiou (touken ranbu) +natsuki mikuru +shin'en-san (shin'en) +oguri cap (umamusume) +aerodactyl +rosmontis (arknights) +schwarz (skyline) (arknights) +harukaze doremi +nhk (voiceroid) +nearl (arknights) +maxie (pokemon) +shigure (fire emblem) +irako (kancolle) +setanta (fate) +sdf-1 +ray (yakusoku no neverland) +bloodhound (apex legends) +baltimore (azur lane) +melona (queen's blade) +ruthenium77's character +onigirya (nekomata okayu) +phanpy +blossom (ppg) +greythroat (arknights) +light dragon (zelda) +jungle cat (kemono friends) +hector (fire emblem) +arene (arknights) +letty whiterock +shuten douji (first ascension) (fate) +viktoriya ivanovna serebryakov +oyashio (kancolle) +nelson (azur lane) +cecilia (fire emblem) +hoshiguma yuugi (kimono) +lillie (anniversary 2021) (pokemon) +tsuyuri kumin +kasuga maru (kancolle) +seaport princess +kama (fate) +aiba uiha +sakurajima mai +askeladd +elu (nijisanji) +airi (ogami kazuki) +shokuhou misaki +trieste (azur lane) +tsona (nyantcha) +inkling player character +reinhardt (overwatch) +midway princess +hana (fire emblem) +mystia lorelei (bird) +mini cu-chan (fate) +arashi chisato +firis mistlud +baltimore (finish line flagbearer) (azur lane) +qiyana (league of legends) +tsunetsuki matoi +penguin 1-gou +ethel (xenoblade) +magilou (tales) +marianne von edmund (summer) +tinker bell (disney) +tar-21 (girls' frontline) +hagiwara yukiho +perseus (azur lane) +amane suzuha +xeno a +unown +paul bunyan (fate) +hoshikawa sara (1st costume) +puyo (puyopuyo) +warlock (ragnarok online) +monica von ochs +roboco-san (hoodie) +silica +franky (one piece) +tsumugi wenders +namine ritsu +altera (fate) +alice margatroid (pc-98) +neco-arc +arulumaya +tapu lele +kishin sagume +tai gong wang (fate) +rosalina +wachi yuri +ryugasaki rene +elven forest maker +hood (warship girls r) +chigusa asuha +seyren windsor +ikuno (darling in the franxx) +an-94 (silent rouge) (girls' frontline) +kamen rider zero-one +spas-12 (midsummer fruit) (girls' frontline) +g-self +baji keisuke +red riding hood (sinoalice) +blonde girl (popopoka) +maetel +gekota +hinamizawa hinami +axew +koizumi mahiru +einhart stratos +usada pekora (new year) +hatsune mikuo +mewtwo +shinguuji sakura +konno mitsune +falkner (pokemon) +euphyllia magenta +i-168 (kancolle) +naga u-chan +jing yuan +sawamura eijun +kasaki nozomi +kooh +tachikawa ayaka +poison ivy +kaminari qpi +kula diamond +utsugi yuuki +felsi rollo +kiryu kazuma +helm (aqua marine) (nikke) +inu sakuya (nejikirio) +akai haato (5th costume) +tama (aquarion) +koyanskaya (lostbelt beast:iv) (fate) +akatsuki kirika +courier (arknights) +hoshimachi suisei (shout in crisis) +rin (kemurikusa) +himiko (fate) +bard (final fantasy) +lakshmibai (fate) +gwen tennyson +cordelia (fire emblem) +brooke (mathias leth) +sieg (fate) +ein (blue archive) +rina (kemurikusa) +frog (chrono trigger) +terence t. d'arby +secelia dote +sakura chiyo +shimamura hougetsu +akiha rumiho +shinra tsubaki +naegi makoto +futami eriko +gundam barbatos +tachibana himeko +m1911 (girls' frontline) +arthur pendragon (fate) +anne bonny (fate) +saaya (kirome) +mikhail buran +asakura otome +hindenburg (azur lane) +sonetto (reverse:1999) +mephisto (arknights) +kriemhild (fate) +barghest (second ascension) (fate) +scolipede +takamachi nanoha (movie 1st mode) +sawa azusa +konan (naruto) +kampfer (mobile suit) +smol mumei +voyager (fate) +taliyah +kujou kazuya +rosa (arknights) +milfa (to heart) +tohsaka rin +king ghidorah +lunamaria hawke +ryou-ouki +oda nobukatsu (fate) +aino minako +mugetsu (touhou) +helena blavatsky (swimsuit archer) (fate) +catura (granblue fantasy) +ike (fire emblem) +campo flicker (kemono friends) +niko (oneshot) +pomni (the amazing digital circus) +spy (tf2) +peter griffin +tiffania westwood +beatrice (princess principal) +pikipek +sweep tosho (umamusume) +revenant (apex legends) +carnelian (arknights) +kimura kaere +anew returner +yoshinon +lisa (genshin impact) +tanigaki genjirou +hatsune miku +arisugawa natsuha +jaye (arknights) +mari (swimsuit) (blue archive) +omanyte +octoling girl +m1918 (girls' frontline) +joseph joestar (young) +watanabe kanako +rupee (nikke) +cure rhythm +seia (blue archive) +hercule barton +tailmon +dawn (palentine's 2021) (pokemon) +tanuki (kemono friends) +fou (fate) +seiun sky (umamusume) +heracles (fate) +higurashi kagome +indeedee +korok +inumuta houka +eve (chihuri) +karuizawa kei +gundam aerial rebuild +rime (cevio) +konpaku youki (ghost) +scene (arknights) +hanako (swimsuit) (blue archive) +precis neumann +adrien agreste +airani iofifteen +archeops +portgas d. ace +weno's blonde original character +yu mei-ren (swimsuit lancer) (fate) +hung (arknights) +raiden mei +meiou setsuna +kikyou (blue archive) +espurr +ayanami (kancolle) +ingrid brandl galatea +red fox (kemono friends) +ichinose kotomi +tina sprout +momomiya ichigo +aquila (a sip of sardegnian elegance) (azur lane) +yuzurizaki nero +mawile +mars (cookie) +uxie +hugo andore +matsuda chiyohiko +shigure (azur lane) +tsunashi takuto +kise yayoi +aizawa kazuha +rampart (apex legends) +pixie (megami tensei) +smeargle +baphomet (monster girl encyclopedia) +kanzaki akari +kurousagi (mondaiji) +europa (granblue fantasy) +mary read (fate) +mikazuki (kancolle) +poppy (pokemon) +gema +mukai takumi +muelsyse (arknights) +sailor jupiter +cloyster +sabito (kimetsu) +jack the ripper (fate/apocrypha) +germany (hetalia) +takashi shirogane +karin (bunny) (blue archive) +asta (honkai: star rail) +anna (fire emblem) +alphard (canaan) +sailor mercury +hime (kaibutsu oujo) +ujikintoki tamaryu +amae koromo +rikku (ff10) +charlotte corday (fate) +sayu (genshin impact) +rei (guilty gear) +hakuryuu (inazuma eleven) +lacus clyne +haruna (korezom) +theresa apocalypse (starlit astrologos) +minato aqua (aqua iro super dream) +skirk (genshin impact) +cagliostro (granblue fantasy) +leizi (arknights) +hazuki ren +bloody wolf (elden ring) +sewaddle +reid hershel +kuroko tetsuya +fuwa minato +shiny chariot +nowaki (kancolle) +tom nook (animal crossing) +inumi +jason (fate) +amelia wil tesla seyruun +mythra (xenoblade) +rajang +azurill +uraraka ochako +julius pringles +liepard +shinjo hinaki +takino tomo +scathach skadi (third ascension) (fate) +kitsu chiri +serizawa chikaru +tsurugi kyousuke +gomamon +tabitha (zero no tsukaima) +futami ami +selen tatsuki (1st costume) +zeke von genbu (xenoblade) +shimamura charlotte +walpurgisnacht (madoka magica) +cerberus (helltaker) +gangut dva (kancolle) +akabane youko +toph bei fong +cure sunshine +lucia (scott malin) +riley (pokemon) +yellow pikmin +reszurre +nogami aoi +enrico pucci +sabrina (pokemon) +layla (genshin impact) +oruyanke (shirakami fubuki) +miyako yoshika +nishizumi tsuneo +lily (gentsuki) +ibrahim (nijisanji) +executor (arknights) +himekuma ribon +shirma +hierophant green +arare (kancolle) +yuri seo +ikusaba mukuro +kurugaya yuiko +celestia ludenberg +avery (pokemon) +agnes tachyon (umamusume) +copano rickey (umamusume) +minamoto sakura +amahane madoka +ise kai ni (kancolle) +uguisumaru +misaka imouto +minazuki karen +lucia (punishing: gray raven) +akihime sumomo +honjou ruri +masaki tenchi +yotsuba alice +luciana mazzei +shindou chihiro +kei (m k) +snow white (disney) +yuki miku (2015) +mog +kinoshita hinata +elf (dragon's crown) +kamen rider wizard +kashiwaba tomoe +hoshino (young) (blue archive) +type 95 (summer cicada) (girls' frontline) +orange heart (neptunia) +labrys (persona) +leo (senran kagura) +moroboshi ataru +houshou (kancolle) +rita rossweisse (fallen rosemary) +yuuki-chan (kanabun) +pac-man +hero (dq4) +cutlass (girls und panzer) +malina (helltaker) +cinnabar (houseki no kuni) +ymir (shingeki no kyojin) +fujimaru ritsuka (male) +tenochtitlan (second ascension) (fate) +marie antoinette (swimsuit caster) (fate) +sailor chibi moon +yozora mel +bremerton (azur lane) +samurai (final fantasy) +nero (devil may cry) +nanami yachiyo +shinozaki sayoko +brook (one piece) +apollo justice +artoria caster (third ascension) (fate) +kotomine kirei +mudsdale +natsuumi manatsu +endou saya +takaya noriko +lucid (maplestory) +lennon +yuuka (track) (blue archive) +zhuge kongming (honkai impact) +tomitake jirou +g41 (girls' frontline) +ai-chan (honkai impact) +flowey (undertale) +dorothea arnault (plegian) +okajima rokuro +medusa (jashin-chan dropkick) +keqing (lantern rite) (genshin impact) +irys (hololive) +bremerton (kung fu cruiser) (azur lane) +swiftsure (azur lane) +indomitable (ms. motivationless maid) (azur lane) +armored aircraft carrier princess +team yell grunt +goku black +kamiki izumo +fergus mac roich (fate) +azelf +maribel hearn +noa (granblue fantasy) +daruk +t-800 +momosuzu nene +alex (street fighter) +alban knox +izumi koshiro +sawada tsunayoshi +kana (fire emblem) +vigna (arknights) +yui (princess connect!) +levy mcgarden +kisaragi chihaya +aizawa tomo +asmodeus (shinrabanshou) +assam (girls und panzer) +torracat +reiuji utsuho +natsu (blue archive) +a~chan +reno (biggest little cheerleader) (azur lane) +namba emi +gloria (summer 2021) (pokemon) +prishe +warlock (granblue fantasy) +fukuda haru +hyuuga hinata +kei (dirty pair) +nu gundam +dark willow +houshou marine (summer) +clumsy nun (diva) +asagumo (kancolle) +silverash (arknights) +dodrio +dagon (housamo) +roon (azur lane) +mae (fire emblem) +vayne (league of legends) +monica (little witch nobeta) +amamiya elena +monokuma +noir vesper +majokko (kancolle) +bikko +bb (bb shot!) (fate) +kyouka (princess connect!) +satyr (granblue fantasy) +konoe kanata +cure passion +tsukioka (40hara) +cross mirage +raising heart +miyanaga teru +aragaki shinjirou +garry (ib) +yanai ichiru +inuyama aoi +vex (league of legends) +kusuha mizuha +shion (no.6) +gold ship (run revolt launcher) (umamusume) +geodude +takanashi rei +raditz +roserade +hinata shouyou +tenkubashi tomoka +oikawa tooru (haikyuu!!) +margay (kemono friends) +jeanne d'arc alter (swimsuit berserker) (fate) +mast (nikke) +nidoran (female) +ereshkigal (fate) +prushka +olimar +elizabeth thompson +yuuka (blue archive) +saya (blue archive) +mejiro bright (umamusume) +ceres fauna (1st costume) +azki (3rd costume) (hololive) +zenno rob roy (umamusume) +watanabe you +heywood l. edwards (kancolle) +kyon's sister +ichijou hotaru +ichigo (darling in the franxx) +red girl (yuuhagi (amaretto-no-natsu)) +nina (breath of fire iii) +amagiri (kancolle) +shinomiya kaguya +yuudachi (shogun of snowballs) (azur lane) +hex maniac (pokemon) +tokarev (girls' frontline) +li xingke +inubouzaki itsuki +aikawa chinatsu +yuma kokohead +tadokoro megumi +viego (league of legends) +mine fujiko +pepe the frog +yakushiji saaya +gridman (ssss) +nu-class light aircraft carrier +enna alouette +gawain (fate) +mika (girls und panzer) +kuroka (high school dxd) +sheffield (kancolle) +peke +nanami chiaki +u-81 (azur lane) +pandora (p&d) +tamamo cat (third ascension) (fate) +suzu (torikissa!) +hatsune (princess connect!) +akuma (street fighter) +scorbunny +ning hai (warship girls r) +crocodile (one piece) +furby +sky high (tiger & bunny) +glowworm (warship girls r) +airi (the infernal temptress) +yamabuki inori +pai-chan (nao) +blaidd the half-wolf +falinks +otoshiro seira +shinouji matsurika +shinazugawa sanemi +hasshaku-sama +usami (danganronpa) +cure felice +shurelia (ar tonelico) +midorikawa ryuuji +artoria caster (swimsuit) (fate) +abe takakazu +kirche augusta frederica von anhalt zerbst +kedama (touhou) +terriermon +hayakawa aki +maria balthasar +lion (kemono friends) +umbrella (skullgirls) +ushiwakamaru (swimsuit assassin) (fate) +fang (arknights) +zaizen tokiko +iggy (jojo) +p7 (girls' frontline) +ibuki (street fighter) +robin (female) (summer) (fire emblem) +makihara shiho +higekiri (touken ranbu) +neneka (princess connect!) +kagome misaki +baltimore (muse) (azur lane) +adolf hitler +maizono sayaka +kureha yuna +bianca (pokemon) +frillish +emerald sustrai +sasa kazamori +seragaki aoba +kamizaki risa +sasha braus +paladin 2 (sekaiju) +nakahara komugi +hoshina tomoko +orsola aquinas +kiryuu michiru +zuihou (kancolle) +hasumi (blue archive) +vei (vtuber) (sky empress) +rally vincent +female gunner (dungeon and fighter) +kino aki +muse (amaburi) +okamura nao +toucannon +nagomi yui +alex (minecraft) +leysritt (fate) +dick gumshoe +meika mikoto +poo (mother 2) +matsuno todomatsu +aihara yuzu +oricorio +butterfree +shaymin (sky) +watanabe minori +souya ichika +usada pekora (5th costume) +keqing (genshin impact) +rpk-16 (girls' frontline) +cure spicy +seulbi lee +padparadscha (houseki no kuni) +kousaka kirino +nyarla (osiimi) +kazamaki matsuri +godot (ace attorney) +poliwhirl +syuen (nikke) +tsunade (naruto) +aek-999 (girls' frontline) +ohtsuki yui +etopen +amagase touma +hilda valentine goneril +kiss-shot acerola-orion heart-under-blade +kimura ryu +shishidou akiha +saotome haruna +gokou ruri +miorine rembran +weedy (arknights) +mireyu +dottore (genshin impact) +perfect cell +al azif +tang sanzang +yoshida ryouko +amazon (dragon's crown) +yo-class submarine +jessie rasberry +medb (alluring chief warden look) (fate) +yorick (shiori novella) +samurai (7th dragon series) +carpaccio (girls und panzer) +sena (blue archive) +luke triton +lady bat +momoi satsuki +admiral shiro (shino) +toxel +maomao (kusuriya no hitorigoto) +marin (umi monogatari) +howe (azur lane) +durin (arknights) +tennouji kotarou +vanilla (nekopara) +male doctor (arknights) +beanstalk (arknights) +karlach +nemo (fate) +sessyoin kiara (swimsuit mooncancer) +mash kyrielight (formal dress) +shimura tae +ben tennyson +heidimarie w. schnaufer +kama (swimsuit avenger) (third ascension) (fate) +takahashi keita (yakitomato) +kogitsunemaru +uruha rushia (school uniform) +blitzle +revolver knuckle +meowth +angela (seiken densetsu 3) +riselia ray crystalia +astaroth (shinrabanshou) +kakyoin noriaki +shinei nouzen +yamada maya (infinite stratos) +kama (swimsuit avenger) (fate) +grimm (rwby) +aisha landar +big sister (navigavi) +spearow +levia (closers) +shiki eiki +iris yuma +miyu (blue archive) +sophia (fire emblem) +kafka (honkai: star rail) +reisa (blue archive) +emma (yakusoku no neverland) +ka-class submarine +nyx (queen's blade) +ogiue chika +mickey mouse +sumeragi lee noriega +yuugenmagan +laura s. arseid +araiguma-san +shamal +vivillon +shin getter-1 +combusken +atago (school traumerei) (azur lane) +shizuka rin (1st costume) +nanakusa nichika +kyoko (kunio-kun) +austria (hetalia) +ganyu (china merchants bank) (genshin impact) +android 21 +futaba sana +sonozaki shion +ange katrina (1st costume) +gourai +digitan (porforever) +captain amari +yaeno muteki (umamusume) +keith claes +aquila (kancolle) +meowfficer (azur lane) +uta (one piece) +glycine bleumer +renamon +fu hua (shadow knight) +team aqua grunt +kinon bachika +mordred (swimsuit rider) (first ascension) (fate) +karasuma chitose +gypsy (ragnarok online) +hyoudou michiru +blue poison (shoal beat) (arknights) +neeko (aldehyde) +phosphophyllite +nagato yuki +surtr (liberte echec) (arknights) +chikorita +hoshimiya ichigo +umbreon +yajuu senpai +phichit chulanont +moon rabbit extra (touhou) +mayer (arknights) +aoba misaki +kris (deltarune) +akagi kai ni (kancolle) +ciela (yuuhagi (amaretto-no-natsu)) +oomuro hanako +milinda brantini +marvelous sunday (umamusume) +flonne +pantheon (league of legends) +will anthonio zeppeli +ichinose hajime +fujimaki (angel beats!) +shinsou hitoshi +snow white (sinoalice) +dusknoir +melony (pokemon) +ursaring +fang assassin irma +meguru (cookie) +super sonico +kazagumo (kancolle) +james (pokemon) +billy herrington +kanoe yuuko +tsurumaru kuninaga +victorious (kancolle) +grape-kun +takamori aiko +kashino (hot springs relaxation) (azur lane) +symmetra (overwatch) +shedinja +kamen rider fourze +cure sunny +aria wintermint +caustic (apex legends) +maga-g +atalanta alter (fate) +serena (pokemon) +takagaki kaede +oda nobunaga (maou avenger) (fate) +astolfo (sailor paladin) (fate) +martha (swimsuit ruler) (fate) +nihilego +matsumoto rise +00 gundam +wakamo (swimsuit) (blue archive) +kayoko (new year) (blue archive) +asashio kai ni (kancolle) +hulk +ryuugazaki rei +cyllene (pokemon) +aida rayhunton +doruji +suou tsukasa +pochimaru (vtuber) +march hare (alice in wonderland) +david king (dead by daylight) +minerva (fire emblem) +aizawa eiko +hunter (bloodborne) +urashima kotetsu +mikeneko (utaite) +mirror maiden (genshin impact) +ukuru (kancolle) +gardevoir +akagi (warship girls r) +chi lian (qinshi mingyue) +ikari shinji +futatsuiwa mamizou +furukawa nagisa +vei (vtuber) (4th costume) +ayamine kei +yan vismok +lich (granblue fantasy) +colorado (kancolle) +bertolt hoover +kousaka reina +nanami haruka +pyotr (madoka magica) +isuzu (kancolle) +silver chariot +corrin (female) (fire emblem) +kazooie (banjo-kazooie) +big bad wolf +kotonomiya yuki +kiichi hougen (fate) +noel vermillion +asuna (stacia) +izuna (shinrabanshou) +bumblebee (transformers) +kino makoto +alakazam +hitsugaya toushirou +gavis bettel +regice +devil (housamo) +otogi (blue archive) +micro uzi (girls' frontline) +katsuragi yako +alluka zoldyck +morgana (league of legends) +hououji fuu +lily (vocaloid) +glimmer (xenoblade) +gold ship (racehorse) +trainer (umamusume) +nagant revolver (girls' frontline) +komori kinoko +k11 (girls' frontline) +alear (fire emblem) +ragna the bloodedge +lance (pokemon) +tonbokiri (touken ranbu) +kazemaru ichirouta +kamisato ayaka (springbloom missive) +yoshinaga koi +saint-louis (holy knight's resplendence) (azur lane) +mienshao +kawasumi ayako +gokotai's tigers +q (control) +white rabbit (alice in wonderland) +primm +riyo servant (bunnygirl) (fate) +asagami fujino +shiragiku hotaru +melleau +sakyumama (kedama milk) +houjou satoko +yukihiro ayaka +shiranui (azur lane) +raiden mei (herrscher of origin) +fujinami (kancolle) +skadi (arknights) +shirasagi chisato +iguro obanai +vanessa (kof) +loremaster (helltaker) +halsin +saegusa akina +kamen rider den-o +yuuki haru +yellow (pokemon) +kim dokja +oshino meme +dark pit +lamia loveless +miyabi (senran kagura) +matsuura kanan +mousse (ranma 1/2) +tsukioka kogane +weezing +frostleaf (arknights) +shiori (kamioka shun'ya) +kisara (yu-gi-oh!) +wolf o'donnell +cure heart +commander (girls' frontline) (xiujia yihuizi) +kay faraday +kiyohime (swimsuit lancer) (third ascension) (fate) +mima sachi +rumia tingel +kirisawa juuzou (character) +kaidou (one piece) +metapod +melone +roy (pokemon) +riko (machikado mazoku) +priest (ragnarok online) +navia (genshin impact) +laharl-chan +matsuo chizuru +xuhuai (the legend of luoxiaohei) +marui hitoha +clare (claymore) +mystia lorelei +kotori (takanashi kiara) +shishiro botan +fungi (genshin impact) +mr. nothing (arknights) +ryofu housen +asakura rikako +ganondorf +mochizuki ryouji +irma (queen's blade) +kishinami hakuno (female) +yukihana lamy (new year) +hisuian growlithe +ranni the witch +twin turbo (umamusume) +nicholas d. wolfwood +fudou akira +kannagi itsuki +kristen (arknights) +poro (league of legends) +z3 max schultz (kancolle) +youjo (creek (moon-sky)) +hatsutori hajime +tiki (young) (fire emblem) +uzumaki kushina +lize helesta (1st costume) +time mage +shizuru (princess connect!) +sanzen'in nagi +solgaleo +baobhan sith (fate) +okada izou (fate) +yoshizawa kasumi +k' (kof) +fighter (dungeon and fighter) +renga (yakihebi) +matou sakura +pignite +aida mana +son goten +hoshizora miyuki +abelia (ogami kazuki) +edytha rossmann +isonade orca +siege (arknights) +phosphophyllite (ll) +sage (dq3) +warrior of light (ff14) +medjed (fate) +monkey d. luffy +mitarashi anko +kudelia aina bernstein +inaba himeko +ashwatthama (fate) +mon3tr (arknights) +kuchiki rukia +kazamaki matsuri (female) +sakayanagi arisu +zodiac (sekaiju) +agnes claudel +kagerou (kancolle) +centorea shianus +momoyama mirai +fu hua (night squire) +humboldt penguin (kemono friends) +prinz eugen (azur lane) +inferna dragnis +nea (chihuri) +ayanami (retrofit) (azur lane) +monk (final fantasy) +kiryuu kaoru +lyle dylandy +tanamachi kaoru +astolfo (saber) (third ascension) (fate) +marcy wu +harukawa maki +sora harewataru +scyther +nanu (pokemon) +funada kiito +helm (nikke) +black cat d.va +jougasaki mika +yoshino chidori +tatl +yatogami tooka +destiny gundam +arika yumemiya +momonosuke (one piece) +sayaka (saru getchu) +nayuta (chainsaw man) +gotland andra (kancolle) +kazama jin +iruma miu +pinocchio (sinoalice) +dekomori sanae +lava the purgatory (arknights) +joseph joestar +signum +ogino chihiro +uzaki yanagi +shimamura uzuki +korea (hetalia) +vira (summer) (granblue fantasy) +opal (pokemon) +mabel pines +tsubaki yayoi +takamachi nanoha (sacred mode) +nishizawa ayumu +clark kent +gliscor +chise (blue archive) +galarian rapidash +hiraga saito +minos (jashin-chan dropkick) +shinonono houki +gaara (naruto) +kamishiro natsume +no.21: xxi (punishing: gray raven) +amamiya yuuko +lu guang +sakura miku +suzuhara lulu (1st costume) +yuel (granblue fantasy) +antonio salieri (second ascension) (fate) +brown thoroughbred (kemono friends) +kaede (blue archive) +hiiragi shinoa +asirpa +canal vorfeed +tonegawa yukio +luca truelywaath +mujina +izumi kanagi +minamoto no raikou (swimsuit lancer) (first ascension) (fate) +ayesha altugle +witch (puyopuyo) +utsushimi kemii +kamen rider faiz +yuri plisetsky +elizabeth (gintama) +nakasu kasumi +symboli rudolf (umamusume) +wailord +pincurchin +hino akane (smile precure!) +bugsy (pokemon) +tsugumi seishirou +elira pendora +takahashi reiko +ayanami (azur lane) +cure star +nagant revolver (mod3) (girls' frontline) +kongou mitsuko +gawr gura +gray fullbuster +imai lisa +kakuna +ogata rizu +selen tatsuki +superb bird-of-paradise (kemono friends) +red xiii +izetta +hilda (pokemon) +hirasawa susumu +hoshikawa sara (4th costume) +captain liliana +candace (genshin impact) +pom-pom (honkai: star rail) +morino rinze +murasaki (senran kagura) +daughter (yoru mac) +asamiya athena +rain mikamura +noumi kudryavka +sakuya (p&d) +zentreya +oribe yasuna +fugee (granblue fantasy) +reindeer (kemono friends) +female commander (girls' frontline) +meteor (arknights) +kii-kun (agnes tachyon) (umamusume) +deirdre (fire emblem) +foo fighters +invincible dragon (last origin) +rukino saki +raboot +r dorothy wayneright +hoshino (blue archive) +mii (nintendo) +ryoubi (senran kagura) +sinon (solus) +ump45 (mod3) (girls' frontline) +tedeza rize +damian desmond +gladiolus amicitia +sumeragi hakua +kiyoshimo (kancolle) +yuuki anju +mutsu (kancolle) +numako (pizza kanon) +modernia (nikke) +sui (isekai ojisan) +amaterasu (ookami) +mitsumine yuika +northern white-faced owl (kemono friends) +akizuki kanna +luna (konosuba) +weiss schnee +bastet (p&d) +tanigawa yuzu +asakura toru +eishin flash (umamusume) +sashou mihiro +minami chiaki +sendai hakurei no miko +erufuda-san +miyazawa kengo +souma hiroomi +crona (soul eater) +momoe maria +li sushang (jade knight) +murasa minamitsu +yukihana lamy (4th costume) +judy hopps +michiru (blue archive) +flayn (fire emblem) +turo (pokemon) +fujii tomo +minior +miriam (pokemon) +jecht +mega charizard x +kamen rider kiva +so-class submarine +sawachika eri +ayane (doa) +3.1-tan +tsugikuni yoriichi +petra leyte +siduri (fate) +fairy miku (project voltage) +uchiha sarada +konpaku youmu (ghost) +bulma +wamdus (granblue fantasy) +yuki (touhou) +achilles (fearless diver) (fate) +kumatora +kazami kazuki +hibiki dan +piers (pokemon) +sylvanas windrunner +switzerland (hetalia) +nanael (queen's blade) +saint-louis (azur lane) +hino rei +officer caitlyn +nagi (kannagi) +vittorio veneto (warship girls r) +turbo byakuren +bebinn +captain nemo (fate) +dr. light (mega man) +hinata hideki +luna (my little pony) +zentreya (dragon) +amamiya ren +miwa mitsune +fujiwara hajime +rana (queen's blade) +xenomorph +selena (fire emblem fates) +sendai kai ni (kancolle) +kiyama harumi +naganami (kancolle) +tachibana rei +charlotte e. yeager +sugiura ayano +kujo jotaro +subaru duck +furfrou +mc lita +shishiro botan (1st costume) +naka kai ni (kancolle) +shiomiya shiori +tatsumi kon +shinx +sakuraba aoi +hayakawa tazuna +hinata (swimsuit) (blue archive) +latios +izayoi miku +angelina (distinguished visitor) (arknights) +wonder woman +neo politan +bremerton (scorching-hot training) (azur lane) +white heart (neptunia) +konnosuke +kamen rider vice +prince of samantoria +eldar +klavier gavin +ghiaccio +zaku ii s char custom +jakuri (ar tonelico) +norea du noc +miyoshi karin +rody roughnight +yusa kozue +yukikaze (azur lane) +admiral graf spee (girl's sunday) (azur lane) +minah (chaesu) +medea (fate) +maple (bofuri) +miyu edelfelt (beast style) +shirokuma-san (drawingchisanne) +keqing (opulent splendor) (genshin impact) +suzushina yuriko +makino ruki +sansei muramasa +nachi kai ni (kancolle) +ryuk +nui sociere (1st costume) +chloe (pokemon) +yunoha thrul +alisa (girls und panzer) +p-head producer +amano pikamee +togami byakuya (danganronpa 2) +houjuu nue +takasugi shinsaku (fate) +gemini sunrise +pharah (overwatch) +asui tsuyu +oshimizu nako +klee (genshin impact) +gotland (kancolle) +stellar loussier +old man (guin guin) +rose (tales) +sananana (cookie) +ol-chan (oouso) +maria renard +harpy (puyopuyo) +richelieu (azur lane) +maruyama saki +serizawa fumino +tsukumo yatsuhashi +zombie fairy (touhou) +trucy wright +misumaru yurika +ashley (warioware) +samidare (kancolle) +konoe konoka +noivern +brigid (fire emblem) +shelly (pokemon) +crested porcupine (kemono friends) +moroboshi kirari +shirakami fubuki (3rd costume) +slime (dragon quest) +kinukawa chinatsu +xatu +i-19 (kancolle) +himiko (first ascension) (fate) +kotobuki tsumugi +frey (rune factory) +pavolia reine +kitakami kai ni (kancolle) +rex lapis (genshin impact) +signora (genshin impact) +gouenji shuuya +duck (duck hunt) +michelle (bang dream!) +avatar (wow) +lord dearche +saionji hiyoko +tsuruki shizuka +ichimaru gin +aoba moca +the baddest ahri +coco (yes! precure 5) +allelujah haptism +suenaga mirai +hinomori shiho +kidouko (zakusi) +ash ketchum +t-elos +chloe valens +corona timir +sakura akari +yamashita jiro +hoshino yumemi +ichihime +rapi (nikke) +passenger pigeon (kemono friends) +erstin ho +kaiki deishuu +kotetsu isane +haro +wailmer +son goku +ariel (disney) +boo (mario) +annie (skullgirls) +minowa gin +aegir (azur lane) +sampo koski +furina (genshin impact) +kaleina (ricegnat) +minegishi ayano +kawai miruku +isshiki akane +sasaki raito +yelan (genshin impact) +nina kosaka +terakomari gandezblood +chocobo +ranpha franboise +sakagami ayumi +minase akiko +kita hinako +gummy (arknights) +niwatari kutaka +aizawa hikaru +yamanaka sawako +morgan (female) (fire emblem) +sherlock shellingford +enemy aircraft (kancolle) +takakura kanba +sango (pokemon) +x-23 +growlithe +kenmochi touko +tokitsukaze (kancolle) +anti (ssss.gridman) +golbat +yukikaze panettone +beelzebub (umineko) +kazami yuuka (pc-98) +bangboo (zenless zone zero) +dante (devil may cry) +anastasia (swimsuit archer) (third ascension) (fate) +yakui +kitashirakawa anko +veigar +miyako (hidamari sketch) +caesar anthonio zeppeli +gambier bay (kancolle) +oinomori may +lord el-melloi ii +yukine chris +trunks (dragon ball) +melpha (queen's blade) +eugeo +arche klein +marikawa shizuka +ayase ena +yamagishi yukako +ojiro mashirao +koito yuu +cthulhu +natsugumo (kancolle) +merurulince rede arls +ricotta elmar +hitmonlee +deoxys +emia renya +chaika bogdan +sonken +houjou hibiki +shirogane noel (1st costume) +isabelle (animal crossing) +minato aqua (1st costume) +hata no kokoro +cure princess +vittorio veneto (azur lane) +sarutobi ayame +medea (lily) (fate) +kogasa-san's father +musashi kai (kancolle) +helena blavatsky (third ascension) (fate) +nagi seishirou +sesshoumaru +olga discordia +cu chulainn (caster) (fate) +mime jr. +natsuki kruger +niyon (granblue fantasy) +hortense +pramanix (arknights) +melissabelle +saeba ryou +hayashimo (kancolle) +hatake kakashi +mantine +krizalid +sharpedo +frisk (undertale) +kurusu shou +hellhound (monster girl encyclopedia) +munetani mashiro +hoshino ichika (collar x malice) +sanya v. litvyak +arisugawa otome +ginga nakajima +horkeu kamui +corphish +sei shounagon (swimsuit berserker) (fate) +seaport water oni +kodama +kageyama tobio +chibi usa +fran (ff12) +finana ryugu (1st costume) +yonah +fuyukawa kagari +yuni (precure) +passionlip (fate) +terry bogard +windblade +ibuki tsubasa +crystal exarch +crow (gravity daze) +elysia (honkai impact) +kawakami mai +enokawa miyu +chitanda eru +hitotsuyanagi riri +kaneki ichika +takasugi shinsuke +hyper blossom +lala satalin deviluke +martina (dq11) +murasaki shikibu (swimsuit rider) (first ascension) (fate) +athena (p&d) +izumi (swimsuit) (blue archive) +hitoyo (baffu) +akai haato (hololive summer 2019) +neuvillette (genshin impact) +mari (headspace) (omori) +eris (asobi ni iku yo!) +gun devil (chainsaw man) +archetto (arknights) +lecia (granblue fantasy) +mito anji +someya mako +psyduck +pansear +rainbow mika +hibiki wataru +starscream +belfast (iridescent rosa) (azur lane) +uka-no-mitama-no-kami (inakon) +luke skywalker +carro pino +bao (vtuber) +kotona elegance +marie antoinette (swimsuit caster) (third ascension) (fate) +benienma (fate) +ozymandias (fate) +felicia (vampire) +nagato (kancolle) +ikari gendou +anne bonny (swimsuit archer) (fate) +tada riina +ashiya douman (fate) +ponyo +nikola tesla (fate) +le malin (azur lane) +hiei (kancolle) +tamatsukuri misumaru +jean (gunnhildr's legacy) (genshin impact) +kisaragi kai ni (kancolle) +kirisame marisa (pc-98) +sakura kyoko +lycanroc +vert (neptunia) +satou sasara +nadeko (cookie) +whisper (youkai watch) +unicorn gundam +kiana kaslana (herrscher of finality) +shion uzuki +tayuya (naruto) +medama oyaji +elizabeth bathory (fate) +chloe von einzbern +dunkerque (azur lane) +taira no fumikado +nitta minami +tennouji rina +plumeria (fire emblem) +abarai renji +saaya (suisei no gargantia) +yano erika +oura rukako +matikane tannhauser (umamusume) +fate testarossa (lightning form) +hisakawa hayate +tsunami jousuke +honolulu (azur lane) +gigi andalusia +hanami kotoha +haru (tsuritama) +kama (swimsuit avenger) (second ascension) (fate) +colonel olcott (fate) +sherry birkin +bartz klauser +kana anaberal +mikogami riko +nakiri ayame (3rd costume) +shizuku (kantoku) +muska +sakaki shizuka +isokaze (kancolle) +wishiwashi +sakuna-hime +ryouna (senran kagura) +lorenz hellman gloucester +quna (pso2) +moro no ichizoku +type 00 takemikazuchi +saki (blue archive) +suzuki ayane +sonia (pokemon) +jounouchi katsuya +ralsei +satsuki (blue archive) +nanase kurumi (menhera-chan) +edogawa conan +okumura haru +caitlyn (league of legends) +eimi (blue archive) +suzuki arisa +riza wildman +pastel ink +hinanawi tenshi +eve santaclaus +okita sawa +baltimore (black ace) (azur lane) +kanzaki aoi (kimetsu no yaiba) +marisa (fire emblem) +ranger (kancolle) +kafu (cevio) +toyokawa fuka +azusa mifuyu +yasuhara ema +kamen rider hibiki +shinshuu maru (kancolle) +fu hua (herrscher of sentience) +benikurage (cookie) +futaba rio +agnes oblige +osaki amana +shiraishi minoru +flareon +higuchi kaede +houston (kancolle) +maylene (pokemon) +mash kyrielight (dangerous beast) +shigure kai ni (kancolle) +guillotine cross (ragnarok online) +takane lui (1st costume) +shinano (dreamy white sands) (azur lane) +kyouka (halloween) (princess connect!) +nilou (genshin impact) +fuecoco +delia ketchum +ots-14 (girls' frontline) +ookami mio (hololive summer 2019) +niimi kaoru +wakabayashi iori +meruccubus (merunyaa) +sin mal +miia (monster musume) +mof's silver haired twintailed girl +hermione (azur lane) +gundam exia +peni parker +mao (disgaea) +protagonist 2 (housamo) +black sister +mage (dq3) +i-504 (kancolle) +amemiya sekira +darth vader +lily bloomerchen +bennett (genshin impact) +lessar +ikezawa kazuma +araragi tsukihi +trunks (future) (dragon ball) +cure moonlight +nakahara masaru +kurebayashi juri +john rottweil (dickfish) +tatebayashi sakurako +kagero (fire emblem) +laffey (retrofit) (azur lane) +serika (new year) (blue archive) +lei lei +crimson viper +sakura ichiko +mika (blue archive) +ebihara naho +rory mercury +hinata hajime (awakened) +fidough +raphael kirsten +hypno +arisugawa himari +illyasviel von einzbern (swimsuit archer) (second ascension) +jean pierre polnareff +mizunashi akari +godzilla (shin) +fuyuki minami +nekomusume (gegege no kitarou 5) +grovyle +carcano m1891 (girls' frontline) +shiota nagisa +sakaki natsuki +mutio +nishizumi miho +lain paterson +liv: empyrea (punishing: gray raven) +hotarumaru +amatsuka mao +kokonose haruka +shirakawa kotori +tighnari (genshin impact) +shirogane tsumugi +rya (elden ring) +maple (nekopara) +tomoe gozen (swimsuit saber) (fate) +nina williams +a.b.a +yamanaka ino +bunny girl (yuuhagi (amaretto-no-natsu)) +sakaki yumiko +cabba +yoshizawa sumire +silva (granblue fantasy) +juniper actias +ada wong +shirakami fubuki (7th costume) +oozora subaru (5th costume) +mist (fire emblem) +female saniwa (touken ranbu) +female assassin (fate/zero) +joshua rosfield +inaba rinne +cheshire (summery date!) (azur lane) +chosen undead +nightingale (arknights) +seteth (fire emblem) +kujou sara +yuno (hidamari sketch) +susie (kirby) +monoyoshi sadamune +margaret (persona) +narberal gamma +murasaki shikibu (fate) +pidgeot +hoshino (girls und panzer) +spider-man (2099) +amayadori machi +pela (honkai: star rail) +yashajin ai +asagiri asagi +yadomi jinta +izumi sagiri +uboa +milfeulle sakuraba +m950a (girls' frontline) +feebas +maekawa miku +matsudaira touko +cyrus (pokemon) +yatonokami kanata +hasumi (track) (blue archive) +shimizudani ryuuka +sun-d +chatot +undine (guilty gear) +kisaki (blue archive) +shinoda hajime +kazama akira +katou hazuki +kanon (umineko) +yaoyorozu momo +arurandeisu +iris wilson +commander (azur lane) +le temeraire (azur lane) +senkawa chihiro +bastet (houtengeki) +feena fam earthlight +justin (grandia) +ditto +nanna (fire emblem) +touyoko momoko +bruno (yu-gi-oh!) +hazuki (tsukuyomi) +cure parfait +fa yuiry +morgan (male) (fire emblem) +anzu (ensemble stars!) +natsuiro matsuri (hololive summer 2019) +mutsuki (blue archive) +tap dance city (umamusume) +mononobe kyoma +kujou karen +spas-12 (girls' frontline) +ericht samaya +kurashima chiyuri +shigure ui (vtuber) +parasect +ifrit (sunburn) (arknights) +sharena (fire emblem) +kamen rider geats +maria traydor +wendy o. koopa +shiina mashiro +sasaki chiho +cu chulainn (fate) +satan (umineko) +dlanor a. knox +ijichi seika +anabuki tomoko +hitori bocchi +kurama norihito +sukoya kana (1st costume) +kirisame marisa +fall guy +jinbe (one piece) +mika melatika +harusaki nodoka +komiya kaho +hanekawa tsubasa +red hood (nikke) +kouhai-chan (tawawa) +honda tohru +kamado tanjirou +hanamichi ran +jeanne d'arc (swimsuit archer) (fate) +yae sakura (goushinnso memento) +yuri leclerc +kunou kodachi +constantine xi (fate) +barbara parker +rosemi lovelock +mysterious heroine x alter (second ascension) (fate) +setsuna (fire emblem) +zuikaku (azur lane) +sp//dr +code: nemesis (elsword) +chikuma kai ni (kancolle) +augustine sycamore +rita rossweisse (umbral rose) +iori shirou +sekiro +stoutland +harukaze (kancolle) +senzaki ema +lyza (made in abyss) +chomusuke +koseki bijou +black widow +dorothy (arknights) +akitsushima (kancolle) +miyamoto musashi (second ascension) (fate) +barnaby brooks jr. +arle nadja +hisui (cookie) +sen no rikyu (fate) +asseylum vers allusia +clive rosfield +komatsu ibuki +black rock shooter (character) +kitahara mio +karibuchi hikari +gneisenau (azur lane) +nikka edvardine katajainen +oomori yuuko +miku (darling in the franxx) +sin kiske +himeyuri ruri +zinnia (pokemon) +kosegawa shiromi +fujisaki chihiro +super sass (girls' frontline) +higashikata josuke (jojolion) +cecilia lynne adelhyde +diamant (fire emblem) +cinder fall +american beaver (kemono friends) +yamin (cookie) +toramaru shou +raoh (hokuto no ken) +richelieu (fleuron of the waves) (azur lane) +kizuna akari (tsubomi) +wolf-chan (wataame27) +black jaguar (kemono friends) +ceobe (summer flowers) (arknights) +taki eri +ushiromiya lion +reze (chainsaw man) +pawmi +malkuth (project moon) +kaname buccaneer +castform +ark royal (azur lane) +raynare +magolor +enjoji michiru +coyote (kemono friends) +kyogre +demitri maximoff +tokunaga (tales) +yuuki aoi +vector (girls' frontline) +wang yuanji +isara mao +petra ral +gwendolyn (odin sphere) +smoker (one piece) +andou mahoro +trubbish +power (chainsaw man) +white mage (fft) +genjii (touhou) +small lady serenity +mihono bourbon (umamusume) +hera (p&d) +orihara kururi +genryuusai maki +arcade sona +yuuri (saikin yatotta maid ga ayashii) +jirachi +stheno (fate) +blitzen +nappa +miyamoto musashi (swimsuit berserker) (first ascension) (fate) +caterpie +ousaka shizuku +childhood friend (ominaeshi) +kusuriuri (mononoke) +male priest (dungeon and fighter) +blue mary +aki rosenthal +ieiri shoko +bb (swimsuit mooncancer) (third ascension) (fate) +emma millstein +houndour +setra +li meiling +sr-3mp (girls' frontline) +belial (granblue fantasy) +oz (genshin impact) +souya agb (kancolle) +rhydon +alolan marowak +kisaragi (azur lane) +lauda neill +sayo samonji +shirabe ako +aragami oga +supply depot princess +glaucus (arknights) +fennec (kemono friends) +jade leech +sage (sonic) +hagikaze (kancolle) +uiharu kazari +golden delmo +ning hai (summer hunger) (azur lane) +alicia testarossa +rainbow dash +meta knight +m. bison +kurokawa eren +shiftry +shirayuki chiyo +sakurai momoka +hoshimachi suisei (maid) +chloe lemaire +sasaki kojirou (fate) +mitsui hisashi +cure prism +red (sygna suit) (pokemon) +alisa southerncross +bongo cat +shiina yuika (1st costume) +asakura ryouko +mai natsume +hiodoshi ao +izumi kanata +qin liangyu (fate) +plasma-chan (kancolle) +minami haruka +little illustrious (azur lane) +detective pikachu (character) +suzuki iruma +freija crescent +galuf halm baldesion +tachibana makoto +fukuda noriko +hayate immelmann +haaton (akai haato) +suzumiya haruhiko +yuugumo kai ni (kancolle) +jean roque lartigue +onsokumaru +kokomine cocona +veronica (fire emblem) +suomi (korvatunturi pixie) (girls' frontline) +nakajima youko +noshiro kai ni (kancolle) +richard (tales) +scanty (psg) +rosehip (girls und panzer) +raising heart (standby mode) +dodoco (genshin impact) +regirock +kongou (azur lane) +weather report +pavolia reine (2nd costume) +kurodani yamame +holo +turtwig +satsuki (kancolle) +heracross +tsuchinoko (kemono friends) +2p (nier:automata) +jean (sea breeze dandelion) (genshin impact) +lize helesta +yotarou (aoki hagane no arpeggio) +breloom +kashima yuu +minato aqua (school uniform) +majin gappa +cure grace +oomuro sakurako +makishima saori +roger (guilty gear) +kitashirakawa tamako +katou danzou (fate) +shirakami fubuki +yamada anna +green (pokemon) +fujimaru ritsuka (female) (tropical summer) +dianna soreil +sessyoin kiara (swimsuit mooncancer) (second ascension) +z23 (azur lane) +crono (chrono trigger) +murakumo (kancolle) +sutera (granblue fantasy) +octopus devil (chainsaw man) +barbie (character) +l (death note) +hinamori amu +mustard seeds +nishi yuuko +eva 00 +nitocris alter (fate) +dunkerque (summer sucre) (azur lane) +morpeko (hangry) +solomon (fate) +kunikida hanamaru +tamade chiyu +hikami sumire +jeanne d'arc alter (avenger) (third ascension) (fate) +claire francois +noel (cookie) +tweyen (eternal's summer vacation) (granblue fantasy) +villetta nu +orchis +hata-tan (rui (hershe)) +pyonta +hobby (azur lane) +elemental master (elsword) +noshiro (kancolle) +orga itsuka +fudou akio +jeanne d'arc alter (avenger) (fate) +kamiki sekai +albedo (genshin impact) +kayoko (blue archive) +korra +liko (pokemon) +tachibana yuu (yakitomato) +futaki kanata +minamisawa atsushi +fuuka (blue archive) +garen (league of legends) +selene (pokemon) +pascal (tales) +zetton +istina (arknights) +commander (girls' frontline) +flynn scifo +zhu bajie +shiratama mikan +lisa lisa +cure papaya +penny polendina +super sailor mercury +wooloo +nidoking +renee (negative hero) +armin arlert +yamato (uchuu senkan yamato) +serra (fire emblem) +gyarados +wolfrun +demi-fiend +kayneth el-melloi archibald +tsumura tokiko +kurokami fubuki +richelieu (warship girls r) +angelise ikaruga misurugi +laundry dragonmaid +nakai hisao +jet black +hitotsubashi yurie +okita j. souji (first ascension) (fate) +akigumo kai ni (kancolle) +udagawa ako +hot pants (sbr) +utsumi shou +waka (yuuhagi (amaretto-no-natsu)) +fate testarossa (true sonic form) +archer (fate) +okonogi yuuko +loona (helluva boss) +sakurakouji kinako +rasis +knight (ragnarok online) +pink (ohasi) +alisaie leveilleur +kohinata aoi (dokidoki sister aoi-chan) +hakumen +pekoyama peko +applejack +baselard +celica a. mercury +alice (alice in wonderland) +marnie (omoide no marnie) +ex-rumia +q-bee +sophocles (pokemon) +hayami saori +elfilin +luigi torelli (kancolle) +kamado nezuko +mage (disgaea) +trung trac (fate) +nazuna (hidamari sketch) +doge +elysium (arknights) +rosia (show by rock!!) +ak-15 (girls' frontline) +shirogane miyuki +gulpin +ho'olheyak (arknights) +hoshimachi suisei (3rd costume) +iihara nao +natsumi (date a live) +angela balzac +suruga (azur lane) +devil mercy +barasuishou +floyd leech +meiko (vocaloid3) +mega rayquaza +hod (project moon) +leorio paladiknight +moltres +sherlock holmes (fate) +haku (p&d) +springfield (stirring mermaid) (girls' frontline) +kongiku +rensouhou-kun +jabberwock (monster girl encyclopedia) +mightyena +robin (male) (fire emblem) +nyx (fire emblem) +ebisuzawa kurumi +ampharos +gawr gura (1st costume) +bokuto koutarou +shinku +mitsumine mashiro +son biten +laegjarn (fire emblem) +dendra (pokemon) +bronya rand +martha (fate) +uzaki tsuki +tsuji renta +velouria (fire emblem) +shigure kai san (kancolle) +johnny joestar +teletha testarossa +natsuki subaru +karin (p&d) +kero +female slayer (dungeon and fighter) +tennousu athena +vox akuma (1st costume) +uchiha itachi +peacock (skullgirls) +byleth (female) (summer) (fire emblem) +honolulu (summer accident?!) (azur lane) +formidable (the lady of the beach) (azur lane) +sunny (omori) +meika hime +harmony (splatoon) +arezu (pokemon) +tenjin kotone +nagase kaede +winter soldier +nishino flower (umamusume) +minami koharu +tenjou utena +kirby +tokido saya +meinya (made in abyss) +yuuki mikan +laguna loire +maruzensky (umamusume) +kuya (nu carnival) +cure majesty +tsukamoto yakumo +united kingdom (hetalia) +rookidee +admiral (kancolle) +ushiromiya ange +isolated island princess +vignette tsukinose april +mejiro mcqueen (ripple fairlady) (umamusume) +avatar (lineage 2) +luke fon fabre +kine (kirby) +undyne +shishiou no mofumofu +fukiyose seiri +sirius symboli (umamusume) +falulu +nishizono shinsuke +saemonza (girls und panzer) +crusch karsten +oribe shiori +pathfinder (apex legends) +captain falcon +dark konoha +hat kid +san diego (azur lane) +otonashi kyouko +kageyama ritsu +raiden mei (herrscher of thunder) +luna (sailor moon) +togekiss +tokoyami towa (school uniform) +hishikawa rikka +yuzuki yukari (onn) +little red riding hood (grimm) +graham aker +valvatorez (disgaea) +wakamiya eve +kurohanya (niliu chahui) +alice (pandora hearts) +secretary-san (zannen onna-kanbu black general-san) +emolga +professor shinonome +yayaka +isagi yoichi +anastasia (swimsuit archer) (fate) +harley quinn +fennekin +texas (arknights) +charlotte (genshin impact) +tk (angel beats!) +brighid (xenoblade) +katsura kotonoha +cure whip +hisuian zoroark +okazaki tomoya +observer alpha (azur lane) +ophilia clement +cure ange +aubrey (omori) +yesod (project moon) +senoo aiko +sheep nun (diva) +high priest (arknights) +dr. eggman +cure berry +ibaraki douji (touhou) +baron bunny (genshin impact) +mp5 (girls' frontline) +daiwa scarlet (scarlet nuit etoile) (umamusume) +doseisan +maka albarn +av-98 ingram +astesia (starseeker) (arknights) +delcatty +murasaki shion (hololive summer 2019) +kisaragi honey +rotom phone +special week (hopping vitamin heart) (umamusume) +theresa apocalypse (twilight paladin) +super pochaco +palutena +hoshino ruri +naomi (girls und panzer) +magatsuchi shouta +ymir (queen's blade) +kitajima sara +esper nyanko +may (spring 2021) (pokemon) +shouhou (azur lane) +lily (ender lilies) +sans +melia antiqua +bergman's bear (kemono friends) +auruo bossard +skadi the corrupting heart (sublimation) (arknights) +agent 8 (splatoon) +mako-chan (minami-ke) +saint tail +munchlax +mashiro (swimsuit) (blue archive) +zooey (granblue fantasy) +karol capel +paimon (genshin impact) +shiramine rika +wizard (ragnarok online) +ogerpon (teal mask) +sariel (touhou) +kamisato ayaka +daiyousei +knuckles the echidna +sakura bakushin o (umamusume) +talim +natasha romanoff +diana (league of legends) +katsushika hokusai (fate) +fafnir (maidragon) +meteion +luxray +karyl (summer) (princess connect!) +sakurano mimito +kurosaki chitose +caren hortensia (amor caren) (second ascension) +kel (omori) +georgette lemare +ringo-chan (otokuyou) +sex pistols (stand) +agent 3 (splatoon) +euryale (fate) +lokulo-chan +yui (sao-alo) +graf zeppelin (azur lane) +vittorio veneto (the flower of la spezia) (azur lane) +nagasaki soyo +duraludon +kimijima sara +artoria pendragon (alter swimsuit rider) (third ascension) (fate) +yggdrasil (granblue fantasy) +hatsuse izuna +miakis (suikoden) +grenville (azur lane) +javelin (beach picnic!) (azur lane) +bettie (pokemon) +nijino yume +mikoshiba mikoto +shishio chris (3rd costume) +celes chere +lili (tekken) +ryuuguu rena +aubrey (faraway) (omori) +dragoon (final fantasy) +isaki kaname +sailor saturn +raven (dc) +annie leonhardt \ No newline at end of file diff --git a/extensions/sd-danbooru-tags-upsampler/tags/copyright.txt b/extensions/sd-danbooru-tags-upsampler/tags/copyright.txt new file mode 100644 index 0000000000000000000000000000000000000000..32648bb6cf1257693671b64e5230f21f889dacc0 --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/tags/copyright.txt @@ -0,0 +1,3439 @@ +kamen rider dcd +idolmaster 2 +immaterial and missing power +chikanoko +cream lemon +sengoku otome +vampire (game) +suigetsu +suicide squad +youkai watch +the witcher 3 +kinnikuman +toriko (series) +bokutachi wa benkyou ga dekinai +mahou sensei negima! +guilty gear 2 +bra-ban! +trigun stampede +luck & logic +pretty rhythm +elona +splatoon 3 +maou-jou de oyasumi +otona no moeoh +taiko no tatsujin +getbackers +pop-up story +transformers animated +strike witches zero +the legend of zelda: breath of the wild +kotoura-san +dragon's dogma +five star stories +bubblegum crisis +kaifuku jutsushi no yarinaoshi ~sokushi mahou to skill copy no chouetsu heal~ +annie mei project +tsukuyomi moonphase +tsunderia +girls bravo +magus tale +baccano! +karakuri pierrot (vocaloid) +kiznaiver +tales of arise +shoujo kakumei utena +shining nikki +ashita no kimi to au tame ni +mayo chiki! +super robot wars og saga mugen no frontier +kaiten muten-maru +umayuru +scryed +idolmaster cinderella girls u149 +nijisanji en +goblin slayer! +pixiv fate/grand order contest 1 +scp foundation +lawson +yu-gi-oh! gx +hanshin tigers +pokemon xy (anime) +dead or alive 5 +.flow +zero kara hajimeru mahou no sho +pokemon (classic anime) +turn a gundam +kimi ga shine +splatoon 2 +futari wa precure splash star +seiken densetsu 2 +xenosaga +gakkou de seishun! +murenase! shiiton gakuen +saibou shinkyoku +fantastic beasts and where to find them +ryuu ga gotoku 0 +mushihime-sama +tokimeki memorial 2 +kingdom hearts +macross zero +ichigo 100 percent +tsuritama +the hobbit +call of duty +unfinished dream of all living ghost +chunithm +shinsougumi +comic anthurium +words worth +addams family +sweet home +warframe +waku waku 7 +kusari hime: euthanasia +black lagoon +panty & stocking with garterbelt +sayonara zetsubou sensei +princess principal +muv-luv total eclipse +elfen lied +tales of phantasia +iron saga +nissin +yuba no shirushi +sangatsu no lion +#compass +iya na kao sare nagara opantsu misete moraitai +final fantasy xi +sd gundam g-generation +zero no kiseki +nyantype +kakyuusei +dengeki hime +dog days +xenoblade chronicles: future connected +mega man legends +team fortress 2 +22/7 +batman (series) +love hina +to heart 2 ad +senjou no valkyria 2 +ef +mugen no fantasia +hourou musuko +ultra seven (series) +white album (series) +wixoss +sengoku basara +toaru majutsu no index +nier +a song of ice and fire +nyan koi! +kyu-kurarin (cevio) +lobotomy corporation +bleach: sennen kessen-hen +rou-kyuu-bu! +koumajou densetsu 2 +shino to ren +bravely default: flying fairy +fire emblem: thracia 776 +kimetsu gakuen +djmax +ginga eiyuu densetsu +castlevania: symphony of the night +warcraft +monster hunter 3 g +medaka box +akarui kioku soushitsu +tayutama +captain america (series) +hajimete no gal +lord of heroes +xenosaga episode i +uber eats +mega man 2 +shimoneta to iu gainen ga sonzai shinai taikutsu na sekai +stardew valley +dragon quest ix +valkyrie drive -mermaid- +sekaiju no meikyuu +pixiv fantasia 1 +marvel cinematic universe +hoshikuzu telepath +kemono jihen +berserk +sketchbook full colors +dairoku ryouhei +mitsuboshi colors +maji de watashi ni koi shinasai! +capsule servant +cuddly octopus +hyakka ryouran samurai girls +touhou cannonball +maburaho +hellsing +sayonara wo oshiete +mirai akari project +azur lane +tiny evil +koi suru kanojo no bukiyou na butai +kara no kyoukai +grappler baki +amakano ~second season~ +yes! precure 5 gogo! +mahou shoujo lyrical nanoha a's +simoun +ring dream +beast wars +medarot +iosys +chloris garden +beast wars: transformers +ai yori aoshi +me!me!me! +legend of mana +gen'ei wo kakeru taiyou +with you +super robot wars z +tales of destiny +nu carnival +venus blade +ishuzoku reviewers +cinderella +cardcaptor sakura +disneyland +genshiken +danna ga nani wo itte iru ka wakaranai ken +propro production +kodomo no jikan +hanikami kanojo +kairaku historie +suzume no tojimari +bungou to alchemist +crazy raccoon +god of war +toaru kagaku no mental out +gainax +crypton future media +eden's zero +animedia +akazukin chacha +fate/extella +assassin's creed +black bullet +shinrabanshou +touken ranbu +blade & soul +hinako note +bacchikoi! +lost technology +dance dance revolution +pretty rhythm rainbow live +guilty gear +the tale of food +sengoku collection +girls' frontline +re:zero kara hajimeru isekai seikatsu +ys +overlord (maruyama) +blazblue: continuum shift +oshi no ko +suzumiya haruhi no yuuutsu +devilman +another eidos-r +sister princess +reminiscence +atelier ayesha +namiuchigiwa no muromi-san +disgaea +taimanin (series) +the legend of korra +cookie run +flcl +kirakira precure a la mode +tokimeki memorial girl's side 4th heart +shining blade +star ocean integrity and faithlessness +lotte no omocha! +blue lock +girls und panzer senshadou daisakusen! +gravity daze 2 +citrus (saburouta) +incise soul +tales of xillia +gyee +destiny (game) +pikmin 4 +shin sakura taisen +odd taxi +ragnarok masters +final fantasy xv +puyopuyo~n +super heroine boy +final fantasy vii ever crisis +hololive indonesia +ensemble girls! +tokimeki memorial 1 +fullmetal alchemist +avengers (series) +manatsu no yo no inmu +black rock shooter +queen's blade grimoire +kekkai sensen +teen titans +dark souls ii +idolmaster movie +metroid +squid game +watashi ni tenshi ga maiorita! +cryamore +crawling dreams +metal gear solid v: the phantom pain +starless +gundam narrative +after (game) +kidou senkan nadesico - prince of darkness +dewprism +star ocean till the end of time +sengoku bushouki muramasa +kamen rider ooo (series) +bloodborne +spider-man: across the spider-verse +pokemon the movie: the power of us +omega strikers +rojiura satsuki: chapter heroine sanctuary +mega man legends (series) +shin megami tensei v +comiket 103 +vanguard princess +monsterverse +star ocean first departure +shinkai no valkyrie +tengen toppa gurren lagann: parallel works +voltron (series) +houchi shoujo +gintama +street fighter ex (series) +puyopuyo 7 +pacific racing team +powerpuff girls z +overwatch +ganbare goemon +tenkuu no craft fleet +shadow hearts +the legend of zelda: a link between worlds +happy tree friends +burger king +bakuretsu tenshi +houseki no kuni +comiket 102 +ice cream kanojo +genmu senki leda +hidan no aria +sen'yuu. +kamen rider den-o (series) +shantae and the pirate's curse +fire emblem: new mystery of the emblem +beyblade +kizumonogatari +mahou girls precure! +shakugan no shana +bayonetta 1 +getting over it +la pucelle +million arthur (series) +nitroplus +warioware +shingeki no bahamut +high school dxd pi +from argonavis +arc system works +senran kagura new wave +downtown no gaki no tsukai ya arahende!! +mario kart 8 +eureka seven (series) +harlock saga +sakura trick +stellula eques +heybot! +maken-ki! +bible black +demi-chan wa kataritai +bravely default (series) +monster hunter x +mystic square +black surge night +wild arms 2 +kakyuusei 2 +liz to aoi tori +comic rin +moyashimon +marvel vs. capcom 3 +kawaii dake ja nai shikimori-san +ushio to tora +made in abyss +tenchuu +ar tonelico +mujin wakusei survive +7th dragon (series) +arc the lad iii +soccer spirits +oboro muramasa +ijiranaide nagatoro-san +ben-tou +oekaki musume +kokoro iroduku koi ga shitai +bakemonogatari +tokyo mew mew +street fighter iii (series) +sansha san'you +gorillaz +pokemon masters ex +skullgirls +uq holder! +avengers: infinity war +pizza hut +justice gakuen +granblue fantasy versus +pretty series +hyrule warriors: age of calamity +shoukan yuusha to f-kei kareshi +kenkon no washi +sengoku rance +vsinger +sweet devil (vocaloid) +silent hill 2 +2014 fifa world cup +rumble roses +xenoblade chronicles (series) +bayonetta (series) +high school dxd born +the seven deadly sins +mother 2 +kemomimi oukoku kokuei housou +pokemon heroes: latios & latias +go! princess precure +assassin's creed ii +animage +sengoku musou +yatagarasu (game) +gotcha! +hibike! euphonium +gekkan shoujo nozaki-kun +ninja slayer +internet explorer +ranma 1/2 +makai senki disgaea 4 +queen's blade rebellion +doki doki literature club +muse dash +pokemon lgpe +xenoblade chronicles 2 +beelzebub (manga) +tenshi souzou re-boot! +fortnite +houkago teibou nisshi +baka to test to shoukanjuu +queen's blade unlimited +jiggly girls +kyoukai no kanata +bishoujo mangekyou +meshimase idol +fate/grand order arcade +shining wind +brave witches +null-meta +crusaders quest +fairy tail +to heart (series) +jojolion +romeo to cinderella (vocaloid) +pokemon sleep +dodonpachi +school girl strikers +shigoto neko +tennis no ouji-sama +ookami (game) +washio sumi wa yuusha de aru +street fighter zero (series) +gundam +tekken 8 +azure striker gunvolt 2 +mega man x: command mission +yuri kuma arashi +dengeki moeou +pokemon (anime) +atelier meruru +shining resonance +brown dust 2 +sword art online the movie: ordinal scale +dissidia final fantasy +gundam zz +pac-man (game) +futaba channel +touhou +promare +uta no prince-sama +oounabara to wadanohara +gundam 08th ms team +darling in the franxx +tsuyokiss +pokemon sm +cyborg 009 +ib +star ocean the last hope +one piece: strong world +fallout 3 +noir (anime) +soulcalibur iv +akb48 +huion +the road to el dorado +silver rain +boku no hero academia +avatar 2.0 project +command and conquer +greaseberries +tenka touitsu chronicle +another +busou shoujo machiavellism +ruri dragon +suki! yuki! maji magic (vocaloid) +akebi-chan no serafuku +gachimuchi +panzer waltz +pokemon: the electric tale of pikachu +gundam thunderbolt +86 -eightysix- +re leaf +arcane: league of legends +sekirei +comic exe +snk heroines: tag team frenzy +shin megami tensei iv +30 minutes sisters +captain america: the winter soldier +rainbow six siege +lupin iii +key (company) +terminator (series) +inazuma eleven (series) +sekaiju no meikyuu 4 +advent cirno +mega man battle network (series) +mahou shoujo madoka magica +kid icarus uprising +kagerou project +beastars +warhammer fantasy +fate/prototype +mario kart +robot girls z +ace attorney investigations 2 +shirobako +mobile suit gundam +pixiv fantasia t +superman (series) +tanaka the wizard +hades (series) +nanatsu no taizai +five nights at freddy's +honest axe +watashi ga motenai no wa dou kangaetemo omaera ga warui! +macross frontier: sayonara no tsubasa +psycho-pass +kokuhaku jikkou iinkai +mother 3 +cafe stella to shinigami no chou +shaft +vanquished queens +shukufuku no campanella +fate/hollow ataraxia +alien (1979) +kono bijutsubu niwa mondai ga aru! +amazon (company) +aerisdies +god eater +project x zone +tales of innocence +peace@pieces +kurano kun chi no futago jijou +re:act +arms (game) +athena (series) +pokemon gsc +amnesia (idea factory) +hollow knight +pixiv fantasia new world +gunxsword +lord of vermilion +devil may cry 3 +kaiji +pokemon ranger +roman (sound horizon) +non non biyori +little nuns (diva) +fire emblem: mystery of the emblem +harukana receive +slow loop +dragon quest iii +yes! precure 5 +deltarune +metal gear solid: peace walker +dororon enma-kun +barbie (live action) +fire emblem warriors +super robot wars judgement +neo-porte +galaxy angel rune +final fantasy xii +comiket 99 +studio ghibli +gundam build fighters +cevio +maria-sama ga miteru +devil may cry (series) +gundam hathaway's flash +gochuumon wa usagi desu ka? +samurai spirits +pokemon rse (anime) +cosmic break +metal gear solid 2: sons of liberty +mad max +dungeon and fighter +sakura musubi +senren banka +rwby +pokemon unite +sora no method +persona 2 tsumi +majo to hyakkihei +bastard!! +the shining +seikimatsu occult gakuin +tower records +selector wixoss +kemono friends pavilion +junketsu duelion +nora cat channel +sensha otoko +toki wo kakeru shoujo +phantasmagoria of dim.dream +kamihime project +pokemon conquest +unconnected marketeers +omc +maerchen (album) +koukaku no pandora +doraemon +niconico +nippon professional baseball +bravely second: end layer +bomberman +meiji (brand) +mato seihei no slave +kmnz +mahou shoujo oriko magica +chucolala +sangoku infinity +senjou no valkyria 1 +choukou sennin haruka +omniscient reader's viewpoint +suisei no gargantia +toeto (vocaloid) +gundam f91 +busou renkin +kanokon +flying witch +sword art online: alicization - war of underworld +luigi's mansion +ultra series +seiken densetsu 3 +godzilla (series) +borderlands (series) +xxxholic +persona 3 portable +ryuu ga gotoku 7 +street fighter 6 +the legend of zelda: spirit tracks +d.gray-man +kaze no klonoa +pokemon platinum +baby princess +iriam +fire emblem: shadow dragon and the blade of light +choujigen game neptune mk2 +yoru no yatterman +yu-gi-oh! go rush!! +tales of xillia 2 +pia carrot (series) +kamen rider hibiki (series) +emil chronicle online +paper mario: the thousand year door +force of will +mc axis +comic aun +rokka no yuusha +pokemon dppt (anime) +the legend of zelda: a link to the past +ao no kanata no four rhythm +nandemo iu koto wo kiite kureru akane-chan (voiceroid) +comiket 97 +drag-on dragoon 3 +pop'n music +otomedius +hachigatsu no cinderella nine +summon night 3 +smite +digimon card game +brand new animal +no more heroes +mighty morphin power rangers +shokugeki no souma +jigokuraku +pita ten +elf-san wa yaserarenai. +shadowverse +genshin impact +mononoke +renri no chigiri wo kimi to shiru +atomic heart +tales of hearts +umamusume +kannazuki no miko +robotics;notes +ojisan to marshmallow +milihime taisen +the king of fighters all-stars +gekitotsu! joshikousei oiroke sensha gundan +han'you no yashahime +danganronpa another episode: ultra despair girls +fate/prototype: fragments of blue and silver +green lantern (series) +wanmei shijie +samsung +manaria friends +maria holic +macross frontier: itsuwari no utahime +one-punch man +link! like! love live! +lotus land story +collar x malice +sword art online: hollow fragment +comic girls +ninin ga shinobuden +pixiv fantasia +mega man (series) +full metal jacket +mega man 11 +lineage 2 +maoyuu maou yuusha +the ring +ai tenshi densetsu wedding peach +cube x cursed x curious +mahoraba +shinryaku! ikamusume +djmax portable +yakitate!! japan +morinaga (brand) +gag manga biyori +fukumoto mahjong +arifureta shokugyou de sekai saikyou +hinabita +star ocean anamnesis +lycoris recoil +samurai champloo +sankarea +kamikaze kaitou jeanne +fallout 4 +borderlands 2 +drifters +dead by daylight +saijaku muhai no bahamut +comic party +synthesizer v +mamonomusume to no seikatsu ~ramia no baai~ +juusan kihei bouei ken +danganronpa (series) +mahou shoujo (raita) +sen no kiseki +pokemon mystery dungeon +sekaiju no meikyuu 5 +super robot wars og saga mugen no frontier exceed +jigoku shoujo +blazblue remix heart +isekai ojisan +breath of fire i +super mario galaxy +mother 1 +clover theater +maison ikkoku +highly responsive to prayers +persona 5 scramble: the phantom strikers +muv-luv alternative (anime) +mitsudomoe +mad max: fury road +air gear +fate/apocrypha +yoake mae yori ruri iro na +kono subarashii sekai ni bakuen wo! +wrestle angels survivor +dragon nest +jibaku shounen hanako-kun +kamitsubaki studio +goodsmile company +escape from tarkov +ganbare douki-chan +aikatsu! +yu yu hakusho +transformers: generation 1 +eternal arcadia +kodoku no gourmet +the moon studio +kiratto pri chan +monster (manga) +tetsuwan birdy decode +utawarerumono +atelier (series) +wild arms 1 +milgram +endro! +mishiranu joshikousei ni kankin sareta mangaka no hanashi +capcom +yu-gi-oh! +aikatsu on parade! +aoi hana +absolute duo +toaru majutsu no index: new testament +punch-out!! +2022 fifa world cup +senran kagura estival versus +uchuu senkan yamato 2199 +dragon ball heroes +gatchaman crowds +new super mario bros. u deluxe +touran-sai +blazblue variable heart +wrestle angels survivor 2 +bakuman +hello kitty +umi monogatari +ga-rei zero +mega man zx +zone of the enders 2 +kamen rider build (series) +bunny and fox world +fire emblem: genealogy of the holy war +strike the blood +just be friends (vocaloid) +ousama ranking +haibane renmei +final fantasy vi +no game no life +strike witches: katayoku no majo-tachi +dragon ball super super hero +aogiri koukou +eternal return: black survival +cinderella series +chaos online +idolmaster million live! theater days +yuusha de aru +rakudai kishi no cavalry +reverse:1999 +mabinogi +sisters ~natsu no saigo no hi~ +honkai impact 3rd +super mario bros. 1 +comic hotmilk +critical role +shirokami project +kamen rider blade (series) +gridman universe +virtual on +taimanin murasaki +winged fusiliers +genmukan +bamboo blade +utawarerumono: lost flag +dragon quest x +king (vocaloid) +madan no ou to vanadis +pokemon rgby (prototype) +diablo +last period +animal crossing +kingdom hearts i +yuri!!! on ice +comiket 90 +mon-musu quest! +hataraku saibou +who framed roger rabbit +flowers (innocent grey) +w tails cat +sekaiju no meikyuu 2 +blood-c +final fantasy v +aku no meshitsukai (vocaloid) +anyamal tantei kirumin zoo +yu-gi-oh! sevens +banjo-kazooie +dragon quest v +toy story +monster hunter 3 +shoujo shuumatsu ryokou +girlfriend (kari) +f-ism +kage no jitsuryokusha ni naritakute! +perfect cherry blossom +kaleido star +comiket 91 +pastel chime +yami to boushi to hon no tabibito +power rangers +ayakashi triangle +lovely x cation 2 +kami nomi zo shiru sekai +senki zesshou symphogear xd unlimited +dead or alive +lovely x cation +ikazuchi no senshi raidy +boku dake ga inai machi +ore no nounai sentakushi ga gakuen love-comedy wo zenryoku de jama shiteiru +shounen jump +sword girls +asobi ni iku yo! +cyber v +hori-san to miyamura-kun +my life as a teenage robot +dirty pair +the king of fighters xv +zom 100: zombie ni naru made ni shitai 100 no koto +kill bill +kamen rider w +strike witches: aurora no majo +escalation heroines +atelier sophie +chain chronicle +pokemon legends: arceus +sekaiju no meikyuu 1 +aikatsu stars! +fortune arterial +plants vs zombies +alice: madness returns +sen no kiseki iii +dorei to no seikatsu ~teaching feeling~ +metal slug attack +pixiv fantasia last saga +chihayafuru +madou monogatari +crayon shin-chan +h2o footprints in the sand +portal (series) +joukamachi no dandelion +slayers +winnie the pooh +touhou sangetsusei +mahou tsukai no yoru +kimi kiss +illustration.media +gate - jieitai ka no chi nite kaku tatakaeri +vivy: fluorite eye's song +watashi no tame ni nuginasai! +sentou mecha xabungle +renkin san-kyuu magical pokaan +tensui no sakuna-hime +ryuu ga gotoku 1 +wonder egg priority +ange vierge +eiyuu densetsu +playerunknown's battlegrounds +terraria +nanashi inc. +monster musume no iru nichijou +resident evil 3 (remake) +musunde hiraite rasetsu to mukuro (vocaloid) +log horizon +kouya no kotobuki hikoutai +transformers prime +grisaia (series) +ghost in the shell stand alone complex +xianjian qixia zhuan +holostars english +yu-gi-oh! duel monsters +accel world +dungeon travelers 2 +blazblue: central fiction +rance 10 +harukanaru toki no naka de +saga frontier 2 +shigatsu wa kimi no uso +tactics ogre +princess principal game of mission +voms +nekomonogatari +2010 fifa world cup +yatterman +musuko ga kawaikute shikatanai mazoku no hahaoya +resident evil 3: nemesis +full metal panic! +noble works +modao zushi +etotama +t.m.revolution +dragon ball gt +wild arms 4 +sola +kanpani girls +umamusume: cinderella gray +love live! sunshine!! the school idol movie over the rainbow +go-toubun no hanayome +atelier escha & logy +shenmue +onii-chan dakedo ai sae areba kankeinai yo ne +new game! +momoiro taisen pairon +nora to oujo to noraneko heart +goddess of victory: nikke +final fantasy ix +star wars: the force awakens +school rumble +phoenix wright: ace attorney - justice for all +sengoku musou 2 +arc the lad ii +pixiv fantasia revenge of the darkness +cthulhu mythos +halo (series) +cross edge +luo xiaohei zhanji +saihate (vocaloid) +needy girl overdose +tokyo necro +wild arms +sonic the hedgehog (archie comics) +uchuu no stellvia +aquarion evol +star trek +star ocean the second story +puzzle & dragons +bioshock infinite +kuroshitsuji +comic penguin club +devil summoner +mana khemia (series) +rune factory +pokemon sv +dragon quest xi +kamippoina (vocaloid) +pia carrot e youkoso!! g.o. +bokusatsu tenshi dokuro-chan +hopeless masquerade +hypnosis mic +kamen rider kabuto (series) +taiho shichauzo +crisis core final fantasy vii +tokyo 7th sisters +olympics +kamen rider (1st series) +witches of africa +eve online +ai the somnium files +megido72 +sleeping beauty +given +mazinger z +melt (vocaloid) +melty blood: type lumina +shuffle! +soredemo machi wa mawatteiru +pui pui molcar +kamen rider black (series) +production kawaii +resort boin +trinity seven +sekiro: shadows die twice +wild flower +hyakko +souzou forest (vocaloid) +rabi-ribi +tell your world (vocaloid) +benghuai xueyuan +backrooms (creepypasta) +monobeno +kono yuusha ga ore tueee kuse ni shinchou sugiru +love live! sunshine!! +to heart +hanasaku iroha +daitoshokan no hitsujikai +valkyrie drive -siren- +grisaia no kajitsu +slime taoshite 300 nen shiranai uchi ni level max ni nattemashita +taimadou gakuen 35 shiken shoutai +fate/unlimited blade works +family guy +project diva (series) +the hunchback of notre dame +wario land +a.i. voice +megami magazine +zoids genesis +nekopara +jurassic world +dark souls (series) +final fantasy ii +kemono friends +idolmaster starlit season +ssss.dynazenon +jet set radio +hatsune miku happy 16th birthday -dear creators- +closers +holostars +fantasista doll +city hunter +kaze no tani no nausicaa +qurare magic library +dlsite.com +taimanin rpgx +avatar legends +i-chu +wily beast and weakest creature +hunie (series) +omamori himari +fire emblem heroes +mon-musu quest: paradox +dr. mario (game) +my little pony: friendship is magic +violet evergarden (series) +atelier lulua +scarlet weather rhapsody +spider-man: into the spider-verse +street fighter ii (series) +cross ange +kuroinu ~kedakaki seijo wa hakudaku ni somaru~ +kyouran kazoku nikki +darkest dungeon +dive to zone +zoids +super robot wars the lord of elemental +gundam g no reconguista +ghost trick +suicide boy +xenoblade chronicles 3: future redeemed +splatoon (manga) +yuuki yuuna wa yuusha de aru +tear ring saga +i want you +precure all stars +pocketland +super mario 3d world +100 percent orange juice +scooby-doo +danganronpa v3: killing harmony +hakuouki shinsengumi kitan +hoshikuzu witch meruru +aria (manga) +shinza bansho series +nyanko daisensou +seitokai no ichizon +seishun buta yarou +kirby and the forgotten land +makai tenshi djibril +tales of eternia +owarimonogatari +nisekoi +xenogears +zozotown +persona 4: dancing all night +the legend of zelda: ocarina of time +toaru majutsu no index: endymion no kiseki +company of heroes +girls und panzer ribbon no musha +ore twintail ni narimasu +wizarding world +neon genesis evangelion +project diva 2nd +fresh precure! +sky: children of the light +jikkyou powerful pro yakyuu +demonbane +black clover +mahoutsukai no yakusoku +square live +the adventures of sherlock holmes +touhou gouyoku ibun +kingdom hearts ii +yuni channel +kurogane no linebarrel +justice league +bishoujo senshi sailor moon crystal +nippon ichi +persona dancing +marvel +ensemble stars! +paper mario 64 +apex legends +macintosh +nurarihyon no mago +kuro no kiseki +macross +nazo no kanojo x +kanon +titanic (movie) +summon night 5 +tokimeki memorial 4 +majutsushi orphen +eromanga sensei +tamako market +masters of the universe +azure striker gunvolt +mikakunin de shinkoukei +death stranding +phantasmagoria of flower view +gj-bu +majo no tabitabi +sonic adventure +touhou hisoutensoku +assassin's creed (series) +golden sun +kirby: star allies +strike witches +resident evil 0 +high score girl +chrono crusade +owari no seraph +pixiv fantasia wizard and knight +dance in the vampire bund +xenoblade chronicles 1 +the evil within +sony +evolution championship series +gundam seed astray +yozakura quartet +persona 2 +love live! school idol project +dead space +idolmaster side-m +kamisama ni natta hi +dragon quest yuusha abel densetsu +ultraman (1st series) +shadow of the colossus +hitsugi no chaika +gunslinger stratos +osu! tatakae! ouendan +majestic prince +fate/kaleid liner prisma illya +fault!! +mahou shoujo lyrical nanoha reflection +puniru wa kawaii slime +dream c club +asobi asobase +occultic;nine +friday the 13th +formation girls +tasogare otome x amnesia +sunoharasou no kanrinin-san +pokemon emerald +kill la kill +kaibutsu oujo +guardian tales +zero no tsukaima +world of warships +kumo desu ga nani ka? +to love-ru +princess royale +toaru majutsu no index: old testament +final fantasy x-2 +yagate kimi ni naru +the legend of zelda +gundam 00 a wakening of the trailblazer +summer pockets +blazblue phase 0 +wcdonald's +akatsuki records +megami tensei +embodiment of scarlet devil +detective pikachu (movie) +kami-sama no memo-chou +atelier rorona +sword art online +kirby super star +atelier live +sora no otoshimono +the loud house +atelier ryza 2 +drag-on dragoon 1 +dragon quest dai no daibouken +uzaki-chan wa asobitai! +the lion king +criminal girls +idolmaster 1 +pokemon hgss +pixiv fantasia mountain of heaven +castlevania: portrait of ruin +vocaloid +ikkitousen great guardians +gamers! +frame arms girl +ano ko wa toshi densetsu +haiiro teien +inazuma eleven go +blazblue: cross tag battle +food fantasy +pretty (series) +prism magical +shachiku succubus no hanashi +kemomimi refle! +casshern (series) +rune factory 4 +jigoku sensei nube +nijiura maids +yama no susume +dokidoki sister aoi-chan +strike witches: suomus misfits squadron +hitoribocchi no marumaru seikatsu +high school dxd infinity +fire emblem: three houses +bunnystein fantasy +napoli no otokotachi +shuumatsu no harem +resident evil 4 (remake) +pokemon adventures +zenless zone zero +border break +professor layton +futsuu no joshikousei ga locodol yattemita +dancouga (series) +dragon ball super +dota 2 +shinkyoku soukai polyphonica +girl cafe gun +blame! +seirei tsukai no blade dance +grimm's fairy tales +toradora! +wake up girls! stage no tenshi +aa megami-sama +stellive +five nights at freddy's: security breach +gensou suikoden +arms note +tenshi no inai 12-gatsu +shadows house +ukagaka +senran kagura peach beach splash +fl studio +koe no katachi +gundam 0083 +the amazing world of gumball +reitaisai +mahou shoujo lyrical nanoha a's portable: the gears of destiny +qualidea code +imperishable night +heartcatch precure! +real drive +precure +xblaze +four goddesses online: cyber dimension neptune +star wars: the clone wars +kamen rider +splatoon 2: octo expansion +hyrule warriors +seikai no senki +sennen sensou aigis +ca (maeda koutarou) +axis powers hetalia +highschool of the dead +snow white and the seven dwarfs +tensei oujo to tensai reijou no mahou kakumei +hajimari no kiseki +comiket 93 +acchi kocchi +summon night +vanillaware +kamichu! +space dandy +tom and jerry +dmm +idolmaster cinderella girls starlight stage +diamond wa kudakenai +hai to gensou no grimgar +saber marionette j +hentai key +cowboy bebop +ghost in the shell +ookiku furikabutte +disgaea rpg +himehina channel +sonic rush +muv-luv alternative +mekakucity actors +adachi to shimamura +nogi wakaba wa yuusha de aru +ongeki +haiyore! nyaruko-san +ben 10 +kaizoku sentai gokaiger +la corda d'oro +prismplus +senyoku no sigrdrifa +shuumatsu nani shitemasu ka? +katekyo hitman reborn! +2018 fifa world cup +kawaikereba hentai demo suki ni natte kuremasu ka? +hitomi sensei no hokenshitsu +choujin x +rokudenashi majutsu koushi to akashic record +gakkou gurashi! +senjou no valkyria 3 +koumajou densetsu +pokemon: twilight wings +the king of fighters +sentouin hakenshimasu! +the king of fighters xiv +little red riding hood +dolls in pseudo paradise +pikmin (series) +planetarian +the amazing digital circus +love live! school idol festival all stars +masterwork apocalypse +sword art online: alicization +fate/samurai remnant +taishou yakyuu musume +casshern sins +tetris +yu-gi-oh! the dark side of dimensions +code geass: boukoku no akito +dimension w +one - kagayaku kisetsu e +tiger & bunny +shigofumi +high school dxd hero +ar tonelico i +guardians of the galaxy +initial d +fate/requiem +neptune (series) +aether gazer +tropical kiss +charlotte (anime) +dragon ball fighterz +gal gamer ni homeraretai +dungeon meshi +viprpg +love lab +gensou suikoden i +bayonetta 3 +tekken +pokemon rse +taimanin asagi kessen arena +pokemon bw +soukou kihei votoms +senran kagura new link +dagashi kashi +destiny child +microsoft windows +senjou no valkyria 4 +phantasy star portable 2 +clockwork rabbit +pokemon +princess lover +the big o +undefined fantastic object +bakusou kyoudai let's & go!! +artery gear +fire emblem engage +gundam seed +angel chromosome xx +scott pilgrim takes off +pixiv fantasia age of starlight +the bible +toaru kagaku no railgun +mayoi neko overrun! +metal gear solid 3: snake eater +rinne no lagrange +fire emblem cipher +riot music +tianguan cifu +marl kingdom +tokyo mirage sessions fe +world masterpiece theater +urusei yatsura +chou shittou caduceus +matryoshka (vocaloid) +gensou suikoden ii +project moon +c2 kikan +the legend of zelda: tears of the kingdom +ring fit adventure +magician's aerial dream +idolmaster live for you! +code vein +girlish number +gugure! kokkuri-san +tears of themis +idolmaster side-m growing stars +nisoku hokou (vocaloid) +zaregoto series +tomb raider +valkyrie no densetsu +tales of legendia +atelier ryza 3 +miracle nikki +nhk ni youkoso! +microsoft +mcdonald's +resident evil 4 +koufuku graffiti +phantom blood +learning with manga! fgo +ai shite! homun +kud wafter +monster hunter: world +sanoba witch +queen (band) +calvin klein +un-go +shingeki no bahamut: genesis +honey come chatka!! +versailles no bara +onegai my melody +phantasy star portable 2 infinity +sousou no frieren +mahou senki lyrical nanoha force +vee (vtuber) +7th dragon 2020 +rance (series) +fate/stay night +hikaru no go +fairy fencer f +indie utaite +tekken tag tournament 2 +dark souls i +fatal frame 3 +g gundam +little busters! +danshi koukousei no nichijou +the iron of yin and yang +onegai teacher +devil survivor +neko musume michikusa nikki +ssss.gridman +curiosities of lotus asia +mona lisa +hapymaher +dankira!!! +sonic the hedgehog (classic) +black rock shooter (game) +sono bisque doll wa koi wo suru +culture japan +tenshinranman +isekai maou to shoukan shoujo no dorei majutsu +virtua fighter +tenki no ko +hiroshima touyou carp +kai-ri-sei million arthur +a hat in time +koishi komeiji's heart-throbbing adventure +artwhirl mahou gakuen no otome-tachi +magic: the gathering +kishuku gakkou no juliet +world cup +joshi kousei rich thots +sora wo kakeru shoujo +monster musume no oisha-san +dirge of cerberus final fantasy vii +cu-no +kagerou days (vocaloid) +cyphers +final fantasy +steampunk (liarsoft) +seiren +marvel vs. capcom +tsuki ni yorisou otome no sahou +osomatsu-kun +r-type +idolmaster poplinks +twisted wonderland +the legend of zelda: link's awakening +zhu xian +strawberry panic! +voiceroid +sonic adventure 2 +danganronpa kirigiri +senjuushi: the thousand noble musketeers +cinderella girls gekijou +kamen rider geats (series) +cutie honey +kantai collection (anime) +dumbbell nan kilo moteru? +nagasarete airantou +sana channel +mahou tsukai to kuroneko no wiz +zombie land saga +satsuriku no tenshi +resident evil: revelations +alien (series) +sakura taisen +nagi no asukara +ueno-san wa bukiyou +pokemon dppt +league of legends +shantae: half-genie hero +toaru kagaku no railgun s +firefox +final fantasy viii +sd gundam +tsukimonogatari +akame ga kill! +jahy-sama wa kujikenai! +deemo +bakumatsu rouman +alia's carnival! +oshiro project:re +gridman universe (film) +the beatles +wild and horned hermit +digimon adventure 02 +v ap art +dark souls iii +dc comics +quilt (game) +sangoku musou 1 +d-frag! +tsuujou kougeki ga zentai kougeki de ni-kai kougeki no okaasan wa suki desu ka? +last origin +gundam 0080 +super robot wars +quiz magic academy +puyopuyo quest +dennou shoujo youtuber siro +echocalypse +sora no kanata no dystopia x shitei +monster hunter portable 3rd +aquarium (visual novel) +kusuriya no hitorigoto +blue reflection +.hack//games +hirogaru sky! precure +hikikomari kyuuketsuki no monmon +black cat (series) +among us +parasite eve +queen's blade white triangle +da capo iii +slam dunk (series) +touhou lost word +bakuon!! +space adventure cobra +diablo 3 +kunoichi tsubaki no mune no uchi +otome youkai zakuro +seiken densetsu +lollipop chainsaw +metal gear (series) +nier (series) +limbus company +sakura-sou no pet na kanojo +fire emblem warriors: three hopes +kamen rider kiva (series) +code geass +koihime musou +cardfight!! vanguard +game & watch +naruto +atelier lydie & suelle +anpanman +grimms notes +soulcalibur +shinkai shoujo (vocaloid) +makaimura +bural chingu +tate no yuusha no nariagari +jinrui wa suitai shimashita +kiddy grade +mega man star force +dracu-riot! +oda nobuna no yabou +fatal frame 2 +kemono friends v project +hataraku maou-sama! +ever 17 +triage x +yuusha ou gaogaigar +one piece +nichijou +kamen rider wizard (series) +blue oath +urasekai picnic +gundam wing +kochikame +suzumiya haruhi-chan no yuuutsu +persona 1 +otome function +metroid dread +the end of evangelion +mabinogi heroes +super real mahjong +coca-cola +super cub +mega man zx advent +miru tights +mahou shoujo madoka magica: hangyaku no monogatari +the transformers (idw) +super sentai +pepsi +zeta gundam +otogi-juushi akazukin +mahou shoujo madoka magica movie 1 & 2 +sora no iro mizu no iro +final fight +kfc +photokano +alice gear aegis +douluo dalu +doom eternal +kirby's return to dream land +super smash bros. +tales of destiny 2 +ghostbusters +grandia i +haks +voltron: legendary defender +lost universe +power pro kun pocket +ultra kaijuu gijinka keikaku +mega man zero (series) +titanfall 2 +dragon ball z +world witches series +shinkansen henkei robo shinkalion +mass effect (series) +pokemon tcg +fantasy earth +kamen rider fourze (series) +one piece film: red +domestic na kanojo +castlevania: rondo of blood +meitantei conan +wrestle angels +senran kagura shoujo-tachi no shin'ei +zone of the enders +bang dream! it's mygo!!!!! +suntory +bashamichi +mirai akari's new virtual youtuber illustration contest +bombergirl +doupo cangqiong +tokidoki bosotto roshia-go de dereru tonari no arya-san +persona 4 +utawarerumono: itsuwari no kamen +tokyo ghoul:re +pacific rim +jingai makyou +fallout new vegas +chrono cross +girls' frontline neural cloud +record of lodoss war +mairimashita! iruma-kun +cattleya regina games +tsukihime (remake) +idolmaster million live! +advance wars +indie virtual youtuber +toushinden +wake up girls! +spy x family +godzilla: king of the monsters +hoshi ori yume mirai +splatoon 1 +dragon: marked for death +bikkuriman +watashi no oshi wa akuyaku reijou +girls und panzer saishuushou +hiyoku no crosspiece +kimi no koto ga dai dai dai dai daisuki na 100-nin no kanojo +paper mario +a deer of nine colors +creepypasta +fii-tan the figure +puyopuyo +hololive alternative +yosuga no sora +cloud nine inc +kerberos blade +pixiv +dissidia final fantasy opera omnia +princess tutu +future card buddyfight +deathsmiles +memories off +pokemon usum +bishoujo senshi sailor moon +dragon quest viii +houkago no pleiades +the legend of zelda (nes) +11eyes +yu-gi-oh! vrains +action pizazz +utau +himouto! umaru-chan +blood+ +gundam arsenal base +little witch nobeta +minecraft +koumajou densetsu 1 +red: pride of eden +final fantasy brave exvius +fallout (series) +sonic (series) +mushoku tensei +eternity sword series +r.o.d the tv +the witcher (series) +half-life (series) +oretachi ni tsubasa wa nai +lilith-soft +silent hill (series) +.hack// +comiket 95 +blend s +kizuato +tsumi no hahen (debris) +star fox +elysion +junk gaming maiden +inazuma eleven go galaxy +modern mogal +gravity falls +pornhub +fate/extra ccc fox tail +hyouka +tenkuu no escaflowne +granblue fantasy +mass effect +houshin engi +the king of fighters '97 +mahjong soul +beautiful gunbari +comic x-eros +kamen rider ex-aid (series) +vshojo +kyuuketsuki sugu shinu +dragon quest iv +kamen rider agito (series) +genjitsu no yohane +netsu ijou (utau) +neko to chiyo +metal gear solid +after war gundam x +kingdom (series) +fire emblem +honzuki no gekokujou +kiseijuu +under night in-birth +castlevania (series) +breaking bad +cafe-chan to break time +kuromukuro +yoru mac +gaki kyonyuu +c.c. lemon +torikissa! +puyopuyo fever +avengers: endgame +final fantasy type-0 +deadman wonderland +top gun +world of warcraft +rhapsody +seikon no qwaser +noragami +kingdom hearts iii +coppelion +onmyoji +ambience synesthesia +ys viii lacrimosa of dana +narutaru +adventure time +joshi kousei +hinata channel +mofumofu channel +oneechanbara +muv-luv unlimited: the day after +magna carta +pokemon rgby +amagami +project diva f +macross delta: zettai live!!!!!! +kemono friends 3 +kidou keisatsu patlabor +wild arms xf +imouto sae ireba ii +va-11 hall-a +ga-rei +magical mirai (vocaloid) +mahouka koukou no rettousei +agarest senki (series) +pixiv fantasia 3 +tree of savior +undead unluck +from software +macross: do you remember love? +show by rock!! +mountain dew +sen no kiseki iv +idolmaster shiny colors +kizuna ai inc. +saya no uta +thunderbolt fantasy +school days +yuru yuri +the super mario bros. movie +white album +pure pure +the little mermaid +granado espada +doki doki majo shinpan +keijo!!!!!!!! +romancing saga 3 +forever 7th capital +love live! +galaxy angel +thomas the tank engine +final fantasy iii +mahou shoujo taisen +digimon adventure +bleach +phantasy star online 2 +sei zenra jogakuen +aikatsu friends! +kamikaze explorer! +kamen rider 555 +eiken +heaven's feel +da capo i +leon the professional +koimonogatari +breath of fire ii +nijisanji +warui ga watashi wa yuri janai +garfield +formula one +egyptian mythology +saenai heroine no sodatekata +kanojo mo kanojo +xenoblade chronicles 2: torna - the golden country +stone ocean +dr pepper +shining star +7th dragon iii +seitokai yakuindomo +rozen maiden +.hack//g.u. +arcana heart +delicious party precure +resident evil 2 +kikou souseiki mospeada +national basketball association +sk8 the infinity +hidden star in four seasons +queen's blade +phantom kingdom +sunrise (company) +night sparrow love +kamen rider kuuga (series) +yume nikki +zutto mayonaka de ii no ni +pixiv fantasia 4 +digimon story: cyber sleuth +mugen senshi valis +crave saga +zoids chaotic century +live a live +pokemon bdsp +brave girl ravens +koi to senkyo to chocolate +star twinkle precure +mm! +tamayura +summertime render +gakuen utopia manabi straight! +onegai twins +akane-iro ni somaru saka +eizouken ni wa te wo dasu na! +alien (movie) +7th dragon +final fantasy x +bokujou monogatari +ichiban ushiro no daimaou +bloodstained: ritual of the night +aoki hagane no arpeggio +flip flappers +gundam sentinel +popotan +goodbye sengen (vocaloid) +macross delta +beatmania iidx +pandora hearts +punishing: gray raven +digimon world re:digitize +vento aureo +kyokou suiri +comiket 96 +asagao to kase-san +senjou no electro girl +katawa shoujo +robotech +divine gate +alchemy stars +makai senki disgaea 5 +super mario rpg +twitter +seisenshi dunbine +ghost sweeper mikami +newtype +shaman king +romancing saga +masamune-kun no revenge +doukyuusei another world +bemani +sarazanmai +gundam build divers re:rise +4chan +shakunetsu no takkyuu musume +char's counterattack +youjo senki +tsurezure children +uta macross sumaho deculture +youkai hyakki-tan! +koi wa sensou (vocaloid) +xenoblade chronicles 3 +scott pilgrim (series) +nagato yuki-chan no shoushitsu +karakai jouzu no takagi-san +en'en no shouboutai +funamusea +black jack (series) +hoshizora e kakaru hashi +danganronpa: trigger happy havoc +dohna dohna issho ni warui koto o shiyou +twin angel +gakusen toshi asterisk +a-soul +agent aika +persona 5: dancing star night +heroman +princess connect! +pia carrot e youkoso!! 3 +high school fleet +fullbokko heroes +muchigaku +sewayaki kitsune no senko-san +fate/tiger colosseum +japanese mythology +journey to the west +monster hunter (series) +touhou bougetsushou +doukyuusei +kamen rider saber (series) +tekken 6 +puchimasu! +inuyasha +hamidashi creative +sakura taisen iii +heavy object +world of tanks +idol time pripara +the king of fighters 2001 +monster musume no iru nichijou online +mahou shoujo lyrical nanoha the movie 1st +ga geijutsuka art design class +akudama drive +hanamaru youchien +kibou no chikara ~otona precure '23~ +ragnarok online +armored core +ano hi mita hana no namae wo bokutachi wa mada shiranai. +ao no exorcist +senjuushi (series) +giant robo +saga frontier +devilman crybaby +sonic and the black knight +9-nine- +arknights: endfield +mahou shoujo madoka magica plus +blazblue: chronophantasma +mushishi +transformers +project diva extend +tombow mono +dramatical murder +xblaze code: embryo +battle angel alita +re:zero kara hajimeru isekai seikatsu: lost in memories +gabriel dropout +gundam seed destiny +cafe sourire +senran kagura shinovi versus +harry potter (series) +shantae (series) +tamagotchi +lord el-melloi ii case files +mega man x (series) +naruto shippuuden +xenosaga episode iii +imaizumin-chi wa douyara gal no tamariba ni natteru rashii +resident evil 5 +persona 5 the royal +transformers (live action) +megami paradise +kemurikusa +you can eat the girl +wonder festival 2007 +kure-nai +poptepipic +sakura taisen v +gake no ue no ponyo +wreck-it ralph +gokukoku no brynhildr +digimon tamers +phoenix wright: ace attorney +kuso miso technique +fire emblem: the sacred stones +chain paradox +vandread +comiket 100 +monster farm +yuragisou no yuuna-san +el cazador de la bruja +maitetsu +happiness! +unleashed +senbonzakura (vocaloid) +engage kiss +indivisible +yokohama kaidashi kikou +re:stage! +danball senki +tengoku daimakyou +koiiro soramoyou +dororo (tezuka) +breath of fire +senran kagura +idoly pride +apollo justice: ace attorney +choujigen game neptune +soredemo ayumu wa yosetekuru +pixiv fantasia sword regalia +shinma x keishou! ragnabreak +battlefield (series) +galgrease +eiyuu senki ww +pokemon gsc (prototype) +digimon +mushiking +kujibiki unbalance +papa no iu koto wo kikinasai! +k-on! movie +nirvana (band) +yu-gi-oh! arc-v +pokemon journeys +love live! nijigasaki high school idol club +ansatsu kyoushitsu +barakamon +soul hackers +karigurashi no arrietty +benesse +oneshot (game) +tenka hyakken +maplestory +dolphin wave +wonderland wars +fire emblem echoes: shadows of valentia +uni create +splatoon (series) +phoenix wright: ace attorney - spirit of justice +tamiya incorporated +priapus +soulcalibur vi +kemono friends kingdom +amphibia +melonbooks +merc storia +last exile: gin'yoku no fam +kyoufuu all back (vocaloid) +kobe shinbun +plastic memories +project sekai +fushigiboshi no futago hime +kamen rider ghost (series) +angel beats! +shukusei!! loli-gami requiem +otome game no hametsu flag shika nai akuyaku reijou ni tensei shite shimatta +needless +tongkkangi +the legend of zelda: skyward sword +sword art online alternative: gun gale online +gouma reifuden izuna +shiguang dailiren +doom (series) +honey strap +twitter-san +furyou michi ~gang road~ +strange creators of outer world +flower knight girl +shin sangoku musou +signalis +tejina senpai (series) +seiken gakuin no maken tsukai +peter pan (disney) +goodsmile racing +yakusoku no neverland +project upd8 +yotsunoha +koutetsujou no kabaneri +len'en +tower of fantasy +victory gundam +wwe +do it yourself!! +top wo nerae 2! +dragon quest ii +fate/strange fake +majo no takkyuubin +grandia +haite kudasai takamine-san +ender lilies quietus of the knights +otome gee sekai wa mob ni kibishii sekai desu +c (control) +gensou suikoden v +otoyomegatari +star driver +fushigi no umi no nadia +tokyo revengers +final fantasy tactics +shiki (novel) +servant x service +captain earth +himawari-san +ark survival evolved +kin-iro mosaic +osomatsu-san +tekkaman blade +read or die +shining tears +digimon adventure tri. +google +phantom of the kill +tantei wa mou shindeiru +undertale +inazuma eleven +legacy of lunatic kingdom +sega +lyrical nanoha +decadence (anime) +happinesscharge precure! +teenage mutant ninja turtles +avatar: the last airbender +druaga no tou +boku no kokoro no yabai yatsu +street fighter v +gundam tekketsu no orphans +akira (manga) +shuumatsu no valkyrie +the matrix +apple inc. +kantai collection +hokkaido nippon-ham fighters +momotarou densetsu +boku no risou no isekai seikatsu +wild arms 3 +himegoto +kid icarus +sousai shoujo teien +densetsu kyojin ideon +saki achiga-hen +battle girl high school +idolmaster one for all +suiheisen made nan mile? +bocchi the rock! +g-taste +cuphead (game) +breath of fire v +magi the labyrinth of magic +mahou shoujo lyrical nanoha a's portable: the battle of aces +top wo nerae! +soul eater +boruto: naruto the movie +gendai ninjakko zukan +infinite stratos +the murder of sonic the hedgehog +resident evil village +tales of asteria +pixiv fantasia 2 +snk +mechanical buddy universe +ma no mono-tachi +ar tonelico iii +devil may cry 4 +silverlight +rolling girl (vocaloid) +koikishi purely kiss +yurucamp +quiz magic academy the world evolve +ninja gaiden +fire emblem awakening +seiken no blacksmith +schwarzesmarken +higurashi no naku koro ni mei +pokemon cafe mix +tenkuu no crystalia +macross 7 +gagraphic +yahari ore no seishun lovecome wa machigatteiru. +ore wo suki nano wa omae dake ka yo +full moon wo sagashite +mister donut +atelier ryza +touka gettan +senkou no ronde +reddit +watchmen +legends of runeterra +k-on! +tokyo afterschool summoners +nintendo +tales of (series) +advance of zeta +yukijirushi +ao no roku-gou +surge concerto +tari tari +fatal fury +eiyuu senki +tales of rebirth +kimi ga aruji de shitsuji ga ore de +cyberpunk edgerunners +mahou shoujo lyrical nanoha innocent +spongebob squarepants +sora no kiseki +shining hearts +duck hunt +crossbone gundam +final fantasy iv: the after years +jujutsu kaisen +true tears +ryuuko no ken +ombok diving and delivery services +wonder festival +bayonetta 2 +moetan +kemono friends 2 +futakoi +beauty and the beast +swapnote +dissidia 012 final fantasy +frozen (disney) +nijisanji id +saga +naruto (series) +forbidden scrollery +lineage +cygames +one piece treasure cruise +mario tennis +the great ace attorney 2: resolve +oshiro project re +tenjou tenge +space invaders +metroid: zero mission +saru getchu +phantasy star +binbougami ga! +kaitou saint tail +guilty dragon +ochikazuki ni naritai miyazen-san +it (stephen king) +dragon quest vi +subterranean animism +saiki kusuo no psi nan +variable geo +resident evil +twitch.tv +aquarion (series) +futari wa precure +little witch academia +id :invaded +portal 1 +pumpkin scissors +otome wa boku ni koishiteru +megami magazine deluxe +doukutsu monogatari +nier:automata +prism project +lanxi zhen +tantei opera milky holmes +toji no miko +overman king gainer +dr. stone +saint seiya +magic kaito +rebuild of evangelion +bandai namco +story of eastern wonderland +taimanin yukikaze +beyblade: burst +djmax respect +tonari no kyuuketsuki-san +jormungand (manga) +hololive dev is +ash arms +comiket 92 +game of thrones +comiket 101 +space channel 5 +dragon ball minus +paryi project +pokemon (game) +netoge no yome wa onna no ko janai to omotta? +voicevox +suite precure +trigger heart exelica +ane naru mono +saikin yatotta maid ga ayashii +kakegurui +aquarian age +garou: mark of the wolves +arcana heart 3 +digimon savers +mahou shoujo ikusei keikaku restart +shinkon gattai godannar!! +dream c club (series) +senpai ga uzai kouhai no hanashi +haevest +taimanin asagi +strider (video game) +comic bavel +sono hanabira ni kuchizuke wo +dango daikazoku +fate/extra ccc +mahou shoujo lyrical nanoha the movie 2nd a's +omoide no marnie +rail wars! +comiket 94 +ashita no nadja +fantasy earth zero +south park +real life +takt op. +spectral (series) +kumamiko +flyable heart +dandadan +evillious nendaiki +di gi charat +alicesoft +tangled +american mcgee's alice +fire emblem: the blazing blade +sekai seifuku: bouryaku no zvezda +tetsuwan atom +comic megastore +cookie (touhou) +inou-battle wa nichijou-kei no naka de +call of cthulhu +pixiv fantasia 5 +twice (group) +virtuareal +outlaw star +tatakau ataisuru +.hack//tasogare no udewa densetsu +kirara fantasia +scarz +trickster +wakfu +guilty gear x +kaede to suzu +koukaku no regios +ikoku meiro no croisee +grand sphere +little tail bronx +valkyrie connect +noble witches +final fantasy tactics advance +ochikobore fruit tart +final fantasy fables +hentai elf to majime orc +amairo islenauts +ryuu ga gotoku (series) +elden ring +no-rin +subarashiki hibi +hataraku saibou black +grimlight +terra battle +sen to chihiro no kamikakushi +mashiroiro symphony +left 4 dead +star vs the forces of evil +artery gear: fusion +japan racing association +shugo chara! +little match girl +teekyuu +trojan green asteroid +arai-san mansion +action taimanin +pokemon swsh +last exile +kono naka ni hitori imouto ga iru! +devil survivor 2 +line (naver) +digimon xros wars +hentai ouji to warawanai neko. +yu-gi-oh! zexal +tokyo exe girls +super blackjack +azur lane: slow ahead +wangzhe rongyao +end of evangelion +gunslinger girl +kimi ni todoke +steins;gate +mob psycho 100 +the elder scrolls v: skyrim +dislyte +gundam build fighters try +touhou ningyougeki +xenoblade chronicles x +agarest senki +lunar +seisen cerberus +steelblue mirage +type-moon +commando (movie) +vspo! +tenshi no 3p! +basquash! +7th dragon 2020-ii +kotobukiya bishoujo +dragon ball +beat angel escalayer +shining musume +resident evil 2 (remake) +gundam wing endless waltz +trusty bell +pokemon oras +umamusume: road to the top +dragon quest vii +sekaiju no meikyuu 3 +naruto: the last +hokuto no ken +romancing saga minstrel song +kuro no kiseki ii +world flipper +battle tendency +pangya +monster strike +helltaker +evertale +jewelpet (series) +mario kart wii +ten desires +atelier shallie +mega man x dive +ore no imouto ga konna ni kawaii wake ga nai +mahou shoujo lyrical nanoha +dengeki g's +lord of the mysteries +sakigake!! cromartie koukou +paradox live +evangelion: 3.0+1.0 thrice upon a time +tenco's story +chobits +the last of us +devil may cry 5 +trigun +mewkledreamy +arcaea +phase connect +inu x boku ss +okujou no yurirei-san +youtube +yuzu-soft +gundam build divers +romancing saga 2 +ryuuou no oshigoto! +haruru minamo ni! +kamen rider gaim (series) +futari wa precure max heart +tropical liquor +kirby's dream land 3 +battle athletes +kidou senkan nadesico +houkago play +aoi ch. +ace attorney investigations +z/x +elfheim +gundam card builder +lord of vermilion iii +super mario world +mario kart tour +prison school +kyoto animation +fate/extra +danganronpa/zero +witchblade +tamatoys +disney +detective pikachu +evangelion: 3.0 you can (not) redo +codename: kids next door +ilog +tokyo ghoul +to heart 2 +wactor production +the silmarillion +golden kamuy +ark order +unlight +musaigen no phantom world +tom clancy's the division +guilty gear strive +ace combat 7 +summon night 2 +yuusha no kuse ni namaiki da +hololive english +ordo mediare sisters (ironlily) +library of ruina +kanagawa okinami ura +animare +chrono trigger +coefont +strawberry prince +healin' good precure +battle spirits +attouteki yuugi mugen souls +riddle joker +ushinawareta mirai wo motomete +fire emblem: the binding blade +azumanga daioh +monster girl encyclopedia +beatmania +kimi no na wa. +denpa onna to seishun otoko +dead or alive 6 +akatsuki no yona +fatal frame 4 +yokohama dena baystars +yondemasu yo azazel-san. +x-men +shingeki no kyojin +getsuyoubi no tawawa +terminator 2: judgment day +pokemon mystery dungeon: explorers of time/darkness/sky +vivid strike! +waktaverse +metroid prime +yuusha ou gaogaigar final +super mario sunshine +makai shin trillion +rosario+vampire +shin koihime musou +dragon quest builders 2 +star wars: revenge of the sith +dota (series) +saturday night fever +queen's gate +strike witches 1940 +denonbu +idolmaster (classic) +yofukashi no uta +toaru kagaku no railgun t +tekken 7 +juuni kokuki +luminous arc +donkey kong (series) +kingdom hearts 358/2 days +under night in-birth exe:late[st] +dennou coil +touhou tag dream +wednesday (netflix) +uchuu patrol luluco +back to the future +karakai jouzu no (moto) takagi-san +shingoku no valhalla gate +rune factory 5 +stratos 4 +amaryllis gumi +melty blood +sanrio +tales of zestiria +joshiraku +ame to kimi to +fate/grand order +zegapain +ghostblade +franken fran +noripro +yotsubato! +guilty gear xrd +namco +final fantasy xiii +uchi no maid ga uzasugiru! +lucky star +grandia ii +friday night funkin' +ole tower +identity v +fire emblem gaiden +jashin-chan dropkick +hololive china +viper +zettai karen children +k-project +sengoku taisen +zenkoku seifuku bishoujo grand prix +mofu-mofu after school +tsubasa chronicle +battle spirits: shounen toppa bashin +100th black market +dorohedoro +persona 4: the ultimate in mayonaka arena +overwatch 2 +bandai +shinmai fukei kiruko-san +pokemon frlg +call of duty: modern warfare 2 +sora yori mo tooi basho +spider-man (series) +kemeko deluxe +new horizon +rewrite +chainsaw man +ano natsu de matteru +mario & luigi rpg +carnival phantasm +makai senki disgaea 3 +v yuusha no kuse ni namaiki da r +youkoso jitsuryoku shijou shugi no kyoushitsu e +girls' frontline 2: exilium +kim possible (series) +clannad +fate/empire of dirt +boku no kanojo sensei +soulcalibur v +blade runner +mist train girls +kimetsu no yaiba +sword art online: memory defrag +inazuma eleven go chrono stone +tales of graces +monster hunter frontier +homestuck +kyousougiga +corrector yui +masou gakuen hxh +nier reincarnation +metal gear rising: revengeance +monogatari (series) +pringles +tsugumomo +tiktok +blue archive +keroro gunsou +ghostly field club +river city girls +kill me baby +galaga +kamen rider revice +kamen rider 01 (series) +howl no ugoku shiro +kakumeiki valvrave +metroid fusion +dies irae +castlevania: order of ecclesia +tenkuu no shiro laputa +mega man (classic) +sugar lyric +idolmaster dearly stars +akatsuki no goei +ikkitousen dragon destiny +portrait of exotic girls +mountain of faith +god eater 2: rage burst +monster hunter 4 +foul detective satori +super robot wars x-omega +sword art online: code register +liver city +cyberpunk (series) +master detective archives: rain code +sound voltex +guilty crown +tokyo ravens +bobobo-bo bo-bobo +saint seiya omega +da capo +kirby 64 +expo2025 +kyokugen dasshutsu +black survival +sidonia no kishi +tokyo xanadu +kamen rider drive (series) +odds & ends (vocaloid) +nisemonogatari +haikyuu!! +love r +high school dxd new +mega man x4 +itai no wa iya nano de bougyoryoku ni kyokufuri shitai to omoimasu +orangina +haou taikei ryuu knight +shiro seijo to kuro bokushi +rurouni kenshin +the legend of zelda: majora's mask +steel ball run +baldur's gate +oshiro project +nanatsuiro drops +hamtaro (series) +osana reimu +mahoromatic +mystical power plant +high school dxd cross +kangoku senkan +sasami-san@ganbaranai +alternative girls +sdorica +mario (series) +mahou shoujo lyrical nanoha vivid +orenchi no meidosan +arslan senki +shin megami tensei +machikado mazoku +sora no woto +star wars: visions +metal slug +huniepop +black souls +sounan desuka? +she-ra and the princesses of power +nijisanji kr +shy (series) +canaan (series) +oomuro-ke +tsugu (vtuber) +bakuretsu hunters +garo (series) +street fighter iv (series) +suna no wakusei (vocaloid) +wacom +sword art online progressive +afk arena +world's end dancehall (vocaloid) +kamen rider zi-o (series) +soulworker +serial experiments lain +subarashiki kono sekai +hooters +fire emblem: shadow dragon +idolmaster xenoglossia +counter:side +hunter x hunter +breath of fire iii +gundam suisei no majo +trigger (company) +baldur's gate 3 +silent hill 3 +idol corp +powerpuff girls +digimon frontier +princess peach: showtime! +wagaya no oinari-sama +brave sword x blaze soul +kimagure orange road +yume 2kki +gekijouban hibike! euphonium +otonari no tenshi-sama ni itsu no mani ka dame ningen ni sarete ita ken +snow white +zootopia +kagura gumi +higanbana no saku yoru ni +aquaplus +shin megami tensei iii: nocturne +muv-luv +sonic frontiers +boruto: naruto next generations +hentai kamen +my little pony +isekai tensei shite v ni narimashita +corpse party +burn the witch +comic tenma +magia record: mahou shoujo madoka magica gaiden +yuyushiki +detroit: become human +inari konkon koi iroha. +kamen rider ryuki (series) +yakin byoutou +spy kyoushitsu +pixiv fate/grand order contest 2 +.live +outbreak company +freezing (series) +eureka seven +helluva boss +f-zero +shironeko project +seitokai nimo anawa aru! +kaitou tenshi twin angel +phantasy star universe +kashimashi +project voltage +boroboro no elf-san o shiawaseni suru kusuri uri-san +dragon ball (classic) +precure all stars new stage: mirai no tomodachi +kyoukaisenjou no horizon +atelier firis +amagi brilliant park +ace combat zero +the lord of the rings +overwatch 1 +kino no tabi +facebook +etra-chan wa mita! +kono subarashii sekai ni shukufuku wo! +getter robo +kao no nai tsuki +talkex +aggressive retsuko +kamen rider black rx (series) +kami jigen game neptune v +j. league +higurashi no naku koro ni +duel masters +macross plus +gunsmith cats +pokemon bw2 +coyote ragtime show +soukyuu no fafner +aldnoah.zero +seihou +phoenix wright: ace attorney - dual destinies +mary skelter +summon night 4 +pokemon xy +dead or alive xtreme +tolkien's legendarium +iris black games +portal 2 +upotte!! +fate/extella link +hilda (series) +to love-ru darkness +fire emblem fates +disgaea d2 +kagetsu tooya +tsukihime +katanagatari +age of ishtaria +mermaid melody pichi pichi pitch +super mario odyssey +magnet (vocaloid) +the great ace attorney +nagai gojitsudan no nechronica +eversoul +demento +yomawari (series) +munou na nana +precure connection puzzlun +foster's home for imaginary friends +tensei shitara slime datta ken +resident evil 1 +invincible (series) +m.u.g.e.n +double spoiler +dosanko gyaru wa namaramenkoi +rinrinne +tales of the abyss +dragon ball super broly +roshin yuukai (vocaloid) +the legend of zelda: twilight princess +hoozuki no reitetsu +pixiv fantasia fallen kings +dragonaut +stardust crusaders +latale +kore ga watashi no goshujin-sama +hugtto! precure +fire emblem: radiant dawn +manga time kirara +mahou shoujo ai +demon's souls +kanojo okarishimasu +looney tunes +star wars +reflect (gawr gura) +ar tonelico ii +super robot wars 30 +sakura quest +bijin onna joushi takizawa-san +the legend of zelda: the wind waker +doom (2016) +mahou shoujo madoka magica (anime) +spice and wolf +strike witches 1991 +moomin +tokimeki memorial girl's side 3rd story +aika (series) +omori +alice in wonderland +warzard +fruits basket +hakanai kimi wa moukou wo hajimeru +final fantasy vii +tower of god +ace combat +pacific (kancolle) +ojamajo doremi +your diary +white album 2 +phantom brave +sky girls +my-otome +mazinger (series) +vyugen +arknights +danganronpa 3 (anime) +chousoku henkei gyrozetter +kannagi +legend of the cryptids +king's raid +urara meirochou +grand theft auto +kimi ga nozomu eien +tenchi muyou! +tales of symphonia +atelier ryza 1 +dragalia lost +arcana heart 2 +gad guard +phoenix wright: ace attorney - trials and tribulations +gosick +donkey kong country +daiteikoku +octopath traveler +dragon's crown +pokemon bw (anime) +steven universe +idolish7 +re:creators +ladies versus butlers! +ookami-san +chibi maruko-chan +da capo ii +ginga tetsudou 999 +evangelion: 2.0 you can (not) advance +falkyrie no monshou +mighty no. 9 +girl's avenue +love live! superstar!! +mahoujin guruguru +fall guys +aladdin (disney) +miku symphony (vocaloid) +kunio-kun series +suguri +the king of fighters xiii +toranoana +fearless night +jurassic park +ace of diamond +final fantasy xiii-2 +busou shinki +koisuru asteroid +comiket 88 +pokemon go +gegege no kitarou +kaguya-sama wa kokurasetai ~tensai-tachi no renai zunousen~ +oshioki sweetie +dungeons and dragons +starcraft +kore wa zombie desu ka? +summer wars +everlasting summer +rune factory 3 +my adventures with superman +tengen toppa gurren lagann +papico (ice cream) +warhammer 40k +kirby: planet robobot +abenobashi mahou shoutengai +the coffin of andy and leyley +iris mysteria! +hades 1 +suzumiya haruhi no shoushitsu +drag-on dragoon +sonic the hedgehog (idw) +senjou no valkyria (series) +kampfer +odin sphere +cyberpunk 2077 +vrchat +soukou akki muramasa +tetsuwan birdy +crash bandicoot (series) +atelier totori +hacka doll +blazblue +fate/zero +vinland saga +resident evil 6 +kokoro connect +the owl house +hoshizora no memoria +os-tan +elsword +dr. slump +gothic wa mahou otome +lupinus virtual games +big hero 6 +danganronpa s: ultimate summer camp +wuthering waves +otoca d'or +hanayamata +the elder scrolls +my little pony: equestria girls +shoujo kageki revue starlight +dungeon ni deai wo motomeru no wa machigatteiru darou ka +valkyrie drive +valorant +yumekui merry +svc chaos +witch craft works +armored core: for answer +vividred operation +pokemon colosseum +felarya +touhou danmaku kagura +ore no kanojo to osananajimi ga shuraba sugiru +darker than black +aikatsu! (series) +boukun habanero +zannen onna-kanbu black general-san +onsen musume +kamisama no you na kimi e +koha-ace +tales of symphonia knight of ratatosk +sentimental graffiti +meridian project +mahou shoujo ikusei keikaku unmarked +shuumatsu no izetta +honkai: star rail +smile precure! +suika game +hua jianghu zhi bei mo ting +final fantasy vii advent children +shijou saikyou no deshi ken'ichi +miniskirt pirates +chaos;child +amano megumi wa suki darake! +macross frontier +perfume (band) +urban legend in limbo +knight's & magic +kobeya +ao no kiseki +gravity daze +jojo no kimyou na bouken +qinshi mingyue +titanfall (series) +fire emblem: path of radiance +working!! +the lego group +starbucks +love live! school idol festival +sword art online: alicization rising steel +square enix +ichigo mashimaro +air (visual novel) +lol -lots of laugh- (vocaloid) +fatal frame +izayoi no hanayome +original +the simpsons +the terminator +daibouken! yukeyuke osawari island +konjiki no gash!! +mospeada +luminous witches +d4dj +the grim adventures of billy & mandy +final fantasy iv +snow fox +onii-chan wa oshimai! +heaven burns red +monsters inc. +tonari no totoro +epic seven +gantz +fate/unlimited codes +uchuu senkan yamato +mortal kombat (series) +rakudai ninja rantarou +final fantasy xvi +catherine (game) +durarara!! +star wars: return of the jedi +shadowverse (anime) +seto no hanayome +sazae-san +boku to koi suru ponkotsu akuma. +eyeshield 21 +bioshock (series) +rakuen tsuihou +kuso zako choroin nishiga hachi +sengoku saga +dragon quest +akemiho tabi nikki +choujikuu yousai macross +night wizard +gensou suikoden iii +aoi shiro +love plus +pokemon horizons +lonely girl ni sakaraenai +tropical-rouge! precure +kuroko no basuke +sangokushi taisen +final fantasy xiv +my-hime +dragon quest i +mahou shoujo ikusei keikaku +super mario 64 +strike witches: kurenai no majo-tachi +mega man 1 +majo no ie +shoujo to ura roji +enjo kouhai +juuni taisen +mondaiji-tachi ga isekai kara kuru sou desu yo? +to heart 2 xrated +neon genesis evangelion gakuen datenroku +berry's +tomo-chan wa onna no ko +guilty gear xx +gouketsuji ichizoku +comic unreal +street fighter +machine-doll wa kizutsukanai +the incredibles +yu-gi-oh! 5d's +ouran high school host club +tales of vesperia +atelier marie +eoduun badaui deungbul-i doeeo +no.6 +animal (vocaloid) +virtual ant channel +hisho collection +arakawa under the bridge +antinomy of common flowers +428 +paripi koumei +predator (movie) +nurse witch komugi-chan +super metroid +gundam msv +magical halloween +pixiv fantasia scepter of zeraldia +pokemon sm (anime) +mega man 9 +chuunibyou demo koi ga shitai! +galhound +akuma no riddle +akagi: yami ni oritatta tensai +juusenki l-gaim +namco x capcom +sumaga +gensou suikoden iv +juukishi cutie bullet +red bull +hayate no gotoku! +date a live +ico +time bokan (series) +arc the lad +kobayashi-san chi no maidragon +yandere simulator +pretty rhythm aurora dream +waccha primagi! +akiba maid sensou +hololive +miraculous ladybug +super mario bros. wonder +hundred +idolmaster side-m live on stage! +char's counterattack - beltorchika's children +prism ark +psychic hearts +hidamari sketch +shiritsu justice gakuen +re:lief ~shin'ai naru anata e~ +irotoridori no sekai +wendy's +umineko no naku koro ni +monster hunter rise +persona 5 +project diva +melty+ +gundam unicorn +gyakushuu no fantasica +2channel +greek mythology +gynoid talk +idolmaster +joker (2019) +nogizaka haruka no himitsu +peanuts +shin jigen game neptune vii +double dealing character +aku no musume (vocaloid) +wii fit +mahou no tenshi creamy mami +death note +touhou (pc-98) +mahou shoujo lyrical nanoha strikers +phantasy star online +doukyuusei 2 +live a hero +final fantasy i +jewelpet twinkle +world trigger +hazbin hotel +dream hunter rem +makai senki disgaea 2 +instagram +bang dream! +persona 3 +kirby (series) +girls und panzer +kotobukiya +wikipedia +shining (series) +mononoke hime +mihoyo +chaos;head +king of prism by prettyrhythm +sen no kiseki ii +boku wa tomodachi ga sukunai +tokimeki memorial +walkure romanze +shin godzilla +armored core 6 +impossible spell card +lovers (game) +senki zesshou symphogear +wild arms 5 +oshiete! galko-chan +shinmai maou no testament +vampire (vocaloid) +mieruko-chan +path to nowhere +growlanser +ace attorney +magic knight rayearth +uchi no hime-sama ga ichiban kawaii +kingdom hearts birth by sleep +galge.com +bilibili +comic lo +majin tantei nougami neuro +world is mine (vocaloid) +gundam 00 +yuusha series +comic kairakuten +sound horizon +kagaminomachi no kaguya +acfun +aohada bocchi +tera online +shiroi suna no aquatope +barbie (franchise) +spider-verse +persona 4 the golden +isshuukan friends +the great ace attorney: adventures +wonder woman (series) +youkai watch 2 +irisu shoukougun! +idolmaster cinderella girls +ice climber +god eater burst +el shaddai +natsume yuujinchou +valkyrie profile (series) +steins;gate 0 +cyberbots +super robot wars original generation +dogs: bullets & carnage +yuusha to maou +choudenji robo combattler v +high school dxd +kanojo x kanojo x kanojo +kurenai no buta +sinoalice +mother (game) +pripara +pani poni dash! +paladins +makai senki disgaea +fireball (series) +warship girls r +sid story +honkai (series) +shin getter robo +octopath traveler i +jinki +komi-san wa komyushou desu +claymore (series) +capcom fighting jam +assault lily +a channel +slow start +yowamushi pedal +hatsune miku no shoushitsu (vocaloid) +final fantasy vii remake +comic kairakuten beast +final fantasy crystal chronicles +takopii no genzai +piapro +shoujo kageki revue starlight -re live- +breath of fire iv +bungou stray dogs +hanebado! +caligula (series) +danganronpa 2: goodbye despair +saki +ikkitousen +dokidoki! precure +mawaru penguindrum +twinbox school +tales of berseria +metal gear solid 4: guns of the patriots +tongari boushi no atelier +star ocean +langrisser +mahou tsukai no yome +mirai nikki +free! +minami-ke +persona +fate (series) +gundam age \ No newline at end of file diff --git a/extensions/sd-danbooru-tags-upsampler/tags/quality.txt b/extensions/sd-danbooru-tags-upsampler/tags/quality.txt new file mode 100644 index 0000000000000000000000000000000000000000..d02309c534180df51def3f04d4515f3bb4716e0b --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/tags/quality.txt @@ -0,0 +1,28 @@ +masterpiece +best quality +amazing quality +high quality +great quality +good quality +medium quality +normal quality +bad quality +low quality +worst quality +newest +late +mid +early +oldest +exceptional +best aesthetic +very aesthetic +aesthetic +normal aesthetic +displeasing +bad aesthetic +very displeasing +deleted +waifu +real life +anime \ No newline at end of file diff --git a/extensions/sd-danbooru-tags-upsampler/tests/test_utils.py b/extensions/sd-danbooru-tags-upsampler/tests/test_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..22a1413ce58f59f7606b706761ae2ef608044b86 --- /dev/null +++ b/extensions/sd-danbooru-tags-upsampler/tests/test_utils.py @@ -0,0 +1,117 @@ +import sys + +sys.path.append(".") + +import re + +from dart.utils import ( + get_valid_tag_list, + get_patterns_from_tag_list, + _get_tag_pattern, + escape_webui_special_symbols, + unescape_webui_special_symbols, +) + + +def test_valid_tag_list(): + text = "simple background, ,, animal ears, umbrella," + expected = ["simple background", "animal ears", "umbrella"] + + assert get_valid_tag_list(text) == expected + + +def test_get_tag_pattern(): + test_cases: list[tuple[str, re.Pattern, str]] = [ + ("1girl", re.compile(r"1girl"), "1girl"), + ( + "character (copyright)", + re.compile(r"character \(copyright\)"), + "character (copyright)", + ), + ("* ears", re.compile(r".* ears"), "cat ears"), + ("looking *", re.compile(r"looking .*"), "looking to the side"), + ("* (cosplay)", re.compile(r".* \(cosplay\)"), "hatsune miku (cosplay)"), + ] + + for input, expected, matches in test_cases: + assert expected.match(matches) + assert _get_tag_pattern(input).match(matches) + + +def test_get_patterns_from_tag_list(): + test_cases: list[tuple[str, list[re.Pattern], list[str]]] = [ + ( + "1boy, umbrella,very long hair", + [ + re.compile( + r"1boy", + ), + re.compile( + r"umbrella", + ), + re.compile( + r"very long hair", + ), + ], + ["1boy", "umbrella", "very long hair"], + ), + ( + "* ears,holding *, animal *", + [ + re.compile( + r".* ears", + ), + re.compile( + r"holding .*", + ), + re.compile( + r"animal .*", + ), + ], + ["cat ears", "holding weapon", "animal ears"], + ), + ( + "copyright (character), star (sky)", + [ + re.compile( + r"copyright \(character\)", + ), + re.compile( + r"star \(sky\)", + ), + ], + ["copyright (character)", "star (sky)"], + ), + ] + + for input, expected, matches in test_cases: + for pattern, target in zip(expected, matches): + assert pattern.match(target) + for pattern, target in zip( + get_patterns_from_tag_list(get_valid_tag_list(input)), matches + ): + assert pattern.match(target) + + +def test_escape_webui_special_symbols(): + test_cases: list[tuple[list[str], list[str]]] = [ + (["1girl", "solo"], ["1girl", "solo"]), + ( + ["star (sky)", "kafka (honkai:star rail)"], + [r"star \(sky\)", r"kafka \(honkai:star rail\)"], + ), + ] + for input, expected in test_cases: + assert escape_webui_special_symbols(input) == expected + + +def test_unescape_webui_special_symbols(): + test_cases: list[tuple[list[str], list[str]]] = [ + (["1girl", "solo"], ["1girl", "solo"]), + ( + [r"star \(sky\)", r"kafka \(honkai:star rail\)"], + ["star (sky)", "kafka (honkai:star rail)"], + ), + ] + for input, expected in test_cases: + assert unescape_webui_special_symbols(input) == expected diff --git a/extensions/sd-fast-pnginfo/.gitignore b/extensions/sd-fast-pnginfo/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..77912a5ab4e81dc9bc1e8bab907abfa4759fed82 --- /dev/null +++ b/extensions/sd-fast-pnginfo/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +*.png +*.gif +*.jpg +javascript/exif-reader.js \ No newline at end of file diff --git a/extensions/sd-fast-pnginfo/README.md b/extensions/sd-fast-pnginfo/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9dce966cd6a18aa3b4e34502eeafb26874e2095c --- /dev/null +++ b/extensions/sd-fast-pnginfo/README.md @@ -0,0 +1,18 @@ +# Fast PNG Info +an Extension for [Stable Diffusion WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) +and [Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge)
+The extension uses a dynamically loaded [ExifReader](https://github.com/mattiasw/ExifReader) library module to extract image metadata locally.
+Eliminating the need to upload it to a server, resulting in Fast PNG Info.
+This is particularly noticeable when running the webui on online services, such as Google Colab, Kaggle, SageMaker Studio Lab, etc.
+ +Support: +- PNG parameters +- JPEG and Avif userComment +- Novel AI parameters with brackets conversion + +# Preview + +

+ + +

\ No newline at end of file diff --git a/extensions/sd-fast-pnginfo/install.py b/extensions/sd-fast-pnginfo/install.py new file mode 100644 index 0000000000000000000000000000000000000000..4e7cc91ca2f57b97b93b92175e47b128a6fa382c --- /dev/null +++ b/extensions/sd-fast-pnginfo/install.py @@ -0,0 +1,13 @@ +import shutil, urllib.request +from pathlib import Path + +req = (Path(__file__).parent / "javascript") / "exif-reader.js" + +def _download(): + if not req.exists(): + print(f"Downloading Fast PNG info requirement: \033[38;5;208mexif-reader.js\033[0m") + url = "https://raw.githubusercontent.com/mattiasw/ExifReader/main/dist/exif-reader.js" + with urllib.request.urlopen(url) as response, open(req, 'wb') as out_file: + shutil.copyfileobj(response, out_file) + +_download() diff --git a/extensions/sd-fast-pnginfo/javascript/exif-reader.js b/extensions/sd-fast-pnginfo/javascript/exif-reader.js new file mode 100644 index 0000000000000000000000000000000000000000..578628409113c1cf6dcf62237c0a65c88358ee0a --- /dev/null +++ b/extensions/sd-fast-pnginfo/javascript/exif-reader.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.ExifReader=t():e.ExifReader=t()}("undefined"!=typeof self?self:this,(function(){return function(){"use strict";var e={d:function(t,n){for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:1,get:n[r]})},o:function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r:function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:1})}},t={};function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(e)}function r(e){var t=function(e,t){if("object"!=n(e)||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var i=r.call(e,"string");if("object"!=n(i))return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return e+""}(e);return"symbol"==n(t)?t:t+""}e.r(t),e.d(t,{default:function(){return wi},errors:function(){return Ti},load:function(){return xi},loadView:function(){return Mi}});var i=function(){return e=function e(t){if(function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),function(e){return"object"!==n(e)||void 0===e.length||void 0===e.readUInt8||void 0===e.readUInt16LE||void 0===e.readUInt16BE||void 0===e.readUInt32LE||void 0===e.readUInt32BE||void 0===e.readInt32LE||void 0===e.readInt32BE}(t))throw Error("DataView: Passed buffer type is unsupported.");this.buffer=t,this.byteLength=this.buffer.length},(t=[{key:"getUint8",value:function(e){return this.buffer.readUInt8(e)}},{key:"getUint16",value:function(e,t){return t?this.buffer.readUInt16LE(e):this.buffer.readUInt16BE(e)}},{key:"getUint32",value:function(e,t){return t?this.buffer.readUInt32LE(e):this.buffer.readUInt32BE(e)}},{key:"getInt32",value:function(e,t){return t?this.buffer.readInt32LE(e):this.buffer.readInt32BE(e)}}])&&function(e,t){for(var n=0;n3&&void 0!==arguments[3]?arguments[3]:"string";if(t===h&&"function"==typeof DecompressionStream){var i=new DecompressionStream("deflate"),o=new Blob([e]).stream().pipeThrough(i);return"dataview"===r?new Response(o).arrayBuffer().then((function(e){return new DataView(e)})):new Response(o).arrayBuffer().then((function(e){return new TextDecoder(n).decode(e)}))}return void 0!==t?Promise.reject("Unknown compression method ".concat(t,".")):e}var y={USE_FILE:1,USE_JFIF:1,USE_PNG_FILE:1,USE_EXIF:1,USE_IPTC:1,USE_XMP:1,USE_ICC:1,USE_MPF:1,USE_PHOTOSHOP:1,USE_THUMBNAIL:1,USE_TIFF:1,USE_JPEG:1,USE_PNG:1,USE_HEIC:1,USE_AVIF:1,USE_WEBP:1,USE_GIF:1};function S(e){return e.map((function(e){return String.fromCharCode(e)})).join("")}function b(e){if(e.length>=8){var t=S(e.slice(0,8));if("ASCII\0\0\0"===t)return S(e.slice(8));if("JIS\0\0\0\0\0"===t)return"[JIS encoded text]";if("UNICODE\0"===t)return"[Unicode encoded text]";if("\0\0\0\0\0\0\0\0"===t)return"[Undefined encoding]"}return"Undefined"}function I(e){return e[0][0]/e[0][1]+e[1][0]/e[1][1]/60+e[2][0]/e[2][1]/3600}var C=18761,U=19789,A={BIG_ENDIAN:U,LITTLE_ENDIAN:C,getByteOrder:function(e,t){if(e.getUint16(t)===C)return C;if(e.getUint16(t)===U)return U;throw Error("Illegal byte order value. Faulty image.")}},P=2,E=65496,w=2,T=4,x=2,F=2,M=10,O=18,D=33,L=79,N=18,k=8,R="ICC_PROFILE\0",B=T+R.length,G=B+1,_="MPF\0",j=65472,z=65474,V=65476,H=65499,W=65501,X=65498,J=65504,Y=65505,q=65506,K=65517,$=65519,Z=65534,Q=65535,ee="JFIF",te="Exif",ne="http://ns.adobe.com/xap/1.0/\0",re="http://ns.adobe.com/xmp/extension/\0",ie="Photoshop 3.0";function oe(e,t){return e.getUint16(t)===j}function ae(e,t){return e.getUint16(t)===z}function ue(e,t){var n=R.length;return e.getUint16(t)===q&&a(e,t+T,n)===R}function ce(e,t){var n=_.length;return e.getUint16(t)===q&&a(e,t+T,n)===_}function fe(e,t){var n=ee.length;return e.getUint16(t)===J&&a(e,t+T,n)===ee&&0===e.getUint8(t+T+n)}function se(e,t){var n=te.length;return e.getUint16(t)===Y&&a(e,t+T,n)===te&&0===e.getUint8(t+T+n)}function le(e,t){return e.getUint16(t)===Y&&function(e,t){var n=ne.length;return a(e,t+T,n)===ne}(e,t)}function de(e,t){return e.getUint16(t)===Y&&function(e,t){var n=re.length;return a(e,t+T,n)===re}(e,t)}function pe(e,t){return{dataOffset:e+D,length:t-(D-x)}}function me(e,t){return{dataOffset:e+L,length:t-(L-x)}}function ge(e,t){var n=ie.length;return e.getUint16(t)===K&&a(e,t+T,n)===ie&&0===e.getUint8(t+T+n)}function he(e,t){var n=e.getUint16(t);return n>=J&&n<=$||n===Z||n===j||n===z||n===V||n===H||n===W||n===X}function ve(e,t){return e.getUint16(t)===Q}var ye="‰PNG\r\n\n",Se=4,be=4,Ie=0,Ce=Se,Ue=Se+be,Ae="XML:com.adobe.xmp\0",Pe="tEXt",Ee="iTXt",we="zTXt",Te="pHYs",xe="tIME",Fe="eXIf",Me="iCCP";function Oe(e,t){return"IHDR"===a(e,t+Ce,be)}function De(e,t){return a(e,t+Ce,be)===Ee&&a(e,t+Ue,Ae.length)===Ae}function Le(e,t,n){var r=a(e,t+Ce,be);return r===Pe||r===Ee||r===we&&n}function Ne(e,t){return a(e,t+Ce,be)===Fe}function ke(e,t){return a(e,t+Ce,be)===Me}function Re(e,t){var n=[Te,xe],r=a(e,t+Ce,be);return n.includes(r)}function Be(e,t){t+=Ue+Ae.length+1+1;for(var n=0;n<2&&t0)return Ve(e,n,r)}function Ve(e,t,n){return 4===n?e.getUint32(t):8===n?(console.warn("This file uses an 8-bit offset which is currently not supported by ExifReader. Contact the maintainer to get it fixed."),function(e,t){return e.getUint32(t+4)}(e,t)):0}var He=1718909296,We=1768977008,Xe=1835365473,Je=1768714083,Ye=1768517222,qe=1768842853,Ke=1768973167,$e=1668246642,Ze=1165519206,Qe=1835625829,et=1970432288;function tt(e,t){var n=function(e,t){var n=e.getUint32(t);return function(e){return 0===e}(n)?{length:e.byteLength-t,contentOffset:t+4+4}:function(e){return 1===e}(n)&&function(e,t){return 0===e.getUint32(t+8)}(e,t)?{length:e.getUint32(t+12),contentOffset:t+4+4+8}:{length:n,contentOffset:t+4+4}}(e,t),r=n.length,i=n.contentOffset;if(!(r<8)){var o=e.getUint32(t+4);if(o===He)return function(e,t,n){return{type:"ftyp",majorBrand:a(e,t,4),length:n}}(e,i,r);if(o===We)return function(e,t,n,r){return{type:"iprp",subBoxes:ot(e,n,r-(n-t)),length:r}}(e,t,i,r);if(o===Ke)return function(e,t,n,r){return{type:"ipco",properties:ot(e,n,r-(n-t)),length:r}}(e,t,i,r);if(o===$e)return function(e,t,n){return{type:"colr",icc:it(e,t),length:n}}(e,i,r);var c=e.getUint8(i);return o===Xe?function(e,t,n,r){return{type:"meta",subBoxes:ot(e,n+3,r-(n+3-t)),length:r}}(e,t,i+1,r):o===Je?function(e,t,n,r){var i=function(e,t){var n={item:{dataReferenceIndex:2,extentCount:2,extent:{}}};e<2?(n.itemCount=2,n.item.itemId=2):2===e&&(n.itemCount=4,n.item.itemId=4),n.item.constructionMethod=1===e||2===e?2:0;var r={offsetSize:t,lengthSize:t,baseOffsetSize:t+1,indexSize:t+1};return r.itemCount=t+2,r.items=r.itemCount+n.itemCount,r.item={itemId:0},r.item.constructionMethod=r.item.itemId+n.item.itemId,r.item.dataReferenceIndex=r.item.constructionMethod+n.item.constructionMethod,{offsets:r,sizes:n}}(t,n+3),o=i.offsets,a=i.sizes,u=e.getUint8(o.offsetSize)>>4;a.item.extent.extentOffset=u;var c=15&e.getUint8(o.lengthSize);a.item.extent.extentLength=c;var f=e.getUint8(o.baseOffsetSize)>>4;a.item.baseOffset=f;var s=function(e,t,n){if(1===n||2===n)return 15&e.getUint8(t)}(e,o.indexSize,t);a.item.extent.extentIndex=void 0!==s?s:0;var l=function(e,t,n){return n<2?e.getUint16(t):2===n?e.getUint32(t):void 0}(e,o.itemCount,t);return{type:"iloc",items:_e(e,t,o,a,u,c,s,l),length:r}}(e,c,i+1,r):o===Ye?function(e,t,n,r,i){var o=function(e,t){var n={entryCount:t+3},r={};return r.entryCount=0===e?2:4,n.itemInfos=n.entryCount+r.entryCount,{offsets:n}}(n,r),a=o.offsets;return{type:"iinf",itemInfos:ot(e,a.itemInfos,i-(a.itemInfos-t)),length:i}}(e,t,c,i+1,r):o===qe?function(e,t,n,r,i){r+=3;var o={type:"infe",length:i};return 0!==n&&1!==n||(o.itemId=e.getUint16(r),r+=2,o.itemProtectionIndex=e.getUint16(r),r+=2,o.itemName=u(e,r),r+=o.itemName.length+1),n>=2&&(2===n?(o.itemId=e.getUint16(r),r+=2):3===n&&(o.itemId=e.getUint32(r),r+=4),o.itemProtectionIndex=e.getUint16(r),r+=2,o.itemType=e.getUint32(r),r+=4,o.itemName=u(e,r),r+=o.itemName.length+1,o.itemType===Qe?(o.contentType=u(e,r),t+i>(r+=o.contentType.length+1)&&(o.contentEncoding=u(e,r),r+=o.contentEncoding.length+1)):o.itemType===et&&(o.itemUri=u(e,r),r+=o.itemUri.length+1)),o}(e,t,c,i+1,r):{type:void 0,length:r}}}function nt(e){if(y.USE_EXIF||y.USE_XMP||y.USE_ICC){var t={},n=function(e){for(var t=0;t+4+4<=e.byteLength;){var n=tt(e,t);if(void 0===n)break;if("meta"===n.type)return n;t+=n.length}}(e);return n?(y.USE_EXIF&&(t.tiffHeaderOffset=function(e,t){try{var n=function(e){return e.subBoxes.find((function(e){return"iinf"===e.type})).itemInfos.find((function(e){return e.itemType===Ze}))}(t).itemId,r=rt(t,n);return function(e,t){return t+4+e.getUint32(t)}(e,r.baseOffset+r.extents[0].extentOffset)}catch(e){return}}(e,n)),y.USE_XMP&&(t.xmpChunks=function(e){try{var t=function(e){return e.subBoxes.find((function(e){return"iinf"===e.type})).itemInfos.find((function(e){return e.itemType===Qe&&"application/rdf+xml"===e.contentType}))}(e).itemId,n=rt(e,t),r=rt(e,t).extents[0];return[{dataOffset:n.baseOffset+r.extentOffset,length:r.extentLength}]}catch(e){return}}(n)),y.USE_ICC&&(t.iccChunks=function(e){try{var t=e.subBoxes.find((function(e){return"iprp"===e.type})).subBoxes.find((function(e){return"ipco"===e.type})).properties.find((function(e){return"colr"===e.type})).icc;if(t)return[t]}catch(e){}}(n)),t.hasAppMarkers=void 0!==t.tiffHeaderOffset||void 0!==t.xmpChunks||void 0!==t.iccChunks,t):{hasAppMarkers:0}}return{}}function rt(e,t){return e.subBoxes.find((function(e){return"iloc"===e.type})).items.find((function(e){return e.itemId===t}))}function it(e,t){var n=a(e,t,4);if("prof"===n||"rICC"===n)return{offset:t+4,length:e.getUint32(t+4),chunkNumber:1,chunksTotal:1}}function ot(e,t,n){for(var r=[Ze,Qe],i=[],o=t;o=4&&function(e){var t=e.getUint16(0)===A.LITTLE_ENDIAN;return 42===e.getUint16(2,t)}(e)}(e))return lt(y.USE_EXIF?{hasAppMarkers:1,tiffHeaderOffset:0}:{},"tiff","TIFF");if(y.USE_JPEG&&function(e){return!!e&&e.byteLength>=P&&e.getUint16(0)===E}(e))return lt(function(e){for(var t,n,r,i,o,a,u,c,f,s=w;s+T+5<=e.byteLength;){if(y.USE_FILE&&oe(e,s))t=e.getUint16(s+x),n=s+x;else if(y.USE_FILE&&ae(e,s))t=e.getUint16(s+x),r=s+x;else if(y.USE_JFIF&&fe(e,s))t=e.getUint16(s+x),i=s+F;else if(y.USE_EXIF&&se(e,s))t=e.getUint16(s+x),o=s+M;else if(y.USE_XMP&&le(e,s))u||(u=[]),t=e.getUint16(s+x),u.push(pe(s,t));else if(y.USE_XMP&&de(e,s))u||(u=[]),t=e.getUint16(s+x),u.push(me(s,t));else if(y.USE_IPTC&&ge(e,s))t=e.getUint16(s+x),a=s+O;else if(y.USE_ICC&&ue(e,s)){t=e.getUint16(s+x);var l=s+N,d=t-(N-x),p=e.getUint8(s+B),m=e.getUint8(s+G);c||(c=[]),c.push({offset:l,length:d,chunkNumber:p,chunksTotal:m})}else if(y.USE_MPF&&ce(e,s))t=e.getUint16(s+x),f=s+k;else{if(!he(e,s)){if(ve(e,s)){s++;continue}break}t=e.getUint16(s+x)}s+=x+t}return{hasAppMarkers:s>w,fileDataOffset:n||r,jfifDataOffset:i,tiffHeaderOffset:o,iptcDataOffset:a,xmpChunks:u,iccChunks:c,mpfDataOffset:f}}(e),"jpeg","JPEG");if(y.USE_PNG&&function(e){return!!e&&a(e,0,ye.length)===ye}(e))return lt(function(e,t){for(var n={hasAppMarkers:0},r=ye.length;r+Se+be<=e.byteLength;){if(y.USE_PNG_FILE&&Oe(e,r))n.hasAppMarkers=1,n.pngHeaderOffset=r+Ue;else if(y.USE_XMP&&De(e,r)){var i=Be(e,r);void 0!==i&&(n.hasAppMarkers=1,n.xmpChunks=[{dataOffset:i,length:e.getUint32(r+Ie)-(i-(r+Ue))}])}else if(Le(e,r,t)){n.hasAppMarkers=1;var o=a(e,r+Ce,be);n.pngTextChunks||(n.pngTextChunks=[]),n.pngTextChunks.push({length:e.getUint32(r+Ie),type:o,offset:r+Ue})}else if(Ne(e,r))n.hasAppMarkers=1,n.tiffHeaderOffset=r+Ue;else if(y.USE_ICC&&t&&ke(e,r)){n.hasAppMarkers=1;var u=e.getUint32(r+Ie),c=r+Ue,f=Ge(e,c),s=f.profileName,l=f.compressionMethod,d=f.compressedProfileOffset;n.iccChunks||(n.iccChunks=[]),n.iccChunks.push({offset:d,length:u-(d-c),chunkNumber:1,chunksTotal:1,profileName:s,compressionMethod:l})}else Re(e,r)&&(n.hasAppMarkers=1,n.pngChunkOffsets||(n.pngChunkOffsets=[]),n.pngChunkOffsets.push(r+Ie));r+=e.getUint32(r+Ie)+Se+be+4}return n}(e,t),"png","PNG");if(y.USE_HEIC&&function(e){if(!e)return 0;try{var t=tt(e,0);return t&&-1!==["heic","heix","hevc","hevx","heim","heis","hevm","hevs","mif1"].indexOf(t.majorBrand)}catch(e){return 0}}(e))return lt(function(e){return nt(e)}(e),"heic","HEIC");if(y.USE_AVIF&&function(e){if(!e)return 0;try{var t=tt(e,0);return t&&"avif"===t.majorBrand}catch(e){return 0}}(e))return lt(function(e){return nt(e)}(e),"avif","AVIF");if(y.USE_WEBP&&function(e){return!!e&&"RIFF"===a(e,0,4)&&"WEBP"===a(e,8,4)}(e))return lt(function(e){for(var t,n,r,i,o=12,u=0;o+8.25){var t=e[0]/e[1];return Number.isInteger(t)?""+t:t.toFixed(1)}return 0!==e[0]?"1/".concat(Math.round(e[1]/e[0])):"0/".concat(e[1])},FNumber:function(e){return"f/".concat(e[0]/e[1])},FocalLength:function(e){return e[0]/e[1]+" mm"},FocalPlaneResolutionUnit:function(e){return 2===e?"inches":3===e?"centimeters":"Unknown"},LightSource:function(e){return 1===e?"Daylight":2===e?"Fluorescent":3===e?"Tungsten (incandescent light)":4===e?"Flash":9===e?"Fine weather":10===e?"Cloudy weather":11===e?"Shade":12===e?"Daylight fluorescent (D 5700 – 7100K)":13===e?"Day white fluorescent (N 4600 – 5400K)":14===e?"Cool white fluorescent (W 3900 – 4500K)":15===e?"White fluorescent (WW 3200 – 3700K)":17===e?"Standard light A":18===e?"Standard light B":19===e?"Standard light C":20===e?"D55":21===e?"D65":22===e?"D75":23===e?"D50":24===e?"ISO studio tungsten":255===e?"Other light source":"Unknown"},MeteringMode:function(e){return 1===e?"Average":2===e?"CenterWeightedAverage":3===e?"Spot":4===e?"MultiSpot":5===e?"Pattern":6===e?"Partial":255===e?"Other":"Unknown"},ResolutionUnit:function(e){return 2===e?"inches":3===e?"centimeters":"Unknown"},Saturation:function(e){return 0===e?"Normal":1===e?"Low saturation":2===e?"High saturation":"Unknown"},SceneCaptureType:function(e){return 0===e?"Standard":1===e?"Landscape":2===e?"Portrait":3===e?"Night scene":"Unknown"},Sharpness:function(e){return 0===e?"Normal":1===e?"Soft":2===e?"Hard":"Unknown"},ShutterSpeedValue:function(e){var t=Math.pow(2,e[0]/e[1]);return t<=1?"".concat(Math.round(1/t)):"1/".concat(Math.round(t))},WhiteBalance:function(e){return 0===e?"Auto white balance":1===e?"Manual white balance":"Unknown"},XResolution:function(e){return""+Math.round(e[0]/e[1])},YResolution:function(e){return""+Math.round(e[0]/e[1])}},pt={11:"ProcessingSoftware",254:{name:"SubfileType",description:function(e){return{0:"Full-resolution image",1:"Reduced-resolution image",2:"Single page of multi-page image",3:"Single page of multi-page reduced-resolution image",4:"Transparency mask",5:"Transparency mask of reduced-resolution image",6:"Transparency mask of multi-page image",7:"Transparency mask of reduced-resolution multi-page image",65537:"Alternate reduced-resolution image",4294967295:"Invalid"}[e]||"Unknown"}},255:{name:"OldSubfileType",description:function(e){return{0:"Full-resolution image",1:"Reduced-resolution image",2:"Single page of multi-page image"}[e]||"Unknown"}},256:"ImageWidth",257:"ImageLength",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",263:{name:"Thresholding",description:function(e){return{1:"No dithering or halftoning",2:"Ordered dither or halfton",3:"Randomized dither"}[e]||"Unknown"}},264:"CellWidth",265:"CellLength",266:{name:"FillOrder",description:function(e){return{1:"Normal",2:"Reversed"}[e]||"Unknown"}},269:"DocumentName",270:"ImageDescription",271:"Make",272:"Model",273:"StripOffsets",274:{name:"Orientation",description:function(e){return 1===e?"top-left":2===e?"top-right":3===e?"bottom-right":4===e?"bottom-left":5===e?"left-top":6===e?"right-top":7===e?"right-bottom":8===e?"left-bottom":"Undefined"}},277:"SamplesPerPixel",278:"RowsPerStrip",279:"StripByteCounts",280:"MinSampleValue",281:"MaxSampleValue",282:{name:"XResolution",description:dt.XResolution},283:{name:"YResolution",description:dt.YResolution},284:"PlanarConfiguration",285:"PageName",286:{name:"XPosition",description:function(e){return""+Math.round(e[0]/e[1])}},287:{name:"YPosition",description:function(e){return""+Math.round(e[0]/e[1])}},290:{name:"GrayResponseUnit",description:function(e){return{1:"0.1",2:"0.001",3:"0.0001",4:"1e-05",5:"1e-06"}[e]||"Unknown"}},296:{name:"ResolutionUnit",description:dt.ResolutionUnit},297:"PageNumber",301:"TransferFunction",305:"Software",306:"DateTime",315:"Artist",316:"HostComputer",317:"Predictor",318:{name:"WhitePoint",description:function(e){return e.map((function(e){return"".concat(e[0],"/").concat(e[1])})).join(", ")}},319:{name:"PrimaryChromaticities",description:function(e){return e.map((function(e){return"".concat(e[0],"/").concat(e[1])})).join(", ")}},321:"HalftoneHints",322:"TileWidth",323:"TileLength",330:"A100DataOffset",332:{name:"InkSet",description:function(e){return{1:"CMYK",2:"Not CMYK"}[e]||"Unknown"}},337:"TargetPrinter",338:{name:"ExtraSamples",description:function(e){return{0:"Unspecified",1:"Associated Alpha",2:"Unassociated Alpha"}[e]||"Unknown"}},339:{name:"SampleFormat",description:function(e){var t={1:"Unsigned",2:"Signed",3:"Float",4:"Undefined",5:"Complex int",6:"Complex float"};return Array.isArray(e)?e.map((function(e){return t[e]||"Unknown"})).join(", "):"Unknown"}},513:"JPEGInterchangeFormat",514:"JPEGInterchangeFormatLength",529:{name:"YCbCrCoefficients",description:function(e){return e.map((function(e){return""+e[0]/e[1]})).join("/")}},530:"YCbCrSubSampling",531:{name:"YCbCrPositioning",description:function(e){return 1===e?"centered":2===e?"co-sited":"undefined "+e}},532:{name:"ReferenceBlackWhite",description:function(e){return e.map((function(e){return""+e[0]/e[1]})).join(", ")}},700:"ApplicationNotes",18246:"Rating",18249:"RatingPercent",33432:{name:"Copyright",description:function(e){return e.join("; ")}},33550:"PixelScale",33723:"IPTC-NAA",33920:"IntergraphMatrix",33922:"ModelTiePoint",34118:"SEMInfo",34264:"ModelTransform",34377:"PhotoshopSettings",34665:"Exif IFD Pointer",34675:"ICC_Profile",34735:"GeoTiffDirectory",34736:"GeoTiffDoubleParams",34737:"GeoTiffAsciiParams",34853:"GPS Info IFD Pointer",40091:"XPTitle",40092:"XPComment",40093:"XPAuthor",40094:"XPKeywords",40095:"XPSubject",42112:"GDALMetadata",42113:"GDALNoData",50341:"PrintIM",50707:"DNGBackwardVersion",50708:"UniqueCameraModel",50709:"LocalizedCameraModel",50721:"ColorMatrix1",50722:"ColorMatrix2",50723:"CameraCalibration1",50724:"CameraCalibration2",50725:"ReductionMatrix1",50726:"ReductionMatrix2",50727:"AnalogBalance",50728:"AsShotNeutral",50729:"AsShotWhiteXY",50730:"BaselineExposure",50731:"BaselineNoise",50732:"BaselineSharpness",50734:"LinearResponseLimit",50735:"CameraSerialNumber",50736:"DNGLensInfo",50739:"ShadowScale",50741:{name:"MakerNoteSafety",description:function(e){return{0:"Unsafe",1:"Safe"}[e]||"Unknown"}},50778:{name:"CalibrationIlluminant1",description:dt.LightSource},50779:{name:"CalibrationIlluminant2",description:dt.LightSource},50781:"RawDataUniqueID",50827:"OriginalRawFileName",50828:"OriginalRawFileData",50831:"AsShotICCProfile",50832:"AsShotPreProfileMatrix",50833:"CurrentICCProfile",50834:"CurrentPreProfileMatrix",50879:"ColorimetricReference",50885:"SRawType",50898:"PanasonicTitle",50899:"PanasonicTitle2",50931:"CameraCalibrationSig",50932:"ProfileCalibrationSig",50933:"ProfileIFD",50934:"AsShotProfileName",50936:"ProfileName",50937:"ProfileHueSatMapDims",50938:"ProfileHueSatMapData1",50939:"ProfileHueSatMapData2",50940:"ProfileToneCurve",50941:{name:"ProfileEmbedPolicy",description:function(e){return{0:"Allow Copying",1:"Embed if Used",2:"Never Embed",3:"No Restrictions"}[e]||"Unknown"}},50942:"ProfileCopyright",50964:"ForwardMatrix1",50965:"ForwardMatrix2",50966:"PreviewApplicationName",50967:"PreviewApplicationVersion",50968:"PreviewSettingsName",50969:"PreviewSettingsDigest",50970:{name:"PreviewColorSpace",description:function(e){return{1:"Gray Gamma 2.2",2:"sRGB",3:"Adobe RGB",4:"ProPhoto RGB"}[e]||"Unknown"}},50971:"PreviewDateTime",50972:"RawImageDigest",50973:"OriginalRawFileDigest",50981:"ProfileLookTableDims",50982:"ProfileLookTableData",51043:"TimeCodes",51044:"FrameRate",51058:"TStop",51081:"ReelName",51089:"OriginalDefaultFinalSize",51090:"OriginalBestQualitySize",51091:"OriginalDefaultCropSize",51105:"CameraLabel",51107:{name:"ProfileHueSatMapEncoding",description:function(e){return{0:"Linear",1:"sRGB"}[e]||"Unknown"}},51108:{name:"ProfileLookTableEncoding",description:function(e){return{0:"Linear",1:"sRGB"}[e]||"Unknown"}},51109:"BaselineExposureOffset",51110:{name:"DefaultBlackRender",description:function(e){return{0:"Auto",1:"None"}[e]||"Unknown"}},51111:"NewRawImageDigest",51112:"RawToPreviewGain"};function mt(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.byteLength);f++){var s=Nt(e,t,n,r,i,o);void 0!==s&&(u[s.name]={id:s.id,value:s.value,description:s.description}),r+=12}if(y.USE_THUMBNAIL&&r"}l===Tt.tagTypes.ASCII&&(a=function(e){try{return e.map((function(e){return decodeURIComponent(escape(e))}))}catch(t){return e}}(a=function(e){for(var t=[],n=0,r=0;r5&&void 0!==arguments[5]&&arguments[5]&&(r*=Tt.typeSizes[n],n=Tt.tagTypes.BYTE);for(var a=0;a>31&1,e>>30&1,e>>29&1],n=[];return t[0]&&n.push("Dependent Parent Image"),t[1]&&n.push("Dependent Child Image"),t[2]&&n.push("Representative Image"),{value:t,description:n.join(", ")||"None"}}function Xt(e){var t=e>>24&7;return{value:t,description:0===t?"JPEG":"Unknown"}}function Jt(e){var t=16777215&e;return{value:t,description:{196608:"Baseline MP Primary Image",65537:"Large Thumbnail (VGA equivalent)",65538:"Large Thumbnail (Full HD equivalent)",131073:"Multi-Frame Image (Panorama)",131074:"Multi-Frame Image (Disparity)",131075:"Multi-Frame Image (Multi-Angle)",0:"Undefined"}[t]||"Unknown"}}function Yt(e,t,n,r){return function(e){return 0===e}(e)?0:Ht(t.value,e*Vt+8,Tt.getTypeSize("LONG"),n)+r}var qt={read:function(e,t){var n=function(e,t){return Tt.getShortAt(e,t)}(e,t),r=function(e,t,n){if(!(8>n)){var r=Tt.getByteAt(e,t+7);return{value:r,description:""+r}}}(e,t,n);return{"Bits Per Sample":Kt(e,t,n),"Image Height":$t(e,t,n),"Image Width":Zt(e,t,n),"Color Components":r,Subsampling:r&&Qt(e,t,r.value,n)}}};function Kt(e,t,n){if(!(3>n)){var r=Tt.getByteAt(e,t+2);return{value:r,description:""+r}}}function $t(e,t,n){if(!(5>n)){var r=Tt.getShortAt(e,t+3);return{value:r,description:"".concat(r,"px")}}}function Zt(e,t,n){if(!(7>n)){var r=Tt.getShortAt(e,t+5);return{value:r,description:"".concat(r,"px")}}}function Qt(e,t,n,r){if(!(8+3*n>r)){for(var i=[],o=0;o1?en(i)+tn(i):""}}}function en(e){var t={1:"Y",2:"Cb",3:"Cr",4:"I",5:"Q"};return e.map((function(e){return t[e[0]]})).join("")}function tn(e){var t={17:"4:4:4 (1 1)",18:"4:4:0 (1 2)",20:"4:4:1 (1 4)",33:"4:2:2 (2 1)",34:"4:2:0 (2 2)",36:"4:2:1 (2 4)",65:"4:1:1 (4 1)",66:"4:1:0 (4 2)"};return 0===e.length||void 0===e[0][1]||void 0===t[e[0][1]]?"":t[e[0][1]]}var nn={read:function(e,t){var n=function(e,t){return Tt.getShortAt(e,t)}(e,t),r=function(e,t,n){if(!(15>n)){var r=Tt.getByteAt(e,t+14);return{value:r,description:"".concat(r,"px")}}}(e,t,n),i=function(e,t,n){if(!(16>n)){var r=Tt.getByteAt(e,t+15);return{value:r,description:"".concat(r,"px")}}}(e,t,n),o={"JFIF Version":rn(e,t,n),"Resolution Unit":on(e,t,n),XResolution:un(e,t,n),YResolution:cn(e,t,n),"JFIF Thumbnail Width":r,"JFIF Thumbnail Height":i};if(void 0!==r&&void 0!==i){var a=function(e,t,n,r){if(!(0===n||16+n>r))return{value:e.buffer.slice(t+16,t+16+n),description:"<24-bit RGB pixel data>"}}(e,t,3*r.value*i.value,n);a&&(o["JFIF Thumbnail"]=a)}for(var u in o)void 0===o[u]&&delete o[u];return o}};function rn(e,t,n){if(!(9>n)){var r=Tt.getByteAt(e,t+7),i=Tt.getByteAt(e,t+7+1);return{value:256*r+i,description:r+"."+i}}}function on(e,t,n){if(!(10>n)){var r=Tt.getByteAt(e,t+9);return{value:r,description:an(r)}}}function an(e){return 0===e?"None":1===e?"inches":2===e?"cm":"Unknown"}function un(e,t,n){if(!(12>n)){var r=Tt.getShortAt(e,t+10);return{value:r,description:""+r}}}function cn(e,t,n){if(!(14>n)){var r=Tt.getShortAt(e,t+12);return{value:r,description:""+r}}}var fn={iptc:{256:{name:"Model Version",description:function(e){return""+((e[0]<<8)+e[1])}},261:{name:"Destination",repeatable:1},276:{name:"File Format",description:function(e){return""+((e[0]<<8)+e[1])}},278:{name:"File Format Version",description:function(e){return""+((e[0]<<8)+e[1])}},286:"Service Identifier",296:"Envelope Number",306:"Product ID",316:"Envelope Priority",326:{name:"Date Sent",description:sn},336:{name:"Time Sent",description:ln},346:{name:"Coded Character Set",description:dn,encoding_name:dn},356:"UNO",376:{name:"ARM Identifier",description:function(e){return""+((e[0]<<8)+e[1])}},378:{name:"ARM Version",description:function(e){return""+((e[0]<<8)+e[1])}},512:{name:"Record Version",description:function(e){return""+((e[0]<<8)+e[1])}},515:"Object Type Reference",516:"Object Attribute Reference",517:"Object Name",519:"Edit Status",520:{name:"Editorial Update",description:function(e){return"01"===S(e)?"Additional Language":"Unknown"}},522:"Urgency",524:{name:"Subject Reference",repeatable:1,description:function(e){var t=S(e).split(":");return t[2]+(t[3]?"/"+t[3]:"")+(t[4]?"/"+t[4]:"")}},527:"Category",532:{name:"Supplemental Category",repeatable:1},534:"Fixture Identifier",537:{name:"Keywords",repeatable:1},538:{name:"Content Location Code",repeatable:1},539:{name:"Content Location Name",repeatable:1},542:"Release Date",547:"Release Time",549:"Expiration Date",550:"Expiration Time",552:"Special Instructions",554:{name:"Action Advised",description:function(e){var t=S(e);return"01"===t?"Object Kill":"02"===t?"Object Replace":"03"===t?"Object Append":"04"===t?"Object Reference":"Unknown"}},557:{name:"Reference Service",repeatable:1},559:{name:"Reference Date",repeatable:1},562:{name:"Reference Number",repeatable:1},567:{name:"Date Created",description:sn},572:{name:"Time Created",description:ln},574:{name:"Digital Creation Date",description:sn},575:{name:"Digital Creation Time",description:ln},577:"Originating Program",582:"Program Version",587:{name:"Object Cycle",description:function(e){var t=S(e);return"a"===t?"morning":"p"===t?"evening":"b"===t?"both":"Unknown"}},592:{name:"By-line",repeatable:1},597:{name:"By-line Title",repeatable:1},602:"City",604:"Sub-location",607:"Province/State",612:"Country/Primary Location Code",613:"Country/Primary Location Name",615:"Original Transmission Reference",617:"Headline",622:"Credit",627:"Source",628:"Copyright Notice",630:{name:"Contact",repeatable:1},632:"Caption/Abstract",634:{name:"Writer/Editor",repeatable:1},637:{name:"Rasterized Caption",description:function(e){return e}},642:"Image Type",643:{name:"Image Orientation",description:function(e){var t=S(e);return"P"===t?"Portrait":"L"===t?"Landscape":"S"===t?"Square":"Unknown"}},647:"Language Identifier",662:{name:"Audio Type",description:function(e){var t=S(e),n=t.charAt(0),r=t.charAt(1),i="";return"1"===n?i+="Mono":"2"===n&&(i+="Stereo"),"A"===r?i+=", actuality":"C"===r?i+=", question and answer session":"M"===r?i+=", music, transmitted by itself":"Q"===r?i+=", response to a question":"R"===r?i+=", raw sound":"S"===r?i+=", scener":"V"===r?i+=", voicer":"W"===r&&(i+=", wrap"),""!==i?i:t}},663:{name:"Audio Sampling Rate",description:function(e){return parseInt(S(e),10)+" Hz"}},664:{name:"Audio Sampling Resolution",description:function(e){var t=parseInt(S(e),10);return t+(1===t?" bit":" bits")}},665:{name:"Audio Duration",description:function(e){var t=S(e);return t.length>=6?t.substr(0,2)+":"+t.substr(2,2)+":"+t.substr(4,2):t}},666:"Audio Outcue",698:"Short Document ID",699:"Unique Document ID",700:"Owner ID",712:{name:function(e){return 2===e.length?"ObjectData Preview File Format":"Record 2 destination"},description:function(e){if(2===e.length){var t=(e[0]<<8)+e[1];return 0===t?"No ObjectData":1===t?"IPTC-NAA Digital Newsphoto Parameter Record":2===t?"IPTC7901 Recommended Message Format":3===t?"Tagged Image File Format (Adobe/Aldus Image data)":4===t?"Illustrator (Adobe Graphics data)":5===t?"AppleSingle (Apple Computer Inc)":6===t?"NAA 89-3 (ANPA 1312)":7===t?"MacBinary II":8===t?"IPTC Unstructured Character Oriented File Format (UCOFF)":9===t?"United Press International ANPA 1312 variant":10===t?"United Press International Down-Load Message":11===t?"JPEG File Interchange (JFIF)":12===t?"Photo-CD Image-Pac (Eastman Kodak)":13===t?"Microsoft Bit Mapped Graphics File [*.BMP]":14===t?"Digital Audio File [*.WAV] (Microsoft & Creative Labs)":15===t?"Audio plus Moving Video [*.AVI] (Microsoft)":16===t?"PC DOS/Windows Executable Files [*.COM][*.EXE]":17===t?"Compressed Binary File [*.ZIP] (PKWare Inc)":18===t?"Audio Interchange File Format AIFF (Apple Computer Inc)":19===t?"RIFF Wave (Microsoft Corporation)":20===t?"Freehand (Macromedia/Aldus)":21===t?'Hypertext Markup Language "HTML" (The Internet Society)':22===t?"MPEG 2 Audio Layer 2 (Musicom), ISO/IEC":23===t?"MPEG 2 Audio Layer 3, ISO/IEC":24===t?"Portable Document File (*.PDF) Adobe":25===t?"News Industry Text Format (NITF)":26===t?"Tape Archive (*.TAR)":27===t?"Tidningarnas Telegrambyrå NITF version (TTNITF DTD)":28===t?"Ritzaus Bureau NITF version (RBNITF DTD)":29===t?"Corel Draw [*.CDR]":"Unknown format ".concat(t)}return S(e)}},713:{name:"ObjectData Preview File Format Version",description:function(e,t){var n={"00":{"00":"1"},"01":{"01":"1","02":"2","03":"3","04":"4"},"02":{"04":"4"},"03":{"01":"5.0","02":"6.0"},"04":{"01":"1.40"},"05":{"01":"2"},"06":{"01":"1"},11:{"01":"1.02"},20:{"01":"3.1","02":"4.0","03":"5.0","04":"5.5"},21:{"02":"2.0"}},r=S(e);if(t["ObjectData Preview File Format"]){var i=S(t["ObjectData Preview File Format"].value);if(n[i]&&n[i][r])return n[i][r]}return r}},714:"ObjectData Preview Data",1802:{name:"Size Mode",description:function(e){return e[0].toString()}},1812:{name:"Max Subfile Size",description:function(e){for(var t=0,n=0;n=8?t.substr(0,4)+"-"+t.substr(4,2)+"-"+t.substr(6,2):t}function ln(e){var t=S(e),n=t;return t.length>=6&&(n=t.substr(0,2)+":"+t.substr(2,2)+":"+t.substr(4,2),11===t.length&&(n+=t.substr(6,1)+t.substr(7,2)+":"+t.substr(9,2))),n}function dn(e){var t=S(e);return"%G"===t?"UTF-8":"%5"===t?"Windows-1252":"%/G"===t?"UTF-8 Level 1":"%/H"===t?"UTF-8 Level 2":"%/I"===t?"UTF-8 Level 3":"/A"===t?"ISO-8859-1":"/B"===t?"ISO-8859-2":"/C"===t?"ISO-8859-3":"/D"===t?"ISO-8859-4":"/@"===t?"ISO-8859-5":"/G"===t?"ISO-8859-6":"/F"===t?"ISO-8859-7":"/H"===t?"ISO-8859-8":"Unknown"}var pn={decode:function(e,t){var n=function(){if("undefined"!=typeof TextDecoder)return TextDecoder}();if("undefined"!=typeof n&&void 0!==e)try{return new n(e).decode(t instanceof DataView?t.buffer:Uint8Array.from(t))}catch(e){}return function(e){try{return decodeURIComponent(escape(e))}catch(t){return e}}(t.map((function(e){return String.fromCharCode(e)})).join(""))},TAG_HEADER_SIZE:5},mn=5,gn={read:function(e,t,n){try{if(Array.isArray(e))return Sn(new DataView(Uint8Array.from(e).buffer),{size:e.length},0,n);var r=function(e,t){for(;t+12<=e.byteLength;){var n=hn(e,t);if(vn(n))return{naaBlock:n,dataOffset:t+12};t+=12+n.size+yn(n)}throw Error("No IPTC NAA resource block.")}(e,t);return Sn(e,r.naaBlock,r.dataOffset,n)}catch(e){return{}}}};function hn(e,t){if(943868237!==e.getUint32(t,0))throw Error("Not an IPTC resource block.");return{type:e.getUint16(t+4),size:e.getUint16(t+10)}}function vn(e){return 1028===e.type}function yn(e){return e.size%2!=0?1:0}function Sn(e,t,n,r){for(var i={},o=void 0,a=n+t.size;ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n1&&n.push(Mn(e,t.slice(1))),n}(e,t),2,function(e){if(Array.isArray(e))return e}(r)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,i,o,a,u=[],c=1,f=0;try{for(o=(n=n.call(e)).next,!2;!(c=(r=o.call(n)).done)&&(u.push(r.value),2!==u.length);c=1);}catch(e){f=1,i=e}finally{try{if(!c&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(f)throw i}}return u}}(r)||function(e,t){if(e){if("string"==typeof e)return xn(e,2);var n={}.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?xn(e,2):void 0}}(r)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()),o=i[0],a=i[1],u=On(n,o);if(a){var c=On(n,a);u||c||(delete n._raw,On(n,Mn(e,t)))}return n}};function Mn(e,t){for(var n=t.reduce((function(e,t){return e+t.length}),0),r=new Uint8Array(n),i=0,o=0;o).+$/,"$1"),"application/xml");if("parsererror"===r.documentElement.nodeName)throw Error(r.documentElement.textContent);return{doc:r,raw:n}}(t),r=n.doc,i=n.raw;return e._raw=(e._raw||"")+i,l(e,kn(Ln(Dn(r),1))),1}catch(e){return 0}}function Dn(e){for(var t=0;t1&&void 0!==arguments[1]?arguments[1]:0,r=function(e){for(var t=[],n=0;n1&&void 0!==arguments[1]?arguments[1]:void 0;if(Array.isArray(e)){var n=function(e){return e.map((function(e){return void 0!==e.value?jn(e.value):jn(e)})).join(", ")}(e);return t&&"function"==typeof An[t]?An[t](e,n):n}if("object"===Tn(e))return function(e){var t=[];for(var n in e)t.push("".concat(zn(n),": ").concat(jn(e[n].value)));return t.join("; ")}(e);try{return t&&"function"==typeof An[t]?An[t](e):decodeURIComponent(escape(e))}catch(t){return e}}function zn(e){return"CiAdrCity"===e?"CreatorCity":"CiAdrCtry"===e?"CreatorCountry":"CiAdrExtadr"===e?"CreatorAddress":"CiAdrPcode"===e?"CreatorPostalCode":"CiAdrRegion"===e?"CreatorRegion":"CiEmailWork"===e?"CreatorWorkEmail":"CiTelWork"===e?"CreatorWorkPhone":"CiUrlWork"===e?"CreatorWorkUrl":e}function Vn(e){var t={};for(var n in e)try{Gn(n)||(t[_n(n)]=Hn(e[n],n))}catch(e){}return t}function Hn(e,t){return function(e){return Array.isArray(e)}(e)?function(e,t){return Qn(e[e.length-1],t)}(e,t):function(e){return"Resource"===e.attributes["rdf:parseType"]&&"string"==typeof e.value&&""===e.value.trim()}(e)?{value:"",attributes:{},description:""}:Wn(e)?Xn(e,t):Yn(e)?qn(e,t):Kn(e)?$n(e,t):function(e){return void 0!==Zn(e.value)}(e)?function(e,t){var n=Zn(e.value).value["rdf:li"],r=Jn(e),i=[];return void 0===n?n=[]:Array.isArray(n)||(n=[n]),n.forEach((function(e){i.push(function(e){return Wn(e)?Xn(e):Yn(e)?qn(e).value:Kn(e)?$n(e).value:Qn(e)}(e))})),{value:i,attributes:r,description:jn(i,t)}}(e,t):Qn(e,t)}function Wn(e){return"Resource"===e.attributes["rdf:parseType"]&&void 0!==e.value["rdf:value"]||void 0!==e.value["rdf:Description"]&&void 0!==e.value["rdf:Description"].value["rdf:value"]}function Xn(e,t){var n=Jn(e);void 0!==e.value["rdf:Description"]&&(e=e.value["rdf:Description"]),l(n,Jn(e),function(e){var t={};for(var n in e.value)"rdf:value"===n||Gn(n)||(t[_n(n)]=e.value[n].value);return t}(e));var r=function(e){return er(e.value["rdf:value"])||e.value["rdf:value"].value}(e);return{value:r,attributes:n,description:jn(r,t)}}function Jn(e){var t={};for(var n in e.attributes)"rdf:parseType"===n||"rdf:resource"===n||Gn(n)||(t[_n(n)]=e.attributes[n]);return t}function Yn(e){return"Resource"===e.attributes["rdf:parseType"]||void 0!==e.value["rdf:Description"]&&void 0===e.value["rdf:Description"].value["rdf:value"]}function qn(e,t){var n={value:{},attributes:{}};return void 0!==e.value["rdf:Description"]&&(l(n.value,Rn(e.value["rdf:Description"].attributes)),l(n.attributes,Jn(e)),e=e.value["rdf:Description"]),l(n.value,Vn(e.value)),n.description=jn(n.value,t),n}function Kn(e){return 0===Object.keys(e.value).length&&void 0===e.attributes["xml:lang"]&&void 0===e.attributes["rdf:resource"]}function $n(e,t){var n=Rn(e.attributes);return{value:n,attributes:{},description:jn(n,t)}}function Zn(e){return e["rdf:Bag"]||e["rdf:Seq"]||e["rdf:Alt"]}function Qn(e,t){var n=er(e)||kn(e.value);return{value:n,attributes:Jn(e),description:jn(n,t)}}function er(e){return e.attributes&&e.attributes["rdf:resource"]}function tr(e){return tr="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},tr(e)}function nr(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=tr(e)||!e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var r=n.call(e,"string");if("object"!=tr(r))return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return e+""}(e);return"symbol"==tr(t)?t:t+""}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:1,configurable:1,writable:1}):e[t]=n,e}function rr(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n>>31==0?1:-1,a=(2130706432&i)>>>32-n,u=i&parseInt(m("1",32-n),2);return o*function(e,t){return parseInt(e.replace(".",""),2)/Math.pow(2,(e.split(".")[1]||"").length)}(a.toString(2)+"."+(m("0",32-n-(r=u.toString(2)).length)+r))}function fr(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n"}r[s||ir[c].name]=p}else t&&(r["undefined-".concat(c)]=p)}i+=l+l%2}return r}},lr="8BIM",dr=2,pr=4,mr=lr.length;function gr(e,t){var n,r=(2,function(e){if(Array.isArray(e))return e}(n=f(e,t))||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,i,o,a,u=[],c=1,f=0;try{for(o=(n=n.call(e)).next,!2;!(c=(r=o.call(n)).done)&&(u.push(r.value),2!==u.length);c=1);}catch(e){f=1,i=e}finally{try{if(!c&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(f)throw i}}return u}}(n)||function(e,t){if(e){if("string"==typeof e)return fr(e,2);var n={}.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?fr(e,2):void 0}}(n)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()),i=r[0];return{tagName:r[1],tagNameSize:1+i+(i%2==0?1:0)}}var hr={desc:{name:"ICC Description"},cprt:{name:"ICC Copyright"},dmdd:{name:"ICC Device Model Description"},vued:{name:"ICC Viewing Conditions Description"},dmnd:{name:"ICC Device Manufacturer for Display"},tech:{name:"Technology"}},vr={4:{name:"Preferred CMM type",value:function(e,t){return a(e,t,4)},description:function(e){return null!==e?yr(e):""}},8:{name:"Profile Version",value:function(e,t){return e.getUint8(t).toString(10)+"."+(e.getUint8(t+1)>>4).toString(10)+"."+(e.getUint8(t+1)%16).toString(10)}},12:{name:"Profile/Device class",value:function(e,t){return a(e,t,4)},description:function(e){switch(e.toLowerCase()){case"scnr":return"Input Device profile";case"mntr":return"Display Device profile";case"prtr":return"Output Device profile";case"link":return"DeviceLink profile";case"abst":return"Abstract profile";case"spac":return"ColorSpace profile";case"nmcl":return"NamedColor profile";case"cenc":return"ColorEncodingSpace profile";case"mid ":return"MultiplexIdentification profile";case"mlnk":return"MultiplexLink profile";case"mvis":return"MultiplexVisualization profile";default:return e}}},16:{name:"Color Space",value:function(e,t){return a(e,t,4)}},20:{name:"Connection Space",value:function(e,t){return a(e,t,4)}},24:{name:"ICC Profile Date",value:function(e,t){return function(e,t){var n=e.getUint16(t),r=e.getUint16(t+2)-1,i=e.getUint16(t+4),o=e.getUint16(t+6),a=e.getUint16(t+8),u=e.getUint16(t+10);return new Date(Date.UTC(n,r,i,o,a,u))}(e,t).toISOString()}},36:{name:"ICC Signature",value:function(e,t){return n=e.buffer.slice(t,t+4),String.fromCharCode.apply(null,new Uint8Array(n));var n}},40:{name:"Primary Platform",value:function(e,t){return a(e,t,4)},description:function(e){return yr(e)}},48:{name:"Device Manufacturer",value:function(e,t){return a(e,t,4)},description:function(e){return yr(e)}},52:{name:"Device Model Number",value:function(e,t){return a(e,t,4)}},64:{name:"Rendering Intent",value:function(e,t){return e.getUint32(t)},description:function(e){switch(e){case 0:return"Perceptual";case 1:return"Relative Colorimetric";case 2:return"Saturation";case 3:return"Absolute Colorimetric";default:return e}}},80:{name:"Profile Creator",value:function(e,t){return a(e,t,4)}}};function yr(e){switch(e.toLowerCase()){case"appl":return"Apple";case"adbe":return"Adobe";case"msft":return"Microsoft";case"sunw":return"Sun Microsystems";case"sgi":return"Silicon Graphics";case"tgnt":return"Taligent";default:return e}}var Sr={read:function(e,t,n){return n&&t[0].compressionMethod!==g?function(e,t){return t[0].compressionMethod!==h?{}:v(new DataView(e.buffer.slice(t[0].offset,t[0].offset+t[0].length)),t[0].compressionMethod,"utf-8","dataview").then(xr).catch((function(){return{}}))}(e,t):function(e,t){try{for(var n=t.reduce((function(e,t){return e+t.length}),0),r=new Uint8Array(n),i=0,o=function(e){return Array.isArray(e)?new DataView(Uint8Array.from(e).buffer).buffer:e.buffer}(e),a=function(e){var n=t.find((function(t){return t.chunkNumber===e}));if(!n)throw Error("ICC chunk ".concat(e," not found"));var a=o.slice(n.offset,n.offset+n.length),u=new Uint8Array(a);r.set(u,i),i+=u.length},u=1;u<=t.length;u++)a(u);return xr(new DataView(r.buffer))}catch(e){return{}}}(e,t)}},br=84,Ir=128,Cr="acsp",Ur="desc",Ar="mluc",Pr="text",Er="sig ",wr=12;function Tr(e,t){return e.lengtht.length)return r;var y=a(e,h,4);if(y===Ur){var S=e.getUint32(h+8);if(S>v)return r;Mr(r,g,Fr(t.slice(h+12,h+S+11)))}else if(y===Ar){for(var b=e.getUint32(h+8),I=e.getUint32(h+12),C=h+16,U=[],A=0;Ae.byteLength)){var n=Tt.getLongAt(e,t);return{value:n,description:"".concat(n,"px")}}}function Lr(e,t){if(!(t+4+4>e.byteLength)){var n=Tt.getLongAt(e,t+4);return{value:n,description:"".concat(n,"px")}}}function Nr(e,t){if(!(t+8+1>e.byteLength)){var n=Tt.getByteAt(e,t+8);return{value:n,description:"".concat(n)}}}function kr(e,t){if(!(t+9+1>e.byteLength)){var n=Tt.getByteAt(e,t+9);return{value:n,description:{0:"Grayscale",2:"RGB",3:"Palette",4:"Grayscale with Alpha",6:"RGB with Alpha"}[n]||"Unknown"}}}function Rr(e,t){if(!(t+10+1>e.byteLength)){var n=Tt.getByteAt(e,t+10);return{value:n,description:0===n?"Deflate/Inflate":"Unknown"}}}function Br(e,t){if(!(t+11+1>e.byteLength)){var n=Tt.getByteAt(e,t+11);return{value:n,description:0===n?"Adaptive":"Unknown"}}}function Gr(e,t){if(!(t+12+1>e.byteLength)){var n=Tt.getByteAt(e,t+12);return{value:n,description:{0:"Noninterlaced",1:"Adam7 Interlace"}[n]||"Unknown"}}}function _r(e){return _r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},_r(e)}var jr={read:function(e,t,n,r){for(var i={},o=[],a=0;a0?Promise.all(o):void 0}}},zr="STATE_KEYWORD",Vr="STATE_COMPRESSION",Hr="STATE_LANG",Wr="STATE_TRANSLATED_KEYWORD",Xr="STATE_TEXT",Jr=1,Yr=1,qr=6;function Kr(e,t,n,r,i){for(var o,a=[],u=[],c=[],f=zr,s=g,l=0;l".split(""),r,u,a)})):Qr(p,r,u,a)}function $r(e){var t=e.type,n=e.dataView,r=e.offset;if(t===Ee){if(n.getUint8(r)===Yr)return n.getUint8(r+1)}else if(t===we)return n.getUint8(r);return g}function Zr(e,t){return t===zr&&[Ee,we].includes(e)?Vr:t===Vr?e===Ee?Hr:Xr:t===Hr?Wr:Xr}function Qr(e,t,n,r){var i=function(e){return e instanceof DataView?a(e,0,e.byteLength):e}(e);return{name:ei(t,n,r),value:i,description:t===Ee?ti(e):i}}function ei(e,t,n){var r=s(n);if(e===Pe||0===t.length)return r;var i=s(t);return"".concat(r," (").concat(i,")")}function ti(e){return pn.decode("UTF-8",e)}function ni(e,t){return"raw profile type exif"===e.toLowerCase()&&"exif"===t.substring(1,5)}function ri(e,t){return"raw profile type iptc"===e.toLowerCase()&&"iptc"===t.substring(1,5)}function ii(e){return function(e){for(var t=new DataView(new ArrayBuffer(e.length/2)),n=0;ne.byteLength)){var t=a(e,3,3);return{value:t,description:t}}}function yi(e){if(!(8>e.byteLength)){var t=e.getUint16(6,1);return{value:t,description:"".concat(t,"px")}}}function Si(e){if(!(10>e.byteLength)){var t=e.getUint16(8,1);return{value:t,description:"".concat(t,"px")}}}function bi(e){if(!(11>e.byteLength)){var t=(128&e.getUint8(10))>>>7;return{value:t,description:1===t?"Yes":"No"}}}function Ii(e){if(!(11>e.byteLength)){var t=1+((112&e.getUint8(10))>>>4);return{value:t,description:"".concat(t," ").concat(1===t?"bit":"bits")}}}function Ci(e){if(!(11>e.byteLength)){var t=1+(7&e.getUint8(10));return{value:t,description:"".concat(t," ").concat(1===t?"bit":"bits")}}}var Ui=[6,7,99],Ai={get:function(e,t,n){if((i=t)&&(void 0===i.Compression||Ui.includes(i.Compression.value))&&i.JPEGInterchangeFormat&&i.JPEGInterchangeFormat.value&&i.JPEGInterchangeFormatLength&&i.JPEGInterchangeFormatLength.value){t.type="image/jpeg";var r=n+t.JPEGInterchangeFormat.value;t.image=e.buffer.slice(r,r+t.JPEGInterchangeFormatLength.value),d(t,"base64",(function(){return p(this.image)}))}var i;return t}};function Pi(e){this.name="MetadataMissingError",this.message=e||"No Exif data",this.stack=Error().stack}Pi.prototype=Error();var Ei={MetadataMissingError:Pi},wi={load:xi,loadView:Mi,errors:Ei},Ti=Ei;function xi(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return function(e){return"string"==typeof e}(e)?(n.async=1,function(e,t){return/^\w+:\/\//.test(e)?"undefined"!=typeof fetch?function(e){var t=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).length,n={method:"GET"};return Number.isInteger(t)&&t>=0&&(n.headers={range:"bytes=0-".concat(t-1)}),fetch(e,n).then((function(e){return e.arrayBuffer()}))}(e,t):function(e){var t=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).length;return new Promise((function(n,r){var i={};Number.isInteger(t)&&t>=0&&(i.headers={range:"bytes=0-".concat(t-1)});var o=function(e){return/^https:\/\//.test(e)?require("https").get:require("http").get}(e);o(e,i,(function(e){if(e.statusCode>=200&&e.statusCode<=299){var t=[];e.on("data",(function(e){return t.push(Buffer.from(e))})),e.on("error",(function(e){return r(e)})),e.on("end",(function(){return n(Buffer.concat(t))}))}else r("Could not fetch file: ".concat(e.statusCode," ").concat(e.statusMessage)),e.resume()})).on("error",(function(e){return r(e)}))}))}(e,t):function(e){return/^data:[^;,]*(;base64)?,/.test(e)}(e)?Promise.resolve(function(e){var t=e.substring(e.indexOf(",")+1);if(-1!==e.indexOf(";base64")){if("undefined"!=typeof atob)return Uint8Array.from(atob(t),(function(e){return e.charCodeAt(0)})).buffer;if("undefined"==typeof Buffer)return;return"undefined"!=typeof Buffer.from?Buffer.from(t,"base64"):new Buffer(t,"base64")}var n=decodeURIComponent(t);return"undefined"!=typeof Buffer?"undefined"!=typeof Buffer.from?Buffer.from(n):new Buffer(n):Uint8Array.from(n,(function(e){return e.charCodeAt(0)})).buffer}(e)):function(e){var t=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).length;return new Promise((function(n,r){var i=function(){try{return require("fs")}catch(e){return}}();i.open(e,(function(o,a){o?r(o):i.stat(e,(function(o,u){if(o)r(o);else{var c=Math.min(u.size,void 0!==t?t:u.size),f=Buffer.alloc(c),s={buffer:f,length:c};i.read(a,s,(function(t){t?r(t):i.close(a,(function(t){t&&console.warn("Could not close file ".concat(e,":"),t),n(f)}))}))}}))}))}))}(e,t)}(e,n).then((function(e){return Fi(e,n)}))):function(e){return"undefined"!=typeof window&&"undefined"!=typeof File&&e instanceof File}(e)?(n.async=1,(t=e,new Promise((function(e,n){var r=new FileReader;r.onload=function(t){return e(t.target.result)},r.onerror=function(){return n(r.error)},r.readAsArrayBuffer(t)}))).then((function(e){return Fi(e,n)}))):Fi(e,n)}function Fi(e,t){return function(e){try{return Buffer.isBuffer(e)}catch(e){return 0}}(e)&&(e=new Uint8Array(e).buffer),Mi(function(e){try{return new DataView(e)}catch(t){return new i(e)}}(e),t)}function Mi(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{expanded:0,async:0,includeUnknown:0},n=t.expanded,r=void 0===n?0:n,i=t.async,o=void 0===i?0:i,a=t.includeUnknown,u=void 0===a?0:a,c=0,f={},d=[],p=st.parseAppMarkers(e,o),m=p.fileType,g=p.fileDataOffset,h=p.jfifDataOffset,v=p.tiffHeaderOffset,S=p.iptcDataOffset,b=p.xmpChunks,C=p.iccChunks,U=p.mpfDataOffset,A=p.pngHeaderOffset,P=p.pngTextChunks,E=p.pngChunkOffsets,w=p.vp8xChunkOffset,T=p.gifHeaderOffset;if(y.USE_JPEG&&y.USE_FILE&&function(e){return void 0!==e}(g)){c=1;var x=qt.read(e,g);r?f.file=x:f=l({},f,x)}if(y.USE_JPEG&&y.USE_JFIF&&function(e){return void 0!==e}(h)){c=1;var F=nn.read(e,h);r?f.jfif=F:f=l({},f,F)}if(y.USE_EXIF&&function(e){return void 0!==e}(v)){c=1;var M=jt.read(e,v,u);if(M.Thumbnail&&(f.Thumbnail=M.Thumbnail,delete M.Thumbnail),r?(f.exif=M,function(e){if(e.exif){if(e.exif.GPSLatitude&&e.exif.GPSLatitudeRef)try{e.gps=e.gps||{},e.gps.Latitude=I(e.exif.GPSLatitude.value),"S"===e.exif.GPSLatitudeRef.value.join("")&&(e.gps.Latitude=-e.gps.Latitude)}catch(e){}if(e.exif.GPSLongitude&&e.exif.GPSLongitudeRef)try{e.gps=e.gps||{},e.gps.Longitude=I(e.exif.GPSLongitude.value),"W"===e.exif.GPSLongitudeRef.value.join("")&&(e.gps.Longitude=-e.gps.Longitude)}catch(e){}if(e.exif.GPSAltitude&&e.exif.GPSAltitudeRef)try{e.gps=e.gps||{},e.gps.Altitude=e.exif.GPSAltitude.value[0]/e.exif.GPSAltitude.value[1],1===e.exif.GPSAltitudeRef.value&&(e.gps.Altitude=-e.gps.Altitude)}catch(e){}}}(f)):f=l({},f,M),y.USE_TIFF&&y.USE_IPTC&&M["IPTC-NAA"]&&!Oi(S)){var O=gn.read(M["IPTC-NAA"].value,0,u);r?f.iptc=O:f=l({},f,O)}if(y.USE_TIFF&&y.USE_XMP&&M.ApplicationNotes&&!Di(b)){var D=Fn.read(s(M.ApplicationNotes.value));r?f.xmp=D:(delete D._raw,f=l({},f,D))}if(y.USE_PHOTOSHOP&&M.ImageSourceData){var L=sr.read(M.PhotoshopSettings.value,u);r?f.photoshop=L:f=l({},f,L)}if(y.USE_TIFF&&y.USE_ICC&&M.ICC_Profile&&!Li(C)){var N=Sr.read(M.ICC_Profile.value,[{offset:0,length:M.ICC_Profile.value.length,chunkNumber:1,chunksTotal:1}]);r?f.icc=N:f=l({},f,N)}}if(y.USE_JPEG&&y.USE_IPTC&&Oi(S)){c=1;var k=gn.read(e,S,u);r?f.iptc=k:f=l({},f,k)}if(y.USE_XMP&&Di(b)){c=1;var R=Fn.read(e,b);r?f.xmp=R:(delete R._raw,f=l({},f,R))}if((y.USE_JPEG||y.USE_WEBP)&&y.USE_ICC&&Li(C)){c=1;var B=Sr.read(e,C,o);B instanceof Promise?d.push(B.then(Y)):Y(B)}if(y.USE_MPF&&function(e){return void 0!==e}(U)){c=1;var G=zt.read(e,U,u);r?f.mpf=G:f=l({},f,G)}if(y.USE_PNG&&y.USE_PNG_FILE&&void 0!==A){c=1;var _=Or.read(e,A);r?(f.png=f.png?l({},f.png,_):_,f.pngFile=_):f=l({},f,_)}if(y.USE_PNG&&function(e){return void 0!==e}(P)){c=1;var j=jr.read(e,P,o,u),z=j.readTags,V=j.readTagsPromise;q(z),V&&d.push(V.then((function(e){return e.forEach(q)})))}if(y.USE_PNG&&function(e){return void 0!==e}(E)){c=1;var H=oi.read(e,E);r?f.png=f.png?l({},f.png,H):H:f=l({},f,H)}if(y.USE_WEBP&&function(e){return void 0!==e}(w)){c=1;var W=di.read(e,w);r?f.riff=f.riff?l({},f.riff,W):W:f=l({},f,W)}if(y.USE_GIF&&function(e){return void 0!==e}(T)){c=1;var X=hi.read(e,T);r?f.gif=f.gif?l({},f.gif,X):X:f=l({},f,X)}var J=(y.USE_JPEG||y.USE_WEBP)&&y.USE_EXIF&&y.USE_THUMBNAIL&&Ai.get(e,f.Thumbnail,v);if(J?(c=1,f.Thumbnail=J):delete f.Thumbnail,m&&(r?(f.file||(f.file={}),f.file.FileType=m):f.FileType=m,c=1),!c)throw new Ei.MetadataMissingError;return o?Promise.all(d).then((function(){return f})):f;function Y(e){r?f.icc=e:f=l({},f,e)}function q(e){if(r){for(var t=0,n=["exif","iptc"];t0}function Li(e){return Array.isArray(e)&&e.length>0}return t}()})); +//# sourceMappingURL=exif-reader.js.map \ No newline at end of file diff --git a/extensions/sd-fast-pnginfo/javascript/fast-pnginfo.js b/extensions/sd-fast-pnginfo/javascript/fast-pnginfo.js new file mode 100644 index 0000000000000000000000000000000000000000..817ed5a385df5a6ecc32ccabc21caa04085afbcd --- /dev/null +++ b/extensions/sd-fast-pnginfo/javascript/fast-pnginfo.js @@ -0,0 +1,170 @@ +async function fastpnginfo_parse_image() { + window.EnCrypt = ''; + window.EPwdSha = ''; + window.SfwNAI = ''; + window.SrcNAI = ''; + + const txt_output_el = gradioApp().querySelector("#fastpnginfo_geninfo > label > textarea"); + const fastpnginfoHTML = gradioApp().querySelector("#fastpnginfo_html"); + + let img_el = gradioApp().querySelector("#fastpnginfo_image > div[data-testid='image'] > div > img"); + if (!img_el) { + fastpnginfoHTML.innerHTML = plainTextToHTML(''); + return; + } + + let response = await fetch(img_el.src); + let img_blob = await response.blob(); + let arrayBuffer = await img_blob.arrayBuffer(); + let tags = ExifReader.load(arrayBuffer); + let output = ""; + + if (tags) { + window.EnCrypt = tags.Encrypt ? tags.Encrypt.description : ''; + window.EPwdSha = tags.EncryptPwdSha ? tags.EncryptPwdSha.description : ''; + + if (tags.parameters) { + output = tags.parameters.description; + + } else if (tags.UserComment && tags.UserComment.value) { + const ray = tags.UserComment.value; + const result = []; + var ar = ray; + var pos = ar.indexOf(0) + 1; + + for(var i = pos; i < ar.length; i += 2) { + var inDEX = ar[i]; + var nEXT = ar[i + 1]; + + if(inDEX === 0 && nEXT === 32) { + result.push(32); + continue; + } + + let vaLUE = inDEX * 256 + nEXT; + result.push(vaLUE); + } + + const userComment = new TextDecoder("utf-16").decode(new Uint16Array(result)); + output = userComment.trim().replace(/^UNICODE[\x00-\x20]*/, ""); + + } else if (tags["Software"] && tags["Software"].description === "NovelAI" && + tags.Comment && tags.Comment.description) { + + window.SfwNAI = tags["Software"] ? tags["Software"].description : ''; + window.SrcNAI = tags["Source"] ? tags["Source"].description : ''; + + const nai = JSON.parse(tags.Comment.description); + nai.sampler = "Euler"; + + output = convertNAI(nai["prompt"]) + + "\nNegative prompt: " + convertNAI(nai["uc"]) + + "\nSteps: " + (nai["steps"]) + + ", Sampler: " + (nai["sampler"]) + + ", CFG scale: " + (parseFloat(nai["scale"]).toFixed(1)) + + ", Seed: " + (nai["seed"]) + + ", Size: " + (nai["width"]) + "x" + (nai["height"]) + + ", Clip skip: 2, ENSD: 31337"; + + } else { + output = "Nothing To See Here"; + } + + if (output) { + txt_output_el.value = output; + updateInput(txt_output_el); + fastpnginfoHTML.classList.add('prose'); + fastpnginfoHTML.innerHTML = plainTextToHTML(output); + } + } + return tags; +} + +function round(v) { return Math.round(v * 10000) / 10000 } + +function convertNAI(input) { + const re_attention = /\{|\[|\}|\]|[^\{\}\[\]]+/gmu; + let text = input.replaceAll("(", "\(").replaceAll(")", "\)").replace(/\\{2,}(\(|\))/gim, '\$1'); + let res = [], curly_brackets = [], square_brackets = []; + const curly_bracket_multiplier = 1.05, square_bracket_multiplier = 1 / 1.05; + function multiply_range(start, multiplier) { + for (let pos = start; pos < res.length; pos++) res[pos][1] = round(res[pos][1] * multiplier); + } + for (const match of text.matchAll(re_attention)) { + let word = match[0]; + if (word == "{") curly_brackets.push(res.length); + else if (word == "[") square_brackets.push(res.length); + else if (word == "}" && curly_brackets.length > 0) multiply_range(curly_brackets.pop(), curly_bracket_multiplier); + else if (word == "]" && square_brackets.length > 0) multiply_range(square_brackets.pop(), square_bracket_multiplier); + else res.push([word, 1.0]); + } + for (const pos of curly_brackets) multiply_range(pos, curly_bracket_multiplier); + for (const pos of square_brackets) multiply_range(pos, square_bracket_multiplier); + if (res.length == 0) res = [["", 1.0]]; + let i = 0; + while (i + 1 < res.length) { + if (res[i][1] == res[i + 1][1]) { + res[i][0] += res[i + 1][0]; + res.splice(i + 1, 1); + } else i++; + } + + let result = ""; + for (let i = 0; i < res.length; i++) { + if (res[i][1] == 1.0) result += res[i][0]; + else result += `(${res[i][0]}:${res[i][1]})`; + } + return result; +} + +function plainTextToHTML(inputs) { + const EnCrypt = window.EnCrypt; + const EPwdSha = window.EPwdSha; + const SfwNAI = window.SfwNAI; + const SrcNAI = window.SrcNAI; + + var box = document.querySelector("#fastpnginfo_panel"); + var sty = "display: block; margin-bottom: 2px;"; + var mTop = "margin-top: 16px;"; + + var pro = `Prompt`; + var neg = `Negative Prompt`; + var prm = `Params`; + var ciH = `Civitai Hashes`; + + var eNC = `Encrypt`; + var pWD = `EncryptPwdSha`; + + var sFW = `Software`; + var sRC = `Source`; + + var br = /\n/g; + + if (inputs === undefined || inputs === null || inputs.trim() === '') { + box.style.opacity = '0'; + } else { + if (inputs.includes("Nothing To See Here")) { + pro = ''; + } + + box.style.opacity = '1'; + inputs = inputs.replace(//g, '>').replace(br, '
'); + inputs = inputs.replace(/Negative prompt:/, neg).replace(/Steps:/, match => prm + match); + inputs = inputs.replace(/, Hashes:/, ciH); + + if (EnCrypt && EnCrypt.trim() !== '') { + inputs += `
${eNC}${EnCrypt}`; + } + if (EPwdSha && EPwdSha.trim() !== '') { + inputs += `
${pWD}${EPwdSha}`; + } + if (SfwNAI && SfwNAI.trim() !== '') { + inputs += `
${sFW}${SfwNAI}`; + } + if (SrcNAI && SrcNAI.trim() !== '') { + inputs += `
${sRC}${SrcNAI}`; + } + } + + return `
${pro}

${inputs}

`; +} \ No newline at end of file diff --git a/extensions/sd-fast-pnginfo/scripts/__pycache__/fast-pnginfo.cpython-310.pyc b/extensions/sd-fast-pnginfo/scripts/__pycache__/fast-pnginfo.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e059b6b942e74333d2f22d71c1749cecfc242ed Binary files /dev/null and b/extensions/sd-fast-pnginfo/scripts/__pycache__/fast-pnginfo.cpython-310.pyc differ diff --git a/extensions/sd-fast-pnginfo/scripts/fast-pnginfo.py b/extensions/sd-fast-pnginfo/scripts/fast-pnginfo.py new file mode 100644 index 0000000000000000000000000000000000000000..e95f453a43ed40e8e480ddbdbc70635e0bca1443 --- /dev/null +++ b/extensions/sd-fast-pnginfo/scripts/fast-pnginfo.py @@ -0,0 +1,28 @@ +import modules.generation_parameters_copypaste as tempe # type: ignore +from modules.ui_components import FormRow, FormColumn +from modules import script_callbacks +import gradio as gr + +def on_ui_tabs(): + with gr.Blocks(analytics_enabled=False) as fast_pnginfo: + with FormRow(equal_height=False): + with FormColumn(variant="panel"): + image = gr.Image(elem_id="fastpnginfo_image", source="upload", interactive=True, type="pil", show_label=False) + + with FormRow(variant="compact"): + buttons = tempe.create_buttons(["txt2img", "img2img", "inpaint", "extras"]) + + with gr.Column(variant="panel", scale=2, elem_id="fastpnginfo_panel"): + geninfo = gr.Textbox(elem_id="fastpnginfo_geninfo", label="RAW", visible=False) + gr.HTML(elem_id="fastpnginfo_html") + + for tabname, button in buttons.items(): + tempe.register_paste_params_button( + tempe.ParamBinding(paste_button=button, tabname=tabname, source_text_component=geninfo, source_image_component=image)) + + image.change(fn=None, inputs=[], outputs=[], _js="() => {fastpnginfo_parse_image();}") + + return [(fast_pnginfo, "Fast PNG Info", "fast_pnginfo")] + +script_callbacks.on_ui_tabs(on_ui_tabs) +print("\033[38;5;208m▶\033[0m Fast PNG Info") \ No newline at end of file diff --git a/extensions/sd-fast-pnginfo/style.css b/extensions/sd-fast-pnginfo/style.css new file mode 100644 index 0000000000000000000000000000000000000000..2a0c9259aa0d0e2c73e0299f2f3a6942d98fa33e --- /dev/null +++ b/extensions/sd-fast-pnginfo/style.css @@ -0,0 +1,7 @@ +#fastpnginfo_panel {opacity: 0} + +.fastpnginfo_cont { + overflow-wrap: break-word; + max-width: 100%; + overflow-x: auto; +} \ No newline at end of file diff --git a/extensions/sd-forge-couple/.github/FUNDING.yml b/extensions/sd-forge-couple/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..b79bf8ec506329c1b756428563c1ad14a28c1886 --- /dev/null +++ b/extensions/sd-forge-couple/.github/FUNDING.yml @@ -0,0 +1 @@ +ko_fi: haoming diff --git a/extensions/sd-forge-couple/.gitignore b/extensions/sd-forge-couple/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..bee8a64b79a99590d5303307144172cfe824fbf7 --- /dev/null +++ b/extensions/sd-forge-couple/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/extensions/sd-forge-couple/CHANGELOG.md b/extensions/sd-forge-couple/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..cb7396a26c3d42be9b081f9d31e0b9adce03f99b --- /dev/null +++ b/extensions/sd-forge-couple/CHANGELOG.md @@ -0,0 +1,88 @@ +### v2.0.3 - 2024 Aug.19 +- Minor **Optimization** + +### v2.0.2 - 2024 Aug.19 +- Fix Incorrect `Advanced` Mapping **Resolution** + +### v2.0.1 - 2024 Aug.15 +- Improve `` editing **QoL** + +### v2.0.0 - 2024 Aug.13 +- Support **Gradio v4** *(both `Basic` and `Advanced`)* +- Use HTML `
` instead of Gradio `Dataframe` +- Improved Visuals +- Optimize Logics + +### v1.6.2 - 2024 Jul.29 +- Preparing for Revamping + +### v1.6.1 - 2024 Jul.28 +- Support **Gradio v4** *(`Basic` only)* + +### v1.6.0 - 2024 May.13 +- Implement **Load Background Image** +- Improved Buttons Visuals + +### v1.5.1 - 2024 Apr.26 +- Improved **Draggable Regions** +- Bug Fix + +### v1.5.0 - 2024 Apr.22 +- Improved **API** Support +- Bug Fix + +### v1.4.4 - 2024 Apr.22 +- Improved **Auto Resolution** + +### v1.4.3 - 2024 Apr.18 +- Support **Infotext Pasting** for Advanced Mapping + +### v1.4.2 - 2024 Apr.18 +- Implement **Prompt Hint** + +### v1.4.1 - 2024 Apr.18 +- Implement **8-Direction** Resizing + +### v1.4.0 - 2024 Apr.16 +- Implement **Draggable Regions** + +### v1.3.7 - 2024 Apr.14 +- **Global Effect Weight** Slider by. ramyma +- **Mask API** by. ramyma + +### v1.3.6 - 2024 Apr.12 +- **Cache** DOM Elements + +### v1.3.5 - 2024 Apr.12 +- Support **Arbitrary Resolution** by. arcusmaximus + +### v1.3.4 - 2024 Apr.10 +- Auto Preview **Resolution** + +### v1.3.3 - 2024 Apr.10 +- Add Row **Colors** + +### v1.3.2 - 2024 Apr.08 +- Implement **Click & Drag** for Advanced Mapping +- Auto **Preview** +- Improved **Error Log** + +### v1.3.1 - 2024 Apr.08 +- Add **Row-Control Buttons** for Advanced Mapping + +### v1.3.0 - 2024 Mar.30 +- Add **Advanced Mapping** *(manual regions)* + +### v1.2.1 - 2024 Mar.29 +- Add **Couple Separator** Option by. pamparamm + +### v1.2.0 - 2024 Mar.28 +- Improved **Prompt** Processing +- Support **Wildcards** + +### v1.1.0 - 2024 Mar.28 +- Improved **Prompt** Processing +- Support **Batch** Count & Size + +### v1.0.0 - 2024 Mar.28 +- Extension **Released**! diff --git a/extensions/sd-forge-couple/LICENSE b/extensions/sd-forge-couple/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..dcc01e0b08098890e2a031ab4438d962d2317141 --- /dev/null +++ b/extensions/sd-forge-couple/LICENSE @@ -0,0 +1,660 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Copyright (C) 2023 laksjdjf + Copyright (C) 2024 Haoming02 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/extensions/sd-forge-couple/README.md b/extensions/sd-forge-couple/README.md new file mode 100644 index 0000000000000000000000000000000000000000..39ded0b892ef61f71db06d42877f825d608c4201 --- /dev/null +++ b/extensions/sd-forge-couple/README.md @@ -0,0 +1,156 @@ +# SD Forge Attention Couple +This is an Extension for the [Forge Webui](https://github.com/lllyasviel/stable-diffusion-webui-forge), which allows you to ~~generate couples~~ target conditioning at different regions. No more color bleeds or mixed features! + +> Compatible with both old & new Forge + +> Supports **SD 1.5** and **SDXL** checkpoints, but not **Flux**... + +> Does **not** work with [Automatic1111 Webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) + +## How to Use + +> As shown in the examples below, even if a region only contains 1 subject, it usually works better to prompt for the total amount of subjects first. + +The default **Basic** mode works by dividing the image into multiple "tiles," each corresponding to one line in the prompt. So if you want more regions, just prompt more lines. + +

+ +

+ +``` +2girls, blonde twintails, cyan eyes, white serafuku, standing, waving, looking at viewer, smile +2girls, black long hair, red eyes, dark school uniform, standing, crossed arms, looking away +``` + +### Tile Direction + +In the **Basic** mode, you can also choose between dividing the image into columns or rows. + +- **Horizontal:** First / Last line corresponds to the Left / Right region +- **Vertical:** First / Last line corresponds to the Top / Bottom region + +

+
+Direction:Vertical +

+ +``` +galaxy, stars, milky way +blue sky, clouds +sunrise, lens flare +ocean, waves +beach, sand +pavement, road +``` + +### Global Effect + +In the **Basic** mode, you can set either the **first line** or the **last line** of the **positive** prompt as the "background," affecting the entire image instead of just one region. Useful for specifying styles or quality tags used by **SD 1.5** and **Pony** checkpoints. + +

+(examples using the same seed)
+ +
+extension: off | extension: on +

+ +``` +a cinematic photo of 2 men arguing, indoors, court room +2 men, jesus christ, white robe, looking at each other, shouting +2 men, santa claus, looking at each other, shouting +``` + +### Couple Separator + +By default when the field is left empty, this Extension uses newline (`\n`) as the separator to determine "lines" of the prompts. You can also specify any word as the separator instead. + +### LoRA Support + +Using multiple LoRAs is possible, though it depends on how well each LoRA works together... + +LoRAs containing multiple subjects works easier and better in my experience. + +

+ +

+ +``` +[high quality, best quality], 2girls, on stage, backlighting, [bloom, hdr], +2girls, miyama suzune, pink idol costume, feather hair ornament, holding hands, looking at viewer, smile, blush +2girls, hanaoi rena, blue idol costume, feather hair ornament, holding hands, looking at viewer, shy, blush +``` + +
+ +## Advanced Mapping + +Were these automated and equally-sized tiles not sufficient for your needs? Now you can manually specify each regions! + +> **Important:** The entire image **must** contain weight. The easiest way would be adding a region that covers the whole image *(like the **Global Effect** in **Basic**)* + +- **Entries:** + - Each row contains a range for **x** axis, a range for **y** axis, a **weight**, as well as the corresponding **line** of prompt + - The range should be within `0.0` ~ `1.0`, representing the **percentage** of the full width/height + - **eg.** `0.0` to `1.0` would span across the entire axis + - **x** axis is from left to right; **y** axis is from top to bottom + - **2** *(to)* should be larger than **1** *(from)* + +- **Control:** + - Click on a row to select it, highlighting its bounding box + - Click on the same row again to deselect it + - When a row is selected, click the `🆕` button above / below to insert a new row above / below + - If holding `Shift`, it will insert a newline to the prompts as well + - When a row is selected, click the `❌` button to delete it + - If holding `Shift`, it will **delete** the corresponding line of prompt + - Click the `Default Mapping` button to reset the mappings + +- **Draggable Region:** + - When a bounding box is highlighted, simply drag the box around to reposition the region; drag the edges / corners to resize the region + +- **Background:** + - Click the `📂` button to load a image as the background of the mapping + - Click the `⏏` button to load the **img2img** input image as the background + - Click the `🗑` button to clear the background + +

+ + +

+ +``` +a cinematic photo of a couple, from side, outdoors +couple photo, man, black tuxedo +couple photo, woman, white dress +wedding photo, holding flower bouquet together +sunset, golden hour, lens flare +``` + +
+ +## API +For usage with API, please refer to the [Wiki](https://github.com/Haoming02/sd-forge-couple/wiki/API) + +
+ +## TypeError: 'NoneType' + +For people that get the following error: +```py +RuntimeError: shape '[X, Y, 1]' is invalid for input of size Z +shape '[X, Y, 1]' is invalid for input of size Z +*** Error completing request + ... + Traceback (most recent call last): + ... + res = list(func(*args, **kwargs)) + TypeError: 'NoneType' object is not iterable +``` + +1. Go to **Settings** -> **Optimizations**, and enable `Pad prompt/negative prompt` +2. Set the `Width` and `Height` to multiple of **64** + +
+ +## Special Thanks +- Credits to the original author, **[laksjdjf](https://github.com/laksjdjf)**, whose [ComfyUI Node](https://github.com/laksjdjf/cgem156-ComfyUI/tree/main/scripts/attention_couple) I used to port into Forge +- Also check out arcusmaximus's alternative approach to [draggable-box-ui](https://github.com/arcusmaximus/sd-forge-couple/tree/draggable-box-ui) diff --git a/extensions/sd-forge-couple/example/00.jpg b/extensions/sd-forge-couple/example/00.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c272db1a85975e2cf8d6f48f8c12a8c7cfe8b375 Binary files /dev/null and b/extensions/sd-forge-couple/example/00.jpg differ diff --git a/extensions/sd-forge-couple/example/03.jpg b/extensions/sd-forge-couple/example/03.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f7ecb2d1ae3831ae527a5f7c049bfdfcffd84ad7 Binary files /dev/null and b/extensions/sd-forge-couple/example/03.jpg differ diff --git a/extensions/sd-forge-couple/example/06.jpg b/extensions/sd-forge-couple/example/06.jpg new file mode 100644 index 0000000000000000000000000000000000000000..768b33ad6c0ae809b5f32ef0a96957b5de378f3a Binary files /dev/null and b/extensions/sd-forge-couple/example/06.jpg differ diff --git a/extensions/sd-forge-couple/example/1-1.jpg b/extensions/sd-forge-couple/example/1-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5f20450f0d35c89bc1fe7d3e312244872103fbce Binary files /dev/null and b/extensions/sd-forge-couple/example/1-1.jpg differ diff --git a/extensions/sd-forge-couple/example/1-2.jpg b/extensions/sd-forge-couple/example/1-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..515dd2a549d1aee736a2d4157ab2f27e467f8995 Binary files /dev/null and b/extensions/sd-forge-couple/example/1-2.jpg differ diff --git a/extensions/sd-forge-couple/example/10.jpg b/extensions/sd-forge-couple/example/10.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b71bf3eb54a37cdc87f542375e527d53249b71dd Binary files /dev/null and b/extensions/sd-forge-couple/example/10.jpg differ diff --git a/extensions/sd-forge-couple/example/10s.jpg b/extensions/sd-forge-couple/example/10s.jpg new file mode 100644 index 0000000000000000000000000000000000000000..42024ce4ba2e45d6a8974c24a4520d513f98adf1 Binary files /dev/null and b/extensions/sd-forge-couple/example/10s.jpg differ diff --git a/extensions/sd-forge-couple/javascript/background_loader.js b/extensions/sd-forge-couple/javascript/background_loader.js new file mode 100644 index 0000000000000000000000000000000000000000..6ae6ab66044c76747f46065ca25816355cf9998e --- /dev/null +++ b/extensions/sd-forge-couple/javascript/background_loader.js @@ -0,0 +1,76 @@ +class ForgeCoupleImageLoader { + + static #maxDim = 1024; + + /** @param {string} filepath @param {method} callback */ + static #path2url(filepath, callback) { + const img = new Image(); + + img.onload = () => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + var width = img.width; + var height = img.height; + + while (width > this.#maxDim || height > this.#maxDim) { + width = parseInt(width / 2); + height = parseInt(height / 2); + } + + canvas.width = width; + canvas.height = height; + + ctx.drawImage(img, 0, 0, width, height); + const resizedDataUrl = canvas.toDataURL('image/jpeg'); + + callback(resizedDataUrl); + }; + + img.src = filepath; + } + + /** @param {Element} image @param {Element[]} btns */ + static setup(image, btns) { + + const [load, load_i2i, clear] = (btns.length === 3) ? btns : [btns[0], null, btns[1]]; + + const img_upload = document.createElement("input"); + img_upload.setAttribute("type", "file"); + img_upload.setAttribute("accept", "image/*"); + + load.onclick = () => { img_upload.click(); }; + + img_upload.onchange = (event) => { + const file = event.target.files[0]; + + if (file != null) { + const reader = new FileReader(); + reader.onload = (e) => { + this.#path2url(e.target.result, (new_src) => { + image.style.backgroundImage = `url("${new_src}")`; + }); + }; + reader.readAsDataURL(file); + } + }; + + if (load_i2i != null) { + load_i2i.onclick = () => { + const src = document.getElementById("img2img_image").querySelector("img")?.src; + if (src != null) { + this.#path2url(src, (new_src) => { + image.style.backgroundImage = `url("${new_src}")`; + }); + } + }; + } + + clear.onclick = () => { image.style.backgroundImage = "none"; }; + + image.parentElement.appendChild(img_upload); + img_upload.style.display = "none"; + + } + +} diff --git a/extensions/sd-forge-couple/javascript/bbox.js b/extensions/sd-forge-couple/javascript/bbox.js new file mode 100644 index 0000000000000000000000000000000000000000..d8ccb7934ffb5c8ae99b007c62fe6316809834cf --- /dev/null +++ b/extensions/sd-forge-couple/javascript/bbox.js @@ -0,0 +1,298 @@ +class ForgeCoupleBox { + + /** "t2i" | "i2i" */ + #mode = undefined; + + /** The background image */ + #img = undefined; + + /** The bounding of the image */ + get #imgBound() { return this.#img.getBoundingClientRect(); } + + /** The bounding box currently selected */ + #box = undefined; + + /** The bounding of the container */ + get #boxBound() { return this.#box.getBoundingClientRect(); } + + /** Booleans representing whether each edge is used for resizing */ + #resize = {}; + /** Delta between the image and the container, when the image is not a square */ + #padding = {}; + /** The pixel distance to the window edge */ + #margin = {}; + /** The step size (1%) for moving and resizing */ + #step = {}; + + /** Currently selected row */ + #cachedRow = null; + + /** @param {Element} image @param {string} mode */ + constructor(image, mode) { + const box = document.createElement("div"); + box.classList.add(`fc_bbox`); + box.style.display = "none"; + + this.#mode = mode; + this.#img = image; + this.#box = box; + + const tab = document.getElementById((mode === "t2i") ? "tab_txt2img" : "tab_img2img"); + this.#registerClick(tab); + this.#registerHover(tab); + this.#registerUp(tab); + + image.parentElement.appendChild(box); + } + + #registerClick(tab) { + this.#img.addEventListener("mousedown", (e) => { + if (e.button !== 0) + return; + + this.isValid = (this.#img.style.cursor != "default"); + this.isResize = (this.#resize.L || this.#resize.R || this.#resize.T || this.#resize.B); + + if (this.isValid) { + this.#initCoord(); + + this.init = { + X: e.clientX, + Y: e.clientY, + left: this.#style2value(this.#box.style.left), + top: this.#style2value(this.#box.style.top) + }; + + tab.style.cursor = this.#img.style.cursor; + } + }); + } + + #registerHover(tab) { + tab.addEventListener("mousemove", (e) => { + + if (!this.isValid) { + this.#checkMouse(e.clientX, e.clientY); + return; + } + + if (this.isResize) + this.#resizeLogic(e.clientX, e.clientY) + else + this.#offsetLogic(e.clientX, e.clientY) + + }); + } + + #registerUp(tab) { + ["mouseup", "mouseleave"].forEach((ev) => { + tab.addEventListener(ev, (e) => { + if (!this.isValid || (ev === "mouseup" && e.button !== 0)) + return; + + const vals = this.#styleToMapping(); + const cells = this.#cachedRow.querySelectorAll("td"); + + for (let i = 0; i < vals.length; i++) + cells[i].textContent = Number(Math.max(0.0, vals[i])).toFixed(2); + + this.isValid = false; + tab.style.cursor = "unset"; + + ForgeCouple.onEntry(this.#mode); + }); + }); + } + + + /** @param {number} mouseX @param {number} mouseY */ + #resizeLogic(mouseX, mouseY) { + const FC_minimumSize = 32; + + if (this.#resize.R) { + const W = this.#clampMinMax(mouseX - this.#boxBound.left, FC_minimumSize, + this.#imgBound.right + this.#padding.left - this.#margin.left - this.init.left + ); + + this.#box.style.width = `${this.#step.w * Math.round(W / this.#step.w)}px`; + } else if (this.#resize.L) { + const rightEdge = this.#style2value(this.#box.style.left) + this.#style2value(this.#box.style.width); + const W = this.#clampMinMax(this.#boxBound.right - mouseX, FC_minimumSize, rightEdge - this.#padding.left) + + this.#box.style.left = `${rightEdge - this.#step.w * Math.round(W / this.#step.w)}px`; + this.#box.style.width = `${this.#step.w * Math.round(W / this.#step.w)}px`; + } + + if (this.#resize.B) { + const H = this.#clampMinMax(mouseY - this.#boxBound.top, FC_minimumSize, + this.#imgBound.bottom + this.#padding.top - this.#margin.top - this.init.top + ); + + this.#box.style.height = `${this.#step.h * Math.round(H / this.#step.h)}px`; + } else if (this.#resize.T) { + const bottomEdge = this.#style2value(this.#box.style.top) + this.#style2value(this.#box.style.height); + const H = this.#clampMinMax(this.#boxBound.bottom - mouseY, FC_minimumSize, bottomEdge - this.#padding.top); + + this.#box.style.top = `${bottomEdge - this.#step.h * Math.round(H / this.#step.h)}px`; + this.#box.style.height = `${this.#step.h * Math.round(H / this.#step.h)}px`; + } + } + + /** @param {number} mouseX @param {number} mouseY */ + #offsetLogic(mouseX, mouseY) { + const deltaX = mouseX - this.init.X; + const deltaY = mouseY - this.init.Y; + + const newLeft = this.#clampMinMax(this.init.left + deltaX, + this.#padding.left, this.#imgBound.width - this.#boxBound.width + this.#padding.left); + + const newTop = this.#clampMinMax(this.init.top + deltaY, + this.#padding.top, this.#imgBound.height - this.#boxBound.height + this.#padding.top); + + this.#box.style.left = `${this.#step.w * Math.round(newLeft / this.#step.w)}px`; + this.#box.style.top = `${this.#step.h * Math.round(newTop / this.#step.h)}px`; + } + + /** + * When a row is selected, display its corresponding bounding box, as well as initialize the coordinates + * @param {string} color + * @param {Element} row + */ + showBox(color, row) { + this.#cachedRow = row; + + setTimeout(() => { + this.#initCoord(); + this.#box.style.background = color; + this.#box.style.display = "block"; + }, 25); + } + + hideBox() { + this.#cachedRow = null; + this.#box.style.display = "none"; + } + + #initCoord() { + if (this.#cachedRow == null) + return; + + const [from_x, delta_x, from_y, delta_y] = this.#mappingToStyle(this.#cachedRow); + const { width, height } = this.#imgBound; + + if (width === height) { + this.#padding.left = 0.0; + this.#padding.top = 0.0; + } else if (width > height) { + const ratio = height / width; + this.#padding.left = 0.0; + this.#padding.top = 256.0 * (1.0 - ratio); + } else { + const ratio = width / height; + this.#padding.left = 256.0 * (1.0 - ratio); + this.#padding.top = 0.0; + } + + this.#step.w = width / 100.0; + this.#step.h = height / 100.0; + + this.#margin.left = this.#imgBound.left; + this.#margin.top = this.#imgBound.top; + + this.#box.style.width = `${width * delta_x}px`; + this.#box.style.height = `${height * delta_y}px`; + + this.#box.style.left = `${this.#padding.left + width * from_x}px`; + this.#box.style.top = `${this.#padding.top + height * from_y}px`; + } + + + /** @param {number} mouseX @param {number} mouseY */ + #checkMouse(mouseX, mouseY) { + const FC_resizeBorder = 8; + + if (this.#box.style.display == "none") { + this.#img.style.cursor = "default"; + return; + } + + const { left, right, top, bottom } = this.#boxBound; + + if (mouseX < left - FC_resizeBorder || mouseX > right + FC_resizeBorder || mouseY < top - FC_resizeBorder || mouseY > bottom + FC_resizeBorder) { + this.#img.style.cursor = "default"; + return; + } + + this.#resize.L = mouseX < left + FC_resizeBorder; + this.#resize.R = mouseX > right - FC_resizeBorder; + this.#resize.T = mouseY < top + FC_resizeBorder; + this.#resize.B = mouseY > bottom - FC_resizeBorder; + + if (!(this.#resize.L || this.#resize.T || this.#resize.R || this.#resize.B)) { + this.#img.style.cursor = "move"; + return; + } + + if (this.#resize.R && this.#resize.B) + this.#img.style.cursor = "nwse-resize"; + else if (this.#resize.R && this.#resize.T) + this.#img.style.cursor = "nesw-resize"; + else if (this.#resize.L && this.#resize.B) + this.#img.style.cursor = "nesw-resize"; + else if (this.#resize.L && this.#resize.T) + this.#img.style.cursor = "nwse-resize"; + else if (this.#resize.R || this.#resize.L) + this.#img.style.cursor = "ew-resize"; + else if (this.#resize.B || this.#resize.T) + this.#img.style.cursor = "ns-resize"; + } + + + /** + * Convert the table row into coordinate ranges + * @param {Element} row + * @returns {number[]} + */ + #mappingToStyle(row) { + const cells = row.querySelectorAll("td"); + + const from_x = parseFloat(cells[0].textContent); + const to_x = parseFloat(cells[1].textContent); + const from_y = parseFloat(cells[2].textContent); + const to_y = parseFloat(cells[3].textContent); + + return [from_x, to_x - from_x, from_y, to_y - from_y] + } + + /** + * Convert the coordinates of bounding box back into string + * @returns {number[]} + */ + #styleToMapping() { + const { width, height } = this.#imgBound; + const { left, right, top, bottom } = this.#boxBound; + const { left: leftMargin, top: topMargin } = this.#margin; + + const from_x = (left - leftMargin) / width; + const to_x = (right - leftMargin) / width; + const from_y = (top - topMargin) / height; + const to_y = (bottom - topMargin) / height; + + return [from_x, to_x, from_y, to_y]; + } + + /** @param {number} v @param {number} min @param {number} max @returns {number} */ + #clampMinMax(v, min, max) { + return Math.min(Math.max(v, min), max); + } + + /** @param {string} style @returns {number} */ + #style2value(style) { + try { + const re = /calc\((-?\d+(?:\.\d+)?)px\)/; + return parseFloat(style.match(re)[1]); + } catch { + return parseFloat(style); + } + } +} diff --git a/extensions/sd-forge-couple/javascript/couple.js b/extensions/sd-forge-couple/javascript/couple.js new file mode 100644 index 0000000000000000000000000000000000000000..5a64d10c3b1d4b6d4959a3e6313b307ebcb8ec99 --- /dev/null +++ b/extensions/sd-forge-couple/javascript/couple.js @@ -0,0 +1,208 @@ +class ForgeCouple { + + /** The fc_mapping \ */ + static container = { "t2i": undefined, "i2i": undefined }; + /** The actual \ */ + static mappingTable = { "t2i": undefined, "i2i": undefined }; + + /** The floating \s for row controls */ + static rowButtons = { "t2i": undefined, "i2i": undefined }; + + /** The \ for preview resolution */ + static previewResolution = { "t2i": undefined, "i2i": undefined }; + /** The \ to trigger preview */ + static previewButton = { "t2i": undefined, "i2i": undefined }; + + /** The ForgeCoupleDataframe class */ + static dataframe = { "t2i": undefined, "i2i": undefined }; + /** The ForgeCoupleBox class */ + static bbox = { "t2i": undefined, "i2i": undefined }; + + /** The \ for SendTo buttons */ + static pasteField = { "t2i": undefined, "i2i": undefined }; + /** The \ for internal updates */ + static entryField = { "t2i": undefined, "i2i": undefined }; + + /** + * After updating the mappings, trigger a preview + * @param {string} mode "t2i" | "i2i" + */ + static async preview(mode) { + + setTimeout(async () => { + var res = null; + + if (mode === "t2i") { + const w = parseInt(document.getElementById("txt2img_width").querySelector("input").value); + const h = parseInt(document.getElementById("txt2img_height").querySelector("input").value); + res = `${Math.max(64, w)}x${Math.max(64, h)}`; + } else { + const i2i_size = document.getElementById("img2img_column_size").querySelector(".tab-nav"); + + if (i2i_size.children[0].classList.contains("selected")) { + // Resize to + const w = parseInt(document.getElementById("img2img_width").querySelector("input").value); + const h = parseInt(document.getElementById("img2img_height").querySelector("input").value); + res = `${Math.max(64, w)}x${Math.max(64, h)}`; + } else { + // Resize by + res = document.getElementById("img2img_scale_resolution_preview")?.querySelector(".resolution")?.textContent; + } + } + + res ??= "1024x1024"; + this.previewResolution[mode].value = res; + updateInput(this.previewResolution[mode]); + + this.previewButton[mode].click(); + }, (mode === "t2i") ? 25 : 50); + } + + /** + * Update the color of the rows based on the order and selection + * @param {string} mode "t2i" | "i2i" + */ + static updateColors(mode) { + const [color, row] = this.dataframe[mode].updateColors(); + + if (color) { + this.bbox[mode].showBox(color, row); + return row; + } + else { + this.bbox[mode].hideBox(); + this.rowButtons[mode].style.display = "none"; + return null; + } + } + + /** + * When using SendTo buttons, refresh the table + * @param {string} mode "t2i" | "i2i" + */ + static onPaste(mode) { + const vals = JSON.parse(this.pasteField[mode].value); + this.dataframe[mode].onPaste(vals); + this.preview(mode); + } + + /** + * When clicking on a row, update the index + * @param {string} mode "t2i" | "i2i" + */ + static onSelect(mode) { + const cell = this.updateColors(mode); + + if (cell) { + const bounding = cell.querySelector("td").getBoundingClientRect(); + const bounding_container = this.container[mode].getBoundingClientRect(); + this.rowButtons[mode].style.top = `calc(${bounding.top - bounding_container.top}px - 1.5em)`; + this.rowButtons[mode].style.display = "block"; + } else + this.rowButtons[mode].style.display = "none"; + } + + /** + * When editing the mapping, update the internal JSON + * @param {string} mode "t2i" | "i2i" + */ + static onEntry(mode) { + const rows = this.mappingTable[mode].querySelectorAll("tr"); + + const vals = Array.from(rows, row => { + return Array.from(row.querySelectorAll("td")) + .slice(0, -1).map(cell => parseFloat(cell.textContent)); + }); + + const json = JSON.stringify(vals); + this.entryField[mode].value = json; + updateInput(this.entryField[mode]); + } + + /** + * Link the buttons related to the mapping + * @param {Element} ex + * @param {string} mode "t2i" | "i2i" + */ + static #registerButtons(ex, mode) { + ex.querySelector(".fc_reset_btn").onclick = () => { this.dataframe[mode].reset(); }; + ex.querySelector("#fc_up_btn").onclick = (e) => { this.dataframe[mode].newRowAbove(e.shiftKey); }; + ex.querySelector("#fc_dn_btn").onclick = (e) => { this.dataframe[mode].newRowBelow(e.shiftKey); }; + ex.querySelector("#fc_del_btn").onclick = (e) => { this.dataframe[mode].deleteRow(e.shiftKey); }; + } + + /** Hook some elements to automatically refresh the resolution */ + static #registerResolutionHandles() { + + [["txt2img", "t2i"], ["img2img", "i2i"]].forEach(([tab, mode]) => { + const btns = document.getElementById(`${tab}_dimensions_row`)?.querySelectorAll("button"); + if (btns != null) + btns.forEach((btn) => { btn.onclick = () => { this.preview(mode); } }); + }); + + const i2i_size_btns = document.getElementById("img2img_column_size").querySelector(".tab-nav"); + i2i_size_btns.addEventListener("click", () => { this.preview("i2i"); }); + + const tabs = document.getElementById('tabs').querySelector('.tab-nav'); + tabs.addEventListener("click", () => { + if (tabs.children[0].classList.contains("selected")) + this.preview("t2i"); + if (tabs.children[1].classList.contains("selected")) + this.preview("i2i"); + }); + + } + + static setup() { + ["t2i", "i2i"].forEach((mode) => { + const ex = document.getElementById(`forge_couple_${mode}`); + const mapping_btns = ex.querySelector(".fc_mapping_btns"); + + this.container[mode] = ex.querySelector(".fc_mapping"); + this.container[mode].appendChild(mapping_btns); + + this.dataframe[mode] = new ForgeCoupleDataframe( + this.container[mode], mode, ex.querySelector(".fc_separator").querySelector("input") + ); + this.mappingTable[mode] = this.container[mode].querySelector("tbody"); + + this.rowButtons[mode] = ex.querySelector(".fc_row_btns"); + this.rowButtons[mode].style.display = "none"; + + this.rowButtons[mode].querySelectorAll("button").forEach((btn) => { + btn.setAttribute('style', 'margin: auto !important'); + }); + + this.container[mode].appendChild(this.rowButtons[mode]); + + this.previewResolution[mode] = ex.querySelector(".fc_preview_res").querySelector("input"); + this.previewButton[mode] = ex.querySelector(".fc_preview"); + + const preview_img = ex.querySelector("img"); + preview_img.ondragstart = (e) => { e.preventDefault(); return false; }; + preview_img.parentElement.style.overflow = "visible"; + + this.bbox[mode] = new ForgeCoupleBox(preview_img, mode); + + const bg_btns = ex.querySelector(".fc_bg_btns"); + preview_img.parentElement.appendChild(bg_btns); + + ForgeCoupleImageLoader.setup(preview_img, bg_btns.querySelectorAll("button")) + + this.pasteField[mode] = ex.querySelector(".fc_paste_field").querySelector("textarea"); + this.entryField[mode] = ex.querySelector(".fc_entry_field").querySelector("textarea"); + + this.#registerButtons(ex, mode); + ForgeCoupleObserver.observe( + mode, + document.getElementById(`${mode === "t2i" ? "txt" : "img"}2img_prompt`).querySelector("textarea"), + () => { this.dataframe[mode].syncPrompt(); } + ); + }); + + this.#registerResolutionHandles(); + } + +} + +onUiLoaded(async () => { ForgeCouple.setup(); }) diff --git a/extensions/sd-forge-couple/javascript/dataframe.js b/extensions/sd-forge-couple/javascript/dataframe.js new file mode 100644 index 0000000000000000000000000000000000000000..bb04622e1110e3b435a2f6db8d8b1d750be92d94 --- /dev/null +++ b/extensions/sd-forge-couple/javascript/dataframe.js @@ -0,0 +1,381 @@ +class ForgeCoupleDataframe { + + static #default_mapping = [ + [0.0, 0.5, 0.0, 1.0, 1.0], + [0.5, 1.0, 0.0, 1.0, 1.0] + ]; + + static get #columns() { return this.#tableHeader.length; } + + static #tableHeader = ["x1", "x2", "y1", "y2", "w", "prompt"]; + static #tableWidth = ["6%", "6%", "6%", "6%", "6%", "70%"]; + + static #colors = [0, 30, 60, 120, 240, 280, 320]; + static #color(i) { return `hsl(${ForgeCoupleDataframe.#colors[i % 7]}, 36%, 36%)` } + + /** "t2i" | "i2i" */ + #mode = undefined; + #promptField = undefined; + #separatorField = undefined; + + get #sep() { + var sep = this.#separatorField.value.trim(); + if (!sep) sep = "\n"; + return sep; + } + + #body = undefined; + #selection = -1; + + /** @param {Element} div @param {string} mode @param {Element} separator */ + constructor(div, mode, separator) { + this.#mode = mode; + this.#promptField = document.getElementById(`${mode === "t2i" ? "txt" : "img"}2img_prompt`).querySelector("textarea"); + this.#separatorField = separator; + + const table = document.createElement('table'); + + + const colgroup = document.createElement('colgroup'); + for (let c = 0; c < ForgeCoupleDataframe.#columns; c++) { + const col = document.createElement('col'); + col.style.width = ForgeCoupleDataframe.#tableWidth[c]; + colgroup.appendChild(col); + } + table.appendChild(colgroup); + + + const thead = document.createElement('thead'); + const thr = thead.insertRow(); + for (let c = 0; c < ForgeCoupleDataframe.#columns; c++) { + const th = document.createElement('th'); + th.textContent = ForgeCoupleDataframe.#tableHeader[c]; + thr.appendChild(th); + } + table.appendChild(thead); + + + const tbody = document.createElement('tbody'); + for (let r = 0; r < ForgeCoupleDataframe.#default_mapping.length; r++) { + const tr = tbody.insertRow(); + + for (let c = 0; c < ForgeCoupleDataframe.#columns; c++) { + const td = tr.insertCell(); + const isPrompt = (c === ForgeCoupleDataframe.#columns - 1); + + td.contentEditable = true; + td.textContent = isPrompt ? "" : Number(ForgeCoupleDataframe.#default_mapping[r][c]).toFixed(2); + + td.addEventListener("keydown", (e) => { + if (e.key == 'Enter') { + e.preventDefault(); + td.blur(); + } + }); + + td.addEventListener("blur", () => { this.#onSubmit(td, isPrompt); }) + td.onclick = () => { this.#onSelect(r); } + } + } + table.appendChild(tbody); + + + div.appendChild(table); + this.#body = tbody; + } + + /** @param {number} row */ + #onSelect(row) { + this.#selection = (row === this.#selection) ? -1 : row; + ForgeCouple.onSelect(this.#mode); + } + + /** @param {Element} cell @param {boolean} isPrompt */ + #onSubmit(cell, isPrompt) { + if (isPrompt) { + const prompts = []; + const rows = this.#body.querySelectorAll("tr"); + rows.forEach((row) => { + const prompt = row.querySelector("td:last-child").textContent.trim(); + prompts.push(prompt); + }); + + const oldPrompts = this.#promptField.value.split(this.#sep).map(line => line.trim()); + const modified = prompts.length; + + if (modified >= oldPrompts.length) + this.#promptField.value = prompts.join(this.#sep); + else { + const newPrompts = [...prompts, ...(oldPrompts.slice(modified))] + this.#promptField.value = newPrompts.join(this.#sep); + } + + updateInput(this.#promptField); + } else { + var val = this.#clamp01(cell.textContent, + Array.from(cell.parentElement.children).indexOf(cell) === 4 + ); + val = Math.round(val / 0.01) * 0.01; + cell.textContent = Number(val).toFixed(2); + ForgeCouple.onSelect(this.#mode); + ForgeCouple.onEntry(this.#mode); + } + } + + /** @param {number[][]} vals */ + onPaste(vals) { + while (this.#body.querySelector("tr")) + this.#body.deleteRow(0); + + const count = vals.length; + + for (let r = 0; r < count; r++) { + const tr = this.#body.insertRow(); + + for (let c = 0; c < ForgeCoupleDataframe.#columns; c++) { + const td = tr.insertCell(); + const prompt = (c === ForgeCoupleDataframe.#columns - 1); + + td.contentEditable = true; + td.textContent = prompt ? "" : Number(vals[r][c]).toFixed(2); + + td.addEventListener("keydown", (e) => { + if (e.key == 'Enter') { + e.preventDefault(); + td.blur(); + } + }); + + td.addEventListener("blur", () => { this.#onSubmit(td, prompt); }) + td.onclick = () => { this.#onSelect(r); } + } + } + + this.#selection = -1; + ForgeCouple.onSelect(this.#mode); + ForgeCouple.onEntry(this.#mode); + this.syncPrompt(); + } + + reset() { + while (this.#body.querySelector("tr")) + this.#body.deleteRow(0); + + for (let r = 0; r < ForgeCoupleDataframe.#default_mapping.length; r++) { + const tr = this.#body.insertRow(); + + for (let c = 0; c < ForgeCoupleDataframe.#columns; c++) { + const td = tr.insertCell(); + const prompt = (c === ForgeCoupleDataframe.#columns - 1); + + td.contentEditable = true; + td.textContent = prompt ? "" : Number(ForgeCoupleDataframe.#default_mapping[r][c]).toFixed(2); + + td.addEventListener("keydown", (e) => { + if (e.key == 'Enter') { + e.preventDefault(); + td.blur(); + } + }); + + td.addEventListener("blur", () => { this.#onSubmit(td, prompt); }) + td.onclick = () => { this.#onSelect(r); } + } + } + + this.#selection = -1; + ForgeCouple.onSelect(this.#mode); + ForgeCouple.onEntry(this.#mode); + this.syncPrompt(); + } + + /** @returns {number[][]} */ + #newRow() { + const rows = this.#body.querySelectorAll("tr"); + const count = rows.length; + + const vals = Array.from(rows, row => { + return Array.from(row.querySelectorAll("td")) + .slice(0, -1).map(cell => parseFloat(cell.textContent)); + }); + + const tr = this.#body.insertRow(); + + for (let c = 0; c < ForgeCoupleDataframe.#columns; c++) { + const td = tr.insertCell(); + const prompt = (c === ForgeCoupleDataframe.#columns - 1); + + td.contentEditable = true; + td.textContent = ""; + + td.addEventListener("keydown", (e) => { + if (e.key == 'Enter') { + e.preventDefault(); + td.blur(); + } + }); + + td.addEventListener("blur", () => { this.#onSubmit(td, prompt); }) + td.onclick = () => { this.#onSelect(count); } + } + + return vals; + } + + /** @param {boolean} newline */ + newRowAbove(newline) { + const vals = this.#newRow(); + + const newVals = [ + ...vals.slice(0, this.#selection), + [0.0, 1.0, 0.0, 1.0, 1.0], + ...vals.slice(this.#selection) + ]; + + const count = newVals.length; + const rows = this.#body.querySelectorAll("tr"); + + for (let r = 0; r < count; r++) { + const cells = rows[r].querySelectorAll("td"); + for (let c = 0; c < ForgeCoupleDataframe.#columns - 1; c++) + cells[c].textContent = Number(newVals[r][c]).toFixed(2); + } + + if (newline) { + const prompts = this.#promptField.value.split(this.#sep).map(line => line.trim()); + const newPrompts = [ + ...prompts.slice(0, this.#selection), + "", + ...prompts.slice(this.#selection) + ]; + this.#promptField.value = newPrompts.join(this.#sep); + updateInput(this.#promptField); + } + + this.#selection += 1; + ForgeCouple.onSelect(this.#mode); + ForgeCouple.onEntry(this.#mode); + this.syncPrompt(); + } + + /** @param {boolean} newline */ + newRowBelow(newline) { + const vals = this.#newRow(); + + const newVals = [ + ...vals.slice(0, this.#selection + 1), + [0.25, 0.75, 0.25, 0.75, 1.0], + ...vals.slice(this.#selection + 1) + ]; + + const count = newVals.length; + const rows = this.#body.querySelectorAll("tr"); + + for (let r = 0; r < count; r++) { + const cells = rows[r].querySelectorAll("td"); + for (let c = 0; c < ForgeCoupleDataframe.#columns - 1; c++) + cells[c].textContent = Number(newVals[r][c]).toFixed(2); + } + + if (newline) { + const prompts = this.#promptField.value.split(this.#sep).map(line => line.trim()); + const newPrompts = [ + ...prompts.slice(0, this.#selection + 1), + "", + ...prompts.slice(this.#selection + 1) + ]; + this.#promptField.value = newPrompts.join(this.#sep); + updateInput(this.#promptField); + } + + ForgeCouple.onSelect(this.#mode); + ForgeCouple.onEntry(this.#mode); + this.syncPrompt(); + } + + /** @param {boolean} removeText */ + deleteRow(removeText) { + const rows = this.#body.querySelectorAll("tr"); + const count = rows.length; + + const vals = Array.from(rows, row => { + return Array.from(row.querySelectorAll("td")) + .slice(0, -1).map(cell => parseFloat(cell.textContent)); + }); + + vals.splice(this.#selection, 1); + this.#body.deleteRow(count - 1); + + for (let r = 0; r < count - 1; r++) { + const cells = rows[r].querySelectorAll("td"); + for (let c = 0; c < ForgeCoupleDataframe.#columns - 1; c++) + cells[c].textContent = Number(vals[r][c]).toFixed(2); + } + + if (removeText) { + const prompts = this.#promptField.value.split(this.#sep).map(line => line.trim()); + prompts.splice(this.#selection, 1); + this.#promptField.value = prompts.join(this.#sep); + updateInput(this.#promptField); + } + + if (this.#selection == count - 1) + this.#selection -= 1; + + ForgeCouple.onSelect(this.#mode); + ForgeCouple.onEntry(this.#mode); + this.syncPrompt(); + } + + /** @returns {[string, Element]} */ + updateColors() { + const rows = this.#body.querySelectorAll("tr"); + + rows.forEach((row, i) => { + const color = ForgeCoupleDataframe.#color(i); + const stripe = (this.#selection === i) ? "var(--table-row-focus)" : + `var(--table-${(i % 2 == 0) ? "odd" : "even"}-background-fill)`; + + row.style.background = `linear-gradient(to right, ${stripe} 80%, ${color})`; + }); + + if (this.#selection < 0 || this.#selection > rows.length) + return [null, null]; + else + return [ForgeCoupleDataframe.#color(this.#selection), rows[this.#selection]]; + } + + syncPrompt() { + const prompt = this.#promptField.value; + + const prompts = prompt.split(this.#sep).map(line => line.trim()); + const rows = this.#body.querySelectorAll("tr"); + + const active = document.activeElement; + rows.forEach((row, i) => { + const promptCell = row.querySelector("td:last-child"); + + // Skip editing Cell + if (promptCell === active) + return; + + if (i < prompts.length) + promptCell.textContent = prompts[i]; + else + promptCell.textContent = ""; + }); + } + + /** @param {number} @param {boolean} w @returns {number} */ + #clamp01(v, w) { + var val = parseFloat(v); + if (Number.isNaN(val)) + val = 0.0; + + return Math.min( + Math.max(val, w ? -10.0 : 0.0), + w ? 10.0 : 1.0 + ); + } + +} diff --git a/extensions/sd-forge-couple/javascript/edit_timer.js b/extensions/sd-forge-couple/javascript/edit_timer.js new file mode 100644 index 0000000000000000000000000000000000000000..d7df36475808ee50b630aa1ee1efd932e9d5c645 --- /dev/null +++ b/extensions/sd-forge-couple/javascript/edit_timer.js @@ -0,0 +1,22 @@ +class ForgeCoupleObserver { + + static get #delay() { return 500; } // 500 ms + static #editTimers = {}; + + /** + * **Reference:** https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/v1.10.1/javascript/ui.js#L425 + * @param {string} id + * @param {Element} field + * @param {Function} callback + */ + static observe(id, field, callback) { + const onInput = () => { + const existingTimer = this.#editTimers[id]; + if (existingTimer) clearTimeout(existingTimer); + + this.#editTimers[id] = setTimeout(callback, this.#delay); + }; + + field.addEventListener("input", onInput); + } +} diff --git a/extensions/sd-forge-couple/lib_couple/__init__.py b/extensions/sd-forge-couple/lib_couple/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/extensions/sd-forge-couple/lib_couple/__pycache__/__init__.cpython-310.pyc b/extensions/sd-forge-couple/lib_couple/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..faf041d0becb93bd7232ecae096cabaca24fe022 Binary files /dev/null and b/extensions/sd-forge-couple/lib_couple/__pycache__/__init__.cpython-310.pyc differ diff --git a/extensions/sd-forge-couple/lib_couple/__pycache__/attention_couple.cpython-310.pyc b/extensions/sd-forge-couple/lib_couple/__pycache__/attention_couple.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db5eef6b87eac81d7becbc6d219455e65febc11c Binary files /dev/null and b/extensions/sd-forge-couple/lib_couple/__pycache__/attention_couple.cpython-310.pyc differ diff --git a/extensions/sd-forge-couple/lib_couple/__pycache__/attention_masks.cpython-310.pyc b/extensions/sd-forge-couple/lib_couple/__pycache__/attention_masks.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2333448ec91d74d58c08f1e2c5ccd0cf9345449 Binary files /dev/null and b/extensions/sd-forge-couple/lib_couple/__pycache__/attention_masks.cpython-310.pyc differ diff --git a/extensions/sd-forge-couple/lib_couple/__pycache__/gr_version.cpython-310.pyc b/extensions/sd-forge-couple/lib_couple/__pycache__/gr_version.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..433f591a85fd0defa23ae4f900b8e24277ee49b9 Binary files /dev/null and b/extensions/sd-forge-couple/lib_couple/__pycache__/gr_version.cpython-310.pyc differ diff --git a/extensions/sd-forge-couple/lib_couple/__pycache__/mapping.cpython-310.pyc b/extensions/sd-forge-couple/lib_couple/__pycache__/mapping.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d7726d0fe2cd173b4c72ae68bd73936965ff069 Binary files /dev/null and b/extensions/sd-forge-couple/lib_couple/__pycache__/mapping.cpython-310.pyc differ diff --git a/extensions/sd-forge-couple/lib_couple/__pycache__/ui.cpython-310.pyc b/extensions/sd-forge-couple/lib_couple/__pycache__/ui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37644cb34801df1e1de51443ade9c74cc0ec50ab Binary files /dev/null and b/extensions/sd-forge-couple/lib_couple/__pycache__/ui.cpython-310.pyc differ diff --git a/extensions/sd-forge-couple/lib_couple/__pycache__/ui_funcs.cpython-310.pyc b/extensions/sd-forge-couple/lib_couple/__pycache__/ui_funcs.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..18b99c903a9f95ff307296678a565226d1ce555c Binary files /dev/null and b/extensions/sd-forge-couple/lib_couple/__pycache__/ui_funcs.cpython-310.pyc differ diff --git a/extensions/sd-forge-couple/lib_couple/attention_couple.py b/extensions/sd-forge-couple/lib_couple/attention_couple.py new file mode 100644 index 0000000000000000000000000000000000000000..92abc2aa5ab55632cf0365cc34e6feb4939526f9 --- /dev/null +++ b/extensions/sd-forge-couple/lib_couple/attention_couple.py @@ -0,0 +1,103 @@ +""" +Credit: laksjdjf +https://github.com/laksjdjf/cgem156-ComfyUI/blob/main/scripts/attention_couple/node.py + +Modified by. Haoming02 to work with Forge +""" + +from .attention_masks import get_mask, lcm_for_list +from modules.devices import get_optimal_device +import torch + +try: + from backend import memory_management + is_classic = False + +except ImportError: + is_classic = True + + +class AttentionCouple: + + @torch.inference_mode() + def patch_unet(self, model, base_mask, kwargs: dict): + + new_model = model.clone() + + if is_classic: + dtype = new_model.model.diffusion_model.dtype + else: + dtype = new_model.model.computation_dtype + + device = get_optimal_device() + + num_conds = len(kwargs) // 2 + 1 + + mask = [base_mask] + [kwargs[f"mask_{i}"] for i in range(1, num_conds)] + mask = torch.stack(mask, dim=0).to(device, dtype=dtype) + assert mask.sum(dim=0).min() > 0, "[Couple] Masks contain non-filled areas..." + mask = mask / mask.sum(dim=0, keepdim=True) + + conds = [ + kwargs[f"cond_{i}"][0][0].to(device, dtype=dtype) + for i in range(1, num_conds) + ] + num_tokens = [cond.shape[1] for cond in conds] + + @torch.inference_mode() + def attn2_patch(q, k, v, extra_options): + assert k.mean() == v.mean(), "k and v must be the same." + cond_or_unconds = extra_options["cond_or_uncond"] + num_chunks = len(cond_or_unconds) + self.batch_size = q.shape[0] // num_chunks + q_chunks = q.chunk(num_chunks, dim=0) + k_chunks = k.chunk(num_chunks, dim=0) + lcm_tokens = lcm_for_list(num_tokens + [k.shape[1]]) + conds_tensor = torch.cat( + [ + cond.repeat(self.batch_size, lcm_tokens // num_tokens[i], 1) + for i, cond in enumerate(conds) + ], + dim=0, + ) + + qs, ks = [], [] + for i, cond_or_uncond in enumerate(cond_or_unconds): + k_target = k_chunks[i].repeat(1, lcm_tokens // k.shape[1], 1) + if cond_or_uncond == 1: # uncond + qs.append(q_chunks[i]) + ks.append(k_target) + else: + qs.append(q_chunks[i].repeat(num_conds, 1, 1)) + ks.append(torch.cat([k_target, conds_tensor], dim=0)) + + qs = torch.cat(qs, dim=0).to(q) + ks = torch.cat(ks, dim=0).to(k) + + return qs, ks, ks + + @torch.inference_mode() + def attn2_output_patch(out, extra_options): + cond_or_unconds = extra_options["cond_or_uncond"] + mask_downsample = get_mask( + mask, self.batch_size, out.shape[1], extra_options["original_shape"] + ) + outputs = [] + pos = 0 + for cond_or_uncond in cond_or_unconds: + if cond_or_uncond == 1: # uncond + outputs.append(out[pos : pos + self.batch_size]) + pos += self.batch_size + else: + masked_output = ( + out[pos : pos + num_conds * self.batch_size] * mask_downsample + ).view(num_conds, self.batch_size, out.shape[1], out.shape[2]) + masked_output = masked_output.sum(dim=0) + outputs.append(masked_output) + pos += num_conds * self.batch_size + return torch.cat(outputs, dim=0) + + new_model.set_model_attn2_patch(attn2_patch) + new_model.set_model_attn2_output_patch(attn2_output_patch) + + return new_model diff --git a/extensions/sd-forge-couple/lib_couple/attention_masks.py b/extensions/sd-forge-couple/lib_couple/attention_masks.py new file mode 100644 index 0000000000000000000000000000000000000000..0be98fef7eba20e678eede360d0f3aa649a5c84f --- /dev/null +++ b/extensions/sd-forge-couple/lib_couple/attention_masks.py @@ -0,0 +1,49 @@ +""" +Credit: laksjdjf +https://github.com/laksjdjf/cgem156-ComfyUI/blob/main/scripts/attention_couple/node.py +""" + +import torch.nn.functional as F +import math + + +def repeat_div(value: int, iterations: int) -> int: + for _ in range(iterations): + value = math.ceil(value / 2) + + return value + + +def get_mask(mask, batch_size, num_tokens, original_shape): + """ + Credit: hako-mikan + https://github.com/hako-mikan/sd-webui-regional-prompter/blob/main/scripts/attention.py + + Issue Found/Fixed by. arcusmaximus & www + https://github.com/arcusmaximus/sd-forge-couple/tree/draggable-box-ui + """ + + image_width: int = original_shape[3] + image_height: int = original_shape[2] + + scale = math.ceil(math.log2(math.sqrt(image_height * image_width / num_tokens))) + size = (repeat_div(image_height, scale), repeat_div(image_width, scale)) + + num_conds = mask.shape[0] + mask_downsample = F.interpolate(mask, size=size, mode="nearest") + mask_downsample = mask_downsample.view(num_conds, num_tokens, 1).repeat_interleave( + batch_size, dim=0 + ) + + return mask_downsample + + +def lcm(a, b): + return a * b // math.gcd(a, b) + + +def lcm_for_list(numbers): + current_lcm = numbers[0] + for number in numbers[1:]: + current_lcm = lcm(current_lcm, number) + return current_lcm diff --git a/extensions/sd-forge-couple/lib_couple/gr_version.py b/extensions/sd-forge-couple/lib_couple/gr_version.py new file mode 100644 index 0000000000000000000000000000000000000000..a13b5ef4dd9df98d99bc9f04de4f8a56f92e7ea0 --- /dev/null +++ b/extensions/sd-forge-couple/lib_couple/gr_version.py @@ -0,0 +1,10 @@ +import gradio as gr + +is_gradio_4: bool = int(str(gr.__version__).split(".")[0]) == 4 + + +def js(func: str) -> dict: + if is_gradio_4: + return {"js": func} + else: + return {"_js": func} diff --git a/extensions/sd-forge-couple/lib_couple/mapping.py b/extensions/sd-forge-couple/lib_couple/mapping.py new file mode 100644 index 0000000000000000000000000000000000000000..94ac9e4a8520d93e34b98a65ee2cb32e10343f1c --- /dev/null +++ b/extensions/sd-forge-couple/lib_couple/mapping.py @@ -0,0 +1,161 @@ +from modules.prompt_parser import SdConditioning + +from base64 import b64decode as decode +from io import BytesIO as bIO +from PIL import Image +import numpy as np +import torch + + +def empty_tensor(H: int, W: int): + return torch.zeros((H, W)).unsqueeze(0) + + +def basic_mapping( + sd_model, + couples: list, + WIDTH: int, + HEIGHT: int, + LINE_COUNT: int, + IS_HORIZONTAL: bool, + background: str, + TILE_SIZE: int, + TILE_WEIGHT: float, + BG_WEIGHT: float, +): + + ARGs: dict = {} + IS_SDXL: bool = hasattr( + sd_model.forge_objects.unet.model.diffusion_model, "label_emb" + ) + + for tile in range(LINE_COUNT): + mask = torch.zeros((HEIGHT, WIDTH)) + + # ===== Cond ===== + texts = SdConditioning([couples[tile]], False, WIDTH, HEIGHT, None) + cond = sd_model.get_learned_conditioning(texts) + pos_cond = [[cond["crossattn"]]] if IS_SDXL else [[cond]] + # ===== Cond ===== + + # ===== Mask ===== + if background == "First Line": + if tile == 0: + mask = torch.ones((HEIGHT, WIDTH)) * BG_WEIGHT + else: + if IS_HORIZONTAL: + mask[:, (tile - 1) * TILE_SIZE : tile * TILE_SIZE] = TILE_WEIGHT + else: + mask[(tile - 1) * TILE_SIZE : tile * TILE_SIZE, :] = TILE_WEIGHT + else: + if IS_HORIZONTAL: + mask[:, tile * TILE_SIZE : (tile + 1) * TILE_SIZE] = TILE_WEIGHT + else: + mask[tile * TILE_SIZE : (tile + 1) * TILE_SIZE, :] = TILE_WEIGHT + # ===== Mask ===== + + ARGs[f"cond_{tile + 1}"] = pos_cond + ARGs[f"mask_{tile + 1}"] = mask.unsqueeze(0) + + if background == "Last Line": + ARGs[f"mask_{LINE_COUNT}"] = ( + torch.ones((HEIGHT, WIDTH)) * BG_WEIGHT + ).unsqueeze(0) + + return ARGs + + +def advanced_mapping(sd_model, couples: list, WIDTH: int, HEIGHT: int, mapping: list): + assert len(couples) == len(mapping) + + ARGs: dict = {} + IS_SDXL: bool = hasattr( + sd_model.forge_objects.unet.model.diffusion_model, "label_emb" + ) + + for tile_index, (x1, x2, y1, y2, w) in enumerate(mapping): + mask = torch.zeros((HEIGHT, WIDTH)) + + x_from = int(WIDTH * x1) + x_to = int(WIDTH * x2) + y_from = int(HEIGHT * y1) + y_to = int(HEIGHT * y2) + + # ===== Cond ===== + texts = SdConditioning([couples[tile_index]], False, WIDTH, HEIGHT, None) + cond = sd_model.get_learned_conditioning(texts) + pos_cond = [[cond["crossattn"]]] if IS_SDXL else [[cond]] + # ===== Cond ===== + + # ===== Mask ===== + mask[y_from:y_to, x_from:x_to] = w + # ===== Mask ===== + + ARGs[f"cond_{tile_index + 1}"] = pos_cond + ARGs[f"mask_{tile_index + 1}"] = mask.unsqueeze(0) + + return ARGs + + +@torch.inference_mode() +def b64image2tensor(img: str, WIDTH: int, HEIGHT: int) -> torch.Tensor: + image_bytes = decode(img) + image = Image.open(bIO(image_bytes)).convert("L") + + if image.width != WIDTH or image.height != HEIGHT: + image = image.resize((WIDTH, HEIGHT), resample=Image.Resampling.NEAREST) + + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image).unsqueeze(0) + + return image + + +def mask_mapping( + sd_model, + couples: list, + WIDTH: int, + HEIGHT: int, + LINE_COUNT: int, + mapping: list[dict], + background: str, + BG_WEIGHT: float, +): + + mapping = [ + b64image2tensor(m["mask"], WIDTH, HEIGHT) * float(m["weight"]) for m in mapping + ] + + ARGs: dict = {} + IS_SDXL: bool = hasattr( + sd_model.forge_objects.unet.model.diffusion_model, "label_emb" + ) + + for layer in range(LINE_COUNT): + mask = torch.zeros((HEIGHT, WIDTH)) + + # ===== Cond ===== + texts = SdConditioning([couples[layer]], False, WIDTH, HEIGHT, None) + cond = sd_model.get_learned_conditioning(texts) + pos_cond = [[cond["crossattn"]]] if IS_SDXL else [[cond]] + # ===== Cond ===== + + # ===== Mask ===== + if background == "First Line": + if layer == 0: + mask = torch.ones((HEIGHT, WIDTH)) * BG_WEIGHT + else: + mask = mapping[layer - 1] + else: + mask = mapping[layer] + # ===== Mask ===== + + ARGs[f"cond_{layer + 1}"] = pos_cond + ARGs[f"mask_{layer + 1}"] = mask.unsqueeze(0) if mask.dim() == 2 else mask + + if background == "Last Line": + ARGs[f"mask_{LINE_COUNT}"] = ( + torch.ones((HEIGHT, WIDTH)) * BG_WEIGHT + ).unsqueeze(0) + + return ARGs diff --git a/extensions/sd-forge-couple/lib_couple/ui.py b/extensions/sd-forge-couple/lib_couple/ui.py new file mode 100644 index 0000000000000000000000000000000000000000..d9213176791ce1e3ce023c98cdb0aca9053621c0 --- /dev/null +++ b/extensions/sd-forge-couple/lib_couple/ui.py @@ -0,0 +1,193 @@ +from modules.ui_components import ToolButton +from PIL import Image +import gradio as gr + +from .ui_funcs import DEFAULT_MAPPING, visualize_mapping, on_entry +from .gr_version import js + + +def couple_UI(script, is_img2img: bool, title: str): + m: str = "i2i" if is_img2img else "t2i" + + with gr.Accordion( + label=title, + elem_id=f"forge_couple_{m}", + open=False, + ): + with gr.Row(): + enable = gr.Checkbox(label="Enable", elem_classes="fc_enable", scale=2) + + mode = gr.Radio( + ["Basic", "Advanced"], label="Region Assignment", value="Basic", scale=3 + ) + + separator = gr.Textbox( + value="", + label="Couple Separator", + lines=1, + max_lines=1, + placeholder="\\n", + elem_classes="fc_separator", + scale=1, + ) + + with gr.Group(visible=True, elem_classes="fc_bsc") as basic_settings: + with gr.Row(): + direction = gr.Radio( + ["Horizontal", "Vertical"], + label="Tile Direction", + value="Horizontal", + scale=2, + ) + + background = gr.Radio( + ["None", "First Line", "Last Line"], + label="Global Effect", + value="None", + scale=3, + ) + + background_weight = gr.Slider( + minimum=0.1, + maximum=1.0, + step=0.1, + value=0.5, + label="Global Effect Weight", + scale=1, + ) + + with gr.Group(visible=False, elem_classes="fc_adv") as adv_settings: + with gr.Row(elem_classes="fc_mapping_btns"): + gr.Button("Default Mapping", elem_classes="fc_reset_btn") + + gr.HTML('
') + + mapping = gr.JSON(value=DEFAULT_MAPPING, visible=False) + + mapping_paste_field = gr.Textbox( + visible=False, elem_classes="fc_paste_field" + ) + mapping_paste_field.change( + on_entry, mapping_paste_field, mapping, show_progress="hidden" + ).success(None, **js(f'() => {{ ForgeCouple.onPaste("{m}"); }}')) + + mapping_entry_field = gr.Textbox( + visible=False, elem_classes="fc_entry_field" + ) + mapping_entry_field.change( + on_entry, mapping_entry_field, mapping, show_progress="hidden" + ).success(None, **js(f'() => {{ ForgeCouple.preview("{m}"); }}')) + + with gr.Group(elem_classes="fc_row_btns"): + with gr.Row(): + with gr.Column(): + ToolButton( + value="\U0001F195", + elem_id="fc_up_btn", + tooltip="Add a New Row above the Selected Row", + ) + ToolButton( + value="\U0001F195", + elem_id="fc_dn_btn", + tooltip="Add a New Row below the Selected Row", + ) + ToolButton( + value="\U0000274C", + elem_id="fc_del_btn", + tooltip="Delete the Selected Row", + ) + + with gr.Column(elem_classes="fc_bg_btns"): + ToolButton( + value="\U0001F4C2", + elem_id="fc_load_img_btn", + tooltip="Load a background image for the mapping visualization", + ) + if is_img2img: + ToolButton( + value="\U000023CF", + elem_id="fc_load_i2i_img_btn", + tooltip="Load the img2img image as the background image", + ) + ToolButton( + value="\U0001F5D1", + elem_id="fc_clear_img_btn", + tooltip="Remove the background image", + ) + + preview_img = gr.Image( + value=Image.new("RGB", (1, 1), "black"), + image_mode="RGBA", + label="Mapping Preview", + elem_classes="fc_preview_img", + type="pil", + interactive=False, + height=512, + show_download_button=False, + show_label=False, + ) + + preview_res = gr.Textbox( + lines=1, + max_lines=1, + visible=False, + interactive=True, + elem_classes="fc_preview_res", + ) + + preview_btn = gr.Button( + visible=False, + interactive=True, + elem_classes="fc_preview", + ) + + preview_btn.click( + visualize_mapping, + [preview_res, mapping], + preview_img, + show_progress="hidden", + ).success(None, **js(f'() => {{ ForgeCouple.updateColors("{m}"); }}')) + + def on_mode_change(choice): + if choice == "Basic": + return [ + gr.update(visible=True), + gr.update(visible=False), + ] + else: + return [ + gr.update(visible=False), + gr.update(visible=True), + ] + + mode.change(on_mode_change, mode, [basic_settings, adv_settings]).success( + fn=None, **js(f'() => {{ ForgeCouple.preview("{m}"); }}') + ) + + script.paste_field_names = [] + script.infotext_fields = [ + (enable, "forge_couple"), + (mode, "forge_couple_mode"), + (separator, "forge_couple_separator"), + (direction, "forge_couple_direction"), + (background, "forge_couple_background"), + (background_weight, "forge_couple_background_weight"), + (mapping_paste_field, "forge_couple_mapping"), + ] + + for comp, name in script.infotext_fields: + comp.do_not_save_to_config = True + script.paste_field_names.append(name) + + for comp in (mapping, preview_res): + comp.do_not_save_to_config = True + + return [ + enable, + mode, + separator, + direction, + background, + background_weight, + mapping, + ] diff --git a/extensions/sd-forge-couple/lib_couple/ui_funcs.py b/extensions/sd-forge-couple/lib_couple/ui_funcs.py new file mode 100644 index 0000000000000000000000000000000000000000..6b0996177471e0d3e4d1df454b2833479a87e01f --- /dev/null +++ b/extensions/sd-forge-couple/lib_couple/ui_funcs.py @@ -0,0 +1,68 @@ +from json.decoder import JSONDecodeError +from PIL import Image, ImageDraw +from json import loads + +DEFAULT_MAPPING = [[0.0, 0.5, 0.0, 1.0, 1.0], [0.5, 1.0, 0.0, 1.0, 1.0]] +COLORS = ("red", "orange", "yellow", "green", "blue", "indigo", "violet") + + +def validate_mapping(data: list) -> bool: + for x1, x2, y1, y2, w in data: + + if not all(0.0 <= v <= 1.0 for v in (x1, x2, y1, y2)): + print("\n[Couple] Region range must be between 0.0 and 1.0...\n") + return False + + if x2 <= x1 or y2 <= y1: + print('\n[Couple] "to" value must be larger than "from" value...\n') + return False + + return True + + +def visualize_mapping(res: str, mapping: list) -> Image: + w, h = res.split("x") + p_WIDTH, p_HEIGHT = int(w), int(h) + + while p_WIDTH > 1024 or p_HEIGHT > 1024: + p_WIDTH, p_HEIGHT = p_WIDTH // 2, p_HEIGHT // 2 + + while p_WIDTH < 512 and p_HEIGHT < 512: + p_WIDTH, p_HEIGHT = p_WIDTH * 2, p_HEIGHT * 2 + + matt = Image.new("RGBA", (p_WIDTH, p_HEIGHT), (0, 0, 0, 64)) + + if not (validate_mapping(mapping)): + return matt + + line_width = int(max(min(p_WIDTH, p_HEIGHT) / 128, 4.0)) + + draw = ImageDraw.Draw(matt) + + for tile_index, (x1, x2, y1, y2, w) in enumerate(mapping): + color_index = tile_index % len(COLORS) + + x_from = int(p_WIDTH * x1) + x_to = int(p_WIDTH * x2) + y_from = int(p_HEIGHT * y1) + y_to = int(p_HEIGHT * y2) + + draw.rectangle( + ((x_from, y_from), (x_to, y_to)), + outline=COLORS[color_index], + width=line_width, + ) + + return matt + + +def on_entry(data: str) -> list: + if ":" in data: + print("\n[Couple] Old infotext is no longer supported...\n") + return DEFAULT_MAPPING + + try: + return loads(data) + except JSONDecodeError: + print("\n[Couple] Something went wrong while parsing advanced mapping...\n") + return DEFAULT_MAPPING diff --git a/extensions/sd-forge-couple/scripts/__pycache__/forge_couple.cpython-310.pyc b/extensions/sd-forge-couple/scripts/__pycache__/forge_couple.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..665d203e3d0faeccb0b6be60e449667ff413f375 Binary files /dev/null and b/extensions/sd-forge-couple/scripts/__pycache__/forge_couple.cpython-310.pyc differ diff --git a/extensions/sd-forge-couple/scripts/forge_couple.py b/extensions/sd-forge-couple/scripts/forge_couple.py new file mode 100644 index 0000000000000000000000000000000000000000..20a2d70e1a32fab6873e08734f962b6e2e6c52e9 --- /dev/null +++ b/extensions/sd-forge-couple/scripts/forge_couple.py @@ -0,0 +1,218 @@ +from modules import scripts +from json import dumps +import re + +from lib_couple.mapping import ( + empty_tensor, + basic_mapping, + advanced_mapping, + mask_mapping, +) + +from lib_couple.ui import couple_UI +from lib_couple.ui_funcs import validate_mapping + +from lib_couple.attention_couple import AttentionCouple +forgeAttentionCouple = AttentionCouple() + +VERSION = "2.0.3" + +from lib_couple.gr_version import js + + +class ForgeCouple(scripts.Script): + + def __init__(self): + self.couples: list = None + + def title(self): + return "Forge Couple" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + return couple_UI(self, is_img2img, f"{self.title()} v{VERSION}") + + def after_component(self, component, **kwargs): + if not (elem_id := kwargs.get("elem_id", None)): + return + + if elem_id in ("txt2img_width", "txt2img_height"): + component.change(None, **js('() => { ForgeCouple.preview("t2i"); }')) + + elif elem_id in ("img2img_width", "img2img_height"): + component.change(None, **js('() => { ForgeCouple.preview("i2i"); }')) + + @staticmethod + def strip_networks(prompt: str) -> str: + """LoRAs are already parsed thus no longer needed""" + pattern = re.compile(r"<.*?>") + cleaned = re.sub(pattern, "", prompt) + + return cleaned + + def after_extra_networks_activate( + self, + p, + enable: bool, + mode: str, + separator: str, + direction: str, + background: str, + background_weight: float, + mapping: list, + *args, + **kwargs, + ): + if not enable: + return + + separator = "\n" if not separator.strip() else separator.strip() + + couples = [] + + chunks = kwargs["prompts"][0].split(separator) + for chunk in chunks: + prompt = self.strip_networks(chunk).strip() + + if not prompt.strip(): + # Skip Empty Lines + continue + + couples.append(prompt) + + match mode: + case "Basic": + if len(couples) < (3 - int(background == "None")): + print("\n[Couple] Not Enough Lines in Prompt...") + print(f"\t[{len(couples)} / {3 - int(background == 'None')}]\n") + self.couples = None + return + + case "Mask": + if not mapping: + print("\n[Couple] No Mapping...?\n") + self.couples = None + return + + if len(couples) != len(mapping) + int(background != "None"): + print( + f"""\n[Couple] Number of Couples and Masks is not the same... + \t[{len(couples)} / {len(mapping) + int( background != 'None')}]\n""" + ) + self.couples = None + return + + case "Advanced": + if not mapping: + print("\n[Couple] No Mapping...?\n") + self.couples = None + return + + if not validate_mapping(mapping): + self.couples = None + return + + if len(couples) != len(mapping): + print("\n[Couple] Number of Couples and Mapping is not the same...") + print(f"[{len(couples)} / {len(mapping)}]\n") + self.couples = None + return + + # ===== Infotext ===== + p.extra_generation_params["forge_couple"] = True + p.extra_generation_params["forge_couple_separator"] = separator + p.extra_generation_params["forge_couple_mode"] = mode + + if mode == "Basic": + p.extra_generation_params.update( + { + "forge_couple_direction": direction, + "forge_couple_background": background, + "forge_couple_background_weight": background_weight, + } + ) + elif mode == "Advanced": + p.extra_generation_params["forge_couple_mapping"] = dumps(mapping) + # ===== Infotext ===== + + self.couples = couples + + def process_before_every_sampling( + self, + p, + enable: bool, + mode: str, + separator: str, + direction: str, + background: str, + background_weight: float, + mapping: list, + *args, + **kwargs, + ): + + if not enable or not self.couples: + return + + # ===== Init ===== + unet = p.sd_model.forge_objects.unet + + WIDTH: int = p.width + HEIGHT: int = p.height + IS_HORIZONTAL: bool = direction == "Horizontal" + NO_BACKGROUND: bool = background == "None" + + LINE_COUNT: int = len(self.couples) + + if mode != "Advanced": + BG_WEIGHT: float = 0.0 if NO_BACKGROUND else max(0.1, background_weight) + + if mode == "Basic": + TILE_COUNT: int = LINE_COUNT - int(not NO_BACKGROUND) + TILE_WEIGHT: float = 1.25 if NO_BACKGROUND else 1.0 + TILE_SIZE: int = ( + (WIDTH if IS_HORIZONTAL else HEIGHT) - 1 + ) // TILE_COUNT + 1 + # ===== Init ===== + + # ===== Tiles ===== + match mode: + case "Basic": + ARGs = basic_mapping( + p.sd_model, + self.couples, + WIDTH, + HEIGHT, + LINE_COUNT, + IS_HORIZONTAL, + background, + TILE_SIZE, + TILE_WEIGHT, + BG_WEIGHT, + ) + + case "Mask": + ARGs = mask_mapping( + p.sd_model, + self.couples, + WIDTH, + HEIGHT, + LINE_COUNT, + mapping, + background, + BG_WEIGHT, + ) + + case "Advanced": + ARGs = advanced_mapping( + p.sd_model, self.couples, WIDTH, HEIGHT, mapping + ) + # ===== Tiles ===== + + assert len(ARGs.keys()) // 2 == LINE_COUNT + + base_mask = empty_tensor(HEIGHT, WIDTH) + patched_unet = forgeAttentionCouple.patch_unet(unet, base_mask, ARGs) + p.sd_model.forge_objects.unet = patched_unet diff --git a/extensions/sd-forge-couple/style.css b/extensions/sd-forge-couple/style.css new file mode 100644 index 0000000000000000000000000000000000000000..fc44e2f8525ab110ad3b235bd2ec1d306ba58151 --- /dev/null +++ b/extensions/sd-forge-couple/style.css @@ -0,0 +1,111 @@ +#forge_couple_t2i, #forge_couple_i2i { + user-select: none; +} + +.fc_enable>label { + height: 100%; +} + +.fc_adv { + flex-direction: row; + justify-content: center; +} + +.fc_adv>div { + gap: 2em; + width: 100%; +} + +.fc_adv .gradio-image { + width: 512px; + height: 512px; + margin: auto; + display: flex; +} + +.fc_adv .fc_preview_img .image-container { + display: flex; +} + +.fc_adv .fc_preview_img img { + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; + margin: auto !important; + + background-image: none; + background-size: contain; + background-position: center; + background-color: black; +} + +.fc_adv .fc_bg_btns { + min-width: unset !important; + left: calc(100% + 4px); + position: absolute; +} + +.fc_bbox { + position: absolute; + opacity: 0.8; + pointer-events: none; +} + +.fc_row_btns { + position: absolute; + z-index: 500; + right: 4px; + top: -1em; +} + +.fc_row_btns div { + width: auto !important; + min-width: unset !important; +} + +.fc_row_btns button { + width: 2em !important; + height: 2em !important; + min-width: unset !important; +} + +.fc_mapping { + display: flex; + flex-direction: column; + gap: 1em; + + padding: 1em; + border-radius: 1em; + background: var(--panel-background-fill); +} + +.fc_mapping table { + width: 100%; + margin: auto; + table-layout: fixed; +} + +.fc_mapping :is(th, td) { + padding: 4px !important; + white-space: nowrap; + overflow: hidden; + text-overflow: clip; + border: 2px solid var(--table-border-color) !important; +} + +.fc_mapping th { + text-align: center !important; +} + +.fc_mapping_btns { + display: flex; + flex-direction: column; + gap: 1em !important; +} + +.fc_mapping_btns>button { + font-family: "Consolas"; + border-radius: 0.5em !important; + margin: auto; +} diff --git a/extensions/sd-hub/.gitignore b/extensions/sd-hub/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..342dd5e5dc7bd905242231261c0a33ba36a202ec --- /dev/null +++ b/extensions/sd-hub/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +*.png +*.gif +*.jpg diff --git a/extensions/sd-hub/LICENSE b/extensions/sd-hub/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..1d0010520b4f96d7e0d9f0690f502e415fe6274d --- /dev/null +++ b/extensions/sd-hub/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 gutris1 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/sd-hub/README.md b/extensions/sd-hub/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a6cc0f4f8206c7290eb8b38b8c760b0a5d19823a --- /dev/null +++ b/extensions/sd-hub/README.md @@ -0,0 +1,201 @@ +# SD-Hub +an Extension for Stable Diffusion WebUI and Forge.
+You can Download, Upload, Archive files and that's it.
+ +# Changelog +### 2024-07-21 v4.5.6 +- Added support for downloading from Civitai using webpage URLs directly.
+![image](https://github.com/user-attachments/assets/2cde28e1-e88b-45cf-aae4-88bf0bfcf17b) + +
2024-07-12 v4.4.4
+ +- Added support for Windows. +
+ +
2024-07-03 v3.3.3
+ +- added venv support. +
+ +
2024-05-14 v3.2.1
+ +- Added an optional argument - for the Scrape button to filter specific extension instead of using the default extension list. +```python +https://huggingface.co/ckpt/controlnet-sdxl-1.0/tree/main - pth md txt safetensors +``` +- Added an optional argument -- for the Uploader input box to exclude specific file extension instead of uploading all files. +```python +$ext/sd-hub -- json txt py +``` +- Moved Token.json to the Stable Diffusion root directory and renamed to sd-hub-token.json. +- Added a Split by radio button for the Archiver to split compressed files based on the total number of files if input is pointing to a folder. +
+ +
2024-04-22 v2.0.2
+ +- Added Scrape Button to return a list of Resolve URL from Huggingface repository, and Pastebin. +- Improved Compress and Decompress logic for Archiver. +
+ +# Usage +

Downloader

+ +![dl](https://github.com/gutris1/sd-hub/assets/132797949/bbe49e03-9c08-4208-8174-438b47d15927) + + +### ● Input box +Similar to [batchlink](https://github.com/etherealxx/batchlinks-webui), you use tag then URL: + +```python +$tag +URL +``` +Tag should begin with $
+Tag is mandatory and there is no default path.
+For available tags, refer to the [Tag List] at the bottom of the extension.
+![taglist](https://github.com/gutris1/sd-hub/assets/132797949/4e08189c-9617-4681-8985-38cbfd5acb2e) + +You can also add subdirectories to the tag if you have any: +```python +$ckpt/tmp_ckpt +https://civitai.com/api/download/models/403131 +``` + +To add an optional path: +```python +$ckpt +https://civitai.com/api/download/models/403131 /kaggle/working/stable-diffusion-webui/zzzzz +``` + +To download with custom filename, add - after the URL or optional path (if provided): +```python +# Without optional path +$ckpt +https://civitai.com/api/download/models/403131 - imagine-anime-XL.safetensors + +# With optional path +$ckpt +https://civitai.com/api/download/models/403131 /kaggle/working/stable-diffusion-webui/zzzzz - imagine-anime-XL.safetensors +``` + +### ● Token box +![token](https://github.com/gutris1/sd-hub/assets/132797949/b95fe024-0cde-4462-8ca1-3e6df2b10cc3)
+ +Enter your Huggingface token with the role READ to download from your private repo, get one [Here](https://huggingface.co/settings/tokens).
+Enter your Civitai API key if you encounter an Authorization failed error. Get your key [Here](https://civitai.com/user/account).
+Save = To automatically load token upon Reload UI or Webui launch.
+Load = Load token. + +### ● Scrape Button +![UntitledProject-ezgif com-video-to-gif-converter (2)](https://github.com/gutris1/sd-hub/assets/132797949/67f09cca-d433-4f16-982b-cb39b3f2dbed) + + +For Huggingface repository:
+By default it will return a list of resolve URLs that match these extensions .safetensors .bin .pth .pt .ckpt .yaml
+add - to return only a specific file extension.
+ +Paste the repository URL in the following format:
+```python +# To scrape everything in the branch tree list (folders and subfolders won't be included) +htttps://huggingface.co/user_name/repo_name/tree/branch_name + +# To filter specific extension +htttps://huggingface.co/user_name/repo_name/tree/branch_name - pth safetensors + +# To Scrape a folder +htttps://huggingface.co/user_name/repo_name/tree/branch_name/folder + +# or +htttps://huggingface.co/user_name/repo_name/tree/branch_name/folder/sub_folder +``` +Enter your Hugginface READ token into Token box if you want to Scrape your private repo.
+ +And Pastebin:
+Simply paste the pastebin URL
+```python +https://pastebin.com/696969 +``` +And it will return a list of whatever is available at the pastebin URL.
+If it has a hashtag from batchlink, it will automatically be replaced with SD-Hub Tags.
+ +### ● Insert TXT Button +To upload a TXT file from your device, simply select it and upload it into the input box.
+ +Supported Domains for Downloader: Civitai Huggingface Github Drive.Google + +

Uploader

+ +![upl](https://github.com/gutris1/sd-hub/assets/132797949/c71a2e75-8a32-4572-b62c-6b3deb6e5993) + +### ● Input +Username = Your username at huggingface.co.
+Repository = Your model repository at huggingface.co, it will automatically create a new repository if reponame does not exist.
+Branch = Defaults to main. You can change the branch name to create a new branch.
+Visibility = Defaults to Private and will only take effect if you are creating a new repository; otherwise, it will be ignored.
+Token = Obtain your huggingface token with the role WRITE from [Here](https://huggingface.co/settings/tokens).
+ +For the input box, you can either provide a path pointing to a folder or a single file.
+You can also use $tag to skip the long path name.
+You can rename the upload (file or folder) by adding - after the input path.
+You can exclude specific file extension from being uploaded by adding --.
+```python +# Folder as input, so all the files inside the folder with its folder uploaded to your repository +/kaggle/working/stable-diffusion-webui/models/Stable-diffusion + +# With tag +$ckpt + +# To rename the folder +$ckpt - my-merge + +# To rename and exclude specific file extension +$ext/sd-hub - mymodel -- json txt py + +# File as input, so only the file gets uploaded to your repository +/kaggle/working/stable-diffusion-webui/models/Stable-diffusion/animagineXLV31_v31.safetensors + +# with tag +$ckpt/animagineXLV31_v31.safetensors + +# to rename the uploaded file +$ckpt/animagineXLV31_v31.safetensors - XL-imagine-animeV31.txt +``` +

Archiver

+ +![arc](https://github.com/gutris1/sd-hub/assets/132797949/f66e58f6-37e6-4f9b-91f7-5e7ce27cce0f) + + +Supported Format: tar.lz4 tar.gz zip + +Archive:
+Name Name for the compressed file (excluding the file extension).
+Input Path Path pointing a single file or folder containing multiple files.
+Output Path Path where the compressed file will be saved.
+Create Directory Automatically creates a new folder at the Output Path if not already existing.
+Split by Divide the compression into multiple files based on number of files in **Input Path**.
+ +Extract:
+Input Path Path pointing to a compressed file.
+Output Path Path where the compressed file will be saved.
+Create Directory Automatically creates a new folder at the Output Path if not already existing.
+ +You can use $tag for the path in Input and Output Path.
+```python +# if input as a file, to compress a single file +/kaggle/working/stable-diffusion-webui/models/Stable-diffusion/animagineXLV31_v31.safetensors + +# else input as a folder, to compress the whole files inside the input folder +/kaggle/working/stable-diffusion-webui/models/Stable-diffusion + +# with Tag if input as a file +$ckpt/animagineXLV31_v31.safetensors + +# with Tag if input as a folder +$ckpt +``` + +# Credits +[camenduru](https://github.com/camenduru) Thanks for the [extension](https://github.com/camenduru/stable-diffusion-webui-huggingface)
+[etherealxx](https://github.com/etherealxx) Thanks for the [inspiration](https://github.com/etherealxx/batchlinks-webui)
+Thanks to my Discord friends [DEX-1101](https://github.com/DEX-1101), [VeonN4](https://github.com/VeonN4), [kokomif](https://github.com/kokomif), for always being there in the middle of the night.
+Especially to [cupang-afk](https://github.com/cupang-afk), who helped me a lot with Python, thank you. diff --git a/extensions/sd-hub/install.py b/extensions/sd-hub/install.py new file mode 100644 index 0000000000000000000000000000000000000000..e3c0f082e4b66210c4c0d8bca19a7b6a9da289c1 --- /dev/null +++ b/extensions/sd-hub/install.py @@ -0,0 +1,165 @@ +from packaging import version +from typing import List, Dict +from pathlib import Path +import pkg_resources, subprocess, requests, zipfile, launch, sys, os + +base = Path(__file__).parent +req_ = base / "requirements.txt" + +orange_ = '\033[38;5;208m' +blue_ = '\033[38;5;39m' +reset_ = '\033[0m' + +def _sub(inputs: List[str]) -> bool: + try: + subprocess.run( + inputs, check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT + ) + + return True + + except subprocess.CalledProcessError: + return False + +def _check_req(pkg: str, args: str, cmd: str, pkg_list: List[str]) -> None: + try: + subprocess.run( + [pkg, args], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + + except FileNotFoundError: + pkg_list.append(pkg) + _sub(cmd.split()) + +def _install_req_1() -> None: + reqs = [] + names = [] + + with open(req_) as file: + for pkg in file: + pkg = pkg.strip() + + if '==' in pkg: + pkg_name, pkg_version = pkg.split('==') + try: + _version = pkg_resources.get_distribution(pkg_name).version + if version.parse(_version) < version.parse(pkg_version): + reqs.append(pkg) + names.append(pkg_name) + + except pkg_resources.DistributionNotFound: + reqs.append(pkg) + names.append(pkg_name) + + else: + if not launch.is_installed(pkg): + reqs.append(pkg) + names.append(pkg) + + if not sys.platform == 'win32': + if not launch.is_installed('aria2'): + reqs.append('aria2') + names.append('aria2') + + if reqs: + print( + f"Installing SD-Hub requirement: " + f"{' '.join(f'{orange_}{pkg}{reset_}' for pkg in names)}" + ) + + for pkg in reqs: + subprocess.run( + [sys.executable, '-m', 'pip', 'install', '-q', pkg] + ) + +def _install_req_2() -> None: + pkg_list: List[str] = [] + + if sys.platform == 'win32': + aria2_exe = base / 'aria2c.exe' + + if not launch.is_installed('lz4'): + pkg_list.append('lz4') + + if not aria2_exe.exists(): + pkg_list.append('aria2') + + for pkg_name in pkg_list: + if pkg_name == 'lz4': + subprocess.run([sys.executable, '-m', 'pip', 'install', '-q', 'lz4']) + elif pkg_name == 'aria2': + aria2_url = 'https://github.com/aria2/aria2/releases/download/release-1.37.0/aria2-1.37.0-win-64bit-build1.zip' + + with requests.get(aria2_url, stream=True) as r: + r.raise_for_status() + aria2_zip = base / Path(aria2_url).name + with open(aria2_zip, 'wb') as f: + for chunk in r.iter_content(chunk_size=8192): + f.write(chunk) + + with zipfile.ZipFile(aria2_zip, 'r') as zip_ref: + for f in zip_ref.infolist(): + if f.filename.endswith('aria2c.exe'): + f.filename = Path(f.filename).name + zip_ref.extract(f, base) + break + + aria2_zip.unlink() + + else: + env_list: Dict[str, str] = { + 'Colab': 'COLAB_JUPYTER_TRANSPORT', + 'SageMaker Studio Lab': 'SAGEMAKER_INTERNAL_IMAGE_URI', + 'Kaggle': 'KAGGLE_DATA_PROXY_TOKEN' + } + + env = 'Unknown' + + for envs, var in env_list.items(): + if var in os.environ: + env = envs + break + + pkg_cmds: Dict[str, str] = { + 'apt': 'update', + 'conda': '--version' + } + + pv_lz4: Dict[str, str] = { + 'pv': '-V', + 'lz4': '-V' + } + + if env in ['Colab', 'Kaggle']: + if _sub(['apt', pkg_cmds['apt']]): + for pkg, args in pv_lz4.items(): + _check_req(pkg, args, f"apt -y install {pkg}", pkg_list) + + elif env == 'SageMaker Studio Lab': + if _sub(['conda', pkg_cmds['conda']]): + for pkg, args in pv_lz4.items(): + _check_req(pkg, args, f"conda install -qyc conda-forge {pkg}", pkg_list) + + elif env == 'Unknown': + if _sub(['apt', pkg_cmds['apt']]): + for pkg, args in pv_lz4.items(): + _check_req(pkg, args, f"apt -y install {pkg}", pkg_list) + elif _sub(['conda', pkg_cmds['conda']]): + for pkg, args in pv_lz4.items(): + _check_req(pkg, args, f"conda install -qyc conda-forge {pkg}", pkg_list) + else: + print("SD-Hub: Failed to install pv and lz4 in an unknown environment") + + if pkg_list: + print( + f"Installing SD-Hub requirement: " + f"{' '.join(f'{blue_}{pkg}{reset_}' for pkg in pkg_list)}" + ) + +_install_req_1() +_install_req_2() diff --git a/extensions/sd-hub/requirements.txt b/extensions/sd-hub/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..9399e0766d27b4d4deddb032c9a14b80c0523377 --- /dev/null +++ b/extensions/sd-hub/requirements.txt @@ -0,0 +1,2 @@ +gdown==5.2.0 +huggingface-hub==0.24.5 diff --git a/extensions/sd-hub/scripts/__pycache__/hub_ui.cpython-310.pyc b/extensions/sd-hub/scripts/__pycache__/hub_ui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14904ea0b8c21caa0e5e520bef6a59968a8425df Binary files /dev/null and b/extensions/sd-hub/scripts/__pycache__/hub_ui.cpython-310.pyc differ diff --git a/extensions/sd-hub/scripts/hub_ui.py b/extensions/sd-hub/scripts/hub_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..42381bce8e5afccecf92510b103bd7b65a77db37 --- /dev/null +++ b/extensions/sd-hub/scripts/hub_ui.py @@ -0,0 +1,153 @@ +from modules.ui_components import FormRow, FormColumn +from modules import script_callbacks +import gradio as gr + +from sd_hub.markdown import dl_md, upl_md, arc_md1, arc_md2 +from sd_hub.tokenizer import load_token, save_token +from sd_hub.downloader import downloader, read_txt +from sd_hub.archiver import archive, extract +from sd_hub.uploader import uploader +from sd_hub.paths import path_path +from sd_hub.scraper import scraper +from sd_hub.version import version + +token1, token2, token3, _, _ = load_token() + +def on_ui_tabs(): + with gr.Blocks(analytics_enabled=False) as hub, gr.Tabs(): + + with gr.TabItem("Downloader", elem_id="hub-dl"): + gr.Markdown("""

🔽 Download Command Center

""") + with FormRow(): + with FormColumn(scale=7): + gr.Markdown(f"{dl_md}") + + with FormColumn(scale=3, variant="panel"): + dl_token1 = gr.Textbox(value=token2, label="Huggingface Token (READ)", max_lines=1, + placeholder="Your Huggingface Token here (role = READ)", interactive=True) + dl_token2 = gr.Textbox(value=token3, label="Civitai API Key", max_lines=1, + placeholder="Your Civitai API Key here", interactive=True) + + with FormRow(scale=1): + dl_save = gr.Button(value="SAVE", variant="primary", min_width=0) + dl_load = gr.Button(value="LOAD", variant="primary", min_width=0) + + dl_input = gr.Textbox(label="Input", lines=5, placeholder="$tag\nURL", elem_classes="dl-input") + + with FormRow(): + with FormColumn(scale=1): + dl_dl = gr.Button("DOWNLOAD", variant="primary") + + with FormColumn(scale=1), FormRow(scale=1, variant="compact"): + dl_scrape = gr.Button("Scrape", variant="secondary", min_width=0) + dl_txt = gr.UploadButton(label="Insert TXT", variant="secondary", + file_count="single", file_types=[".txt"], min_width=0) + + with FormColumn(scale=2, variant="compact"): + dl_out1 = gr.Textbox(show_label=False, interactive=False, max_lines=1) + dl_out2 = gr.TextArea(show_label=False, interactive=False, lines=5) + + dl_load.click(fn=load_token, inputs=[], outputs=[dl_out2, dl_token1, dl_token2, dl_out2]) + dl_save.click(fn=lambda token2, token3: save_token(None, token2, token3), inputs=[dl_token1, dl_token2], outputs=dl_out2) + dl_dl.click(fn=downloader, inputs=[dl_input, dl_token1, dl_token2, gr.State()], outputs=[dl_out1, dl_out2]) + dl_txt.upload(fn=read_txt, inputs=[dl_txt, dl_input], outputs=dl_input) + dl_scrape.click(fn=scraper, inputs=[dl_input, dl_token1, gr.State()], outputs=[dl_input, dl_out2]) + + + with gr.TabItem("Uploader", elem_id="hub-up"): + gr.Markdown("""

🤗 Upload To Huggingface

""") + with FormRow(): + with FormColumn(scale=7): + gr.Markdown(f"{upl_md}") + + with FormColumn(scale=3, variant="panel"): + gr.Textbox(visible=False, max_lines=1) + upl_token = gr.Textbox(value=token1, label="Huggingface Token (WRITE)", max_lines=1, + placeholder="Your Huggingface Token here (role = WRITE)", interactive=True) + + with FormRow(scale=1): + upl_save = gr.Button(value="SAVE", variant="primary", min_width=0) + upl_load = gr.Button(value="LOAD", variant="primary", min_width=0) + + with FormRow(scale=1): + user_box = gr.Textbox(max_lines=1, placeholder="Username", label="Username", scale=1) + repo_box = gr.Textbox(max_lines=1, placeholder="Repository", label="Repository", scale=1) + branch_box = gr.Textbox(value="main", max_lines=1, placeholder="Branch", label="Branch", elem_classes="up-btn", scale=1) + repo_radio = gr.Radio(["Public", "Private"], value="Private", label="Visibility", interactive=True, scale=1) + gr.Textbox(max_lines=1, show_label=False, scale=2, elem_classes="hide-this") + + upl_inputs = gr.Textbox(show_label=False, lines=5, placeholder="Input File Path") + + with FormRow(): + with FormColumn(scale=1): + upl_btn = gr.Button("UPLOAD", variant="primary") + + with FormColumn(scale=1): + gr.Button("hantu", variant="primary", elem_classes="hide-this") + + with FormColumn(scale=2, variant="compact"): + upl_output1 = gr.Textbox(show_label=False, interactive=False, max_lines=1) + upl_output2 = gr.Textbox(show_label=False, interactive=False, lines=5) + + upl_load.click(fn=load_token,inputs=[], outputs=[upl_token, upl_output2, upl_output2, upl_output2]) + upl_save.click(fn=lambda token1: save_token(token1, None, None), inputs=[upl_token], outputs=upl_output2) + upl_btn.click(fn=uploader, inputs=[upl_inputs, user_box, repo_box, branch_box, upl_token, repo_radio, gr.State()], + outputs=[upl_output1, upl_output2]) + + with gr.TabItem("Archiver", elem_id="hub-arc", elem_classes="tabs"): + with FormRow(): + with FormColumn(): + gr.Markdown(f"{arc_md1}") + with FormColumn(): + gr.Markdown(f"{arc_md2}") + + gr.Markdown("""

Archive

""", scale=1) + arc_format = gr.Radio(["tar.lz4", "tar.gz", "zip"], value="tar.lz4", label="Format", interactive=True) + arc_split = gr.Radio(["None", "2", "3", "4", "5"], value="None", label="Split by", interactive=True) + + with FormRow(): + arc_name = gr.Textbox(max_lines=1, placeholder="Name", show_label=False) + gr.Textbox("hantu", max_lines=1, show_label=False, elem_classes="hide-this") + + with gr.Column(elem_classes="arc-row"): + arc_in = gr.Textbox(max_lines=1, placeholder="Input Path", show_label=False) + arc_out = gr.Textbox(max_lines=1, placeholder="Output Path", show_label=False) + + with gr.Row(elem_classes="arc-row"): + with gr.Column(): + with gr.Row(): + arc_run = gr.Button("Compress", variant="primary") + mkdir_cb1 = gr.Checkbox(label="Create Directory", default=False, elem_classes="cb") + + with gr.Column(): + arc_output1 = gr.Textbox(show_label=False, interactive=False, max_lines=1) + arc_output2 = gr.Textbox(show_label=False, interactive=False, lines=5) + + gr.Markdown("""

Extract

""") + extr_in = gr.Textbox(max_lines=1, placeholder="Input Path", show_label=False) + extr_out = gr.Textbox(max_lines=1, placeholder="Output Path", show_label=False) + + with gr.Row(elem_classes="arc-row"): + with gr.Column(): + with gr.Row(): + extr_btn = gr.Button("Decompress", variant="primary", elem_classes="arc-btn") + mkdir_cb2 = gr.Checkbox(label="Create Directory", default=False, elem_classes="cb") + + with gr.Column(): + gr.Textbox(show_label=False, interactive=False, max_lines=1, elem_classes="hide-this") + + arc_run.click(fn=archive, inputs=[arc_in, arc_name, arc_out, arc_format, mkdir_cb1, arc_split, gr.State()], outputs=[arc_output1, arc_output2]) + extr_btn.click(fn=extract, inputs=[extr_in, extr_out, mkdir_cb2, gr.State()], outputs=[arc_output1, arc_output2]) + + with gr.Accordion("Tag List", open=False, visible=True): + gr.DataFrame(path_path(), headers=["Tag", "Path"], datatype=["str", "str"], interactive=False) + + gr.HTML(f"""
+

SD-Hub • v{version}

""") + + hub.load(fn=load_token, inputs=[], outputs=[upl_token, dl_token1, dl_token2, dl_out2, upl_output2], queue=False) + + return (hub, "HUB", "hub"), + +script_callbacks.on_ui_tabs(on_ui_tabs) +print(f"\033[38;5;208m▶\033[0m SD-Hub: \033[38;5;39mv{version}\033[0m") diff --git a/extensions/sd-hub/sd_hub/__init__.py b/extensions/sd-hub/sd_hub/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/extensions/sd-hub/sd_hub/__pycache__/__init__.cpython-310.pyc b/extensions/sd-hub/sd_hub/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62139b64049751c6d021243b43a0f9c1181564ca Binary files /dev/null and b/extensions/sd-hub/sd_hub/__pycache__/__init__.cpython-310.pyc differ diff --git a/extensions/sd-hub/sd_hub/__pycache__/archiver.cpython-310.pyc b/extensions/sd-hub/sd_hub/__pycache__/archiver.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f519c57315e2fae67a2bff77e170fa01ffbcf68d Binary files /dev/null and b/extensions/sd-hub/sd_hub/__pycache__/archiver.cpython-310.pyc differ diff --git a/extensions/sd-hub/sd_hub/__pycache__/downloader.cpython-310.pyc b/extensions/sd-hub/sd_hub/__pycache__/downloader.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e08eb436aac5b0bd94dd1538d84df30a25753f90 Binary files /dev/null and b/extensions/sd-hub/sd_hub/__pycache__/downloader.cpython-310.pyc differ diff --git a/extensions/sd-hub/sd_hub/__pycache__/markdown.cpython-310.pyc b/extensions/sd-hub/sd_hub/__pycache__/markdown.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7eec6c8b7aa1e717d890e5cd4c77d82cb21ae9a0 Binary files /dev/null and b/extensions/sd-hub/sd_hub/__pycache__/markdown.cpython-310.pyc differ diff --git a/extensions/sd-hub/sd_hub/__pycache__/paths.cpython-310.pyc b/extensions/sd-hub/sd_hub/__pycache__/paths.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f5e2720fcf8cf8638b33d758e284249ff0c58d4 Binary files /dev/null and b/extensions/sd-hub/sd_hub/__pycache__/paths.cpython-310.pyc differ diff --git a/extensions/sd-hub/sd_hub/__pycache__/scraper.cpython-310.pyc b/extensions/sd-hub/sd_hub/__pycache__/scraper.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e993597b2ae5e3131b2bfa4e760d2db0ee41f67 Binary files /dev/null and b/extensions/sd-hub/sd_hub/__pycache__/scraper.cpython-310.pyc differ diff --git a/extensions/sd-hub/sd_hub/__pycache__/tokenizer.cpython-310.pyc b/extensions/sd-hub/sd_hub/__pycache__/tokenizer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..215d6b5dda0716e5c28a4e31ea61edfce1fdb360 Binary files /dev/null and b/extensions/sd-hub/sd_hub/__pycache__/tokenizer.cpython-310.pyc differ diff --git a/extensions/sd-hub/sd_hub/__pycache__/uploader.cpython-310.pyc b/extensions/sd-hub/sd_hub/__pycache__/uploader.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be3bc31342d683ff72b864e86465b23a20ffb271 Binary files /dev/null and b/extensions/sd-hub/sd_hub/__pycache__/uploader.cpython-310.pyc differ diff --git a/extensions/sd-hub/sd_hub/__pycache__/version.cpython-310.pyc b/extensions/sd-hub/sd_hub/__pycache__/version.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24878f8048ed305de48ce6227f2d302aeb193552 Binary files /dev/null and b/extensions/sd-hub/sd_hub/__pycache__/version.cpython-310.pyc differ diff --git a/extensions/sd-hub/sd_hub/archiver.py b/extensions/sd-hub/sd_hub/archiver.py new file mode 100644 index 0000000000000000000000000000000000000000..973825337ee10634cfe38963a6c057ae98a4d97e --- /dev/null +++ b/extensions/sd-hub/sd_hub/archiver.py @@ -0,0 +1,563 @@ +from pathlib import Path +from tqdm import tqdm +import gradio as gr +import subprocess, zipfile, select, sys, os +if sys.platform == 'win32': + import tarfile, gzip, lz4.frame +else: + import pty + +from sd_hub.paths import hub_path + + +def tar_win_process(inputs, paths, formats, outputs): + tar_out = str(outputs) + '.tar' + + with tarfile.open(tar_out, 'w') as tar: + for file in inputs: + tar.add(paths / file, arcname=file.name) + + if formats == 'lz4': + lz4_out = str(outputs) + '.tar.lz4' + with open(tar_out, 'rb') as tar_file: + with open(lz4_out, 'wb') as lz4_file: + data = lz4.frame.compress(tar_file.read()) + lz4_file.write(data) + + Path(tar_out).unlink() + + elif formats == 'gz': + gz_out = str(outputs) + '.tar.gz' + with open(tar_out, 'rb') as tar_file: + with gzip.open(gz_out, 'wb') as gz_file: + gz_file.write(tar_file.read()) + + Path(tar_out).unlink() + + yield f"Saved to: {outputs}.tar.{formats}", True + + +def tar_win(input_path, file_name, output_path, input_type, format_type, split_by): + input_path_obj = Path(input_path) + output_path_obj = Path(output_path) + + yield f"Compressing {input_path_obj}", False + + if input_type == "folder": + all_files = [f for f in input_path_obj.iterdir() if f.is_file() or f.is_dir()] + + total_parts = len(all_files) + files_split = min(split_by, total_parts) if split_by > 0 else 1 + + for i in range(files_split): + start = i * (total_parts // files_split) + end = start + (total_parts // files_split) if i < files_split - 1 else None + split = all_files[start:end] + + output = output_path_obj / f"{file_name}{'_' + str(i + 1) if split_by > 0 else ''}" + + yield from tar_win_process(split, input_path_obj, format_type, output) + + else: + output = output_path_obj / f"{file_name}" + + yield from tar_win_process([input_path_obj], input_path_obj.parent, format_type, output) + + +def tar_process(_tar, cwd, _pv, _format, _output): + ayu, rika = pty.openpty() + + p_tar = subprocess.Popen( + _tar, + cwd=cwd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + p_pv = subprocess.Popen( + _pv, + stdin=p_tar.stdout, + stdout=subprocess.PIPE, + stderr=rika, + text=True + ) + + p_type = subprocess.Popen( + _format, + stdin=p_pv.stdout, + stdout=open(str(_output), 'wb'), + stderr=subprocess.PIPE, + text=True + ) + + os.close(rika) + + while True: + try: + temenan, _, _ = select.select([ayu], [], []) + if temenan: + ketemuan = os.read(ayu, 8192) + if not ketemuan: + break + + yield ketemuan.decode('utf-8'), False + + except OSError: + break + + p_tar.stdout.close() + p_pv.stdout.close() + + _ = p_tar.wait() + _ = p_pv.wait() + _ = p_type.wait() + + yield f"Saved to: {_output}", True + + +def tar_tar(input_path, file_name, output_path, input_type, format_type, split_by): + input_path_obj = Path(input_path) + output_path_obj = Path(output_path) + + if format_type == "gz": + comp_type = "gzip" + elif format_type == "lz4": + comp_type = "lz4" + + if input_type == "folder": + cwd = str(input_path_obj) + all_files = [ + f for f in input_path_obj.iterdir() + if (f.is_file() or (f.is_dir() and any(f.iterdir()))) + ] + + count = 0 + total_parts = len(all_files) + files_split = min(split_by, total_parts) if split_by > 0 else 1 + + for i in range(files_split): + start = i * (total_parts // files_split) + end = start + (total_parts // files_split) if i < files_split - 1 else None + _split = all_files[start:end] + + count += 1 + _output = output_path_obj / f"{file_name}{'_' + str(count) if split_by > 0 else ''}.tar.{format_type}" + _tar = ['tar', 'cfh', '-'] + [f.name for f in _split] + _pv = ['pv'] + _format = [comp_type] + + yield from tar_process(_tar, cwd, _pv, _format, _output) + + else: + _tar = ['tar', 'cf', '-', input_path_obj.name] + cwd = str(input_path_obj.parent) + _output = output_path_obj / f"{file_name}.tar.{format_type}" + _pv = ['pv'] + _format = [comp_type] + + yield from tar_process(_tar, cwd, _pv, _format, _output) + + +def _zip(input_path, file_name, output_path, input_type, format_type, split_by): + _ = format_type + zip_in = Path(input_path) + zip_out = Path(output_path) + _bar = '{percentage:3.0f}% | {n_fmt}/{total_fmt} | {rate_fmt}{postfix}' + + if input_type == 'folder': + cwd = zip_in + all_files = [ + file for file in cwd.iterdir() + if (file.is_file() or (file.is_dir() and any(file.iterdir()))) + ] + + _count = 0 + total_parts = len(all_files) + files_split = min(split_by, total_parts) if split_by > 0 else 1 + + for i in range(files_split): + start = i * (total_parts // files_split) + end = (i + 1) * (total_parts // files_split) if i < files_split - 1 else None + _split = all_files[start:end] + + if not _split: + continue + + _count += 1 + output_zip = zip_out / f"{file_name}{'_' + str(_count) if split_by > 0 else ''}.zip" + + yield f"Compressing {output_zip.name}", False + + with tqdm( + total=sum(f.stat().st_size for f in _split if f.is_file()), + unit="B", unit_scale=True, bar_format=_bar + ) as pbar: + with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf: + for file in _split: + if file.is_file(): + zipf.write(file, file.relative_to(cwd)) + pbar.update(file.stat().st_size) + + else: + for sub_file in file.rglob('*'): + if sub_file.is_file(): + zipf.write(sub_file, sub_file.relative_to(cwd)) + pbar.update(sub_file.stat().st_size) + + yield pbar, False + + yield f"Saved To: {output_zip}", True + + else: + output_zip = zip_out / f"{file_name}.zip" + + yield f"Compressing {output_zip.name}", False + + with tqdm( + total=zip_in.stat().st_size, unit="B", unit_scale=True, bar_format=_bar + ) as pbar: + with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf: + chunk_size = 4096 * 1024 + with open(zip_in, 'rb') as file_to_compress: + while True: + chunk = file_to_compress.read(chunk_size) + if not chunk: + break + + zipf.writestr(zip_in.name, chunk) + pbar.update(len(chunk)) + + yield pbar, False + + yield f"Saved To: {output_zip}", True + + +def path_archive(input_path, file_name, output_path, arc_format, mkdir_cb1, split_by): + input_path = input_path.strip('"').strip("'") + output_path = output_path.strip('"').strip("'") + + params = [ + name for name, value in zip( + ["Input Path", "Name", "Output Path"], + [input_path, file_name, output_path] + ) if not value + ] + + missing = ', '.join(params) + + if missing: + yield f"Missing: [ {missing} ]", True + return + + tag_tag = hub_path() + + for i, path_str in enumerate([input_path, output_path]): + if path_str.startswith('$'): + tag_key, _, subpath_or_file = path_str[1:].partition('/') + resolved_path = tag_tag.get(tag_key) + + if resolved_path is None: + yield f"{tag_key}\nInvalid tag.", True + return + + resolved_path = Path(resolved_path, subpath_or_file) + if i == 0: + input_path = resolved_path + else: + output_path = resolved_path + + input_path_obj = Path(input_path) + output_path_obj = Path(output_path) + + if not input_path_obj.exists(): + yield f"{input_path}\nInput Path does not exist.", True + return + + if output_path_obj.suffix: + yield f"{output_path}\nOutput Path is not a directory.", True + return + + if input_path_obj.is_file(): + input_type = 'file' + elif input_path_obj.is_dir(): + input_type = 'folder' + + if mkdir_cb1: + output_path_obj.mkdir(parents=True, exist_ok=True) + + else: + if not output_path_obj.exists(): + yield f"{output_path}\nOutput Path does not exist.", True + return + + if sys.platform == 'win32': + select_arc = {'zip': _zip, 'tar.gz': tar_win, 'tar.lz4': tar_win} + else: + select_arc = {'zip': _zip, 'tar.gz': tar_tar, 'tar.lz4': tar_tar} + + + arc_select = select_arc.get(arc_format) + + split_dict = {"None": 0, "2": 2, "3": 3, "4": 4, "5": 5} + split_by = split_dict.get(split_by, 0) + + for output in arc_select( + input_path, + file_name, + output_path, + input_type, + format_type=arc_format.split('.')[-1], + split_by=split_by + ): + yield output + + +def archive(input_path, file_name, output_path, arc_format, mkdir_cb1, split_by, box_state=gr.State()): + output_box = box_state if box_state else [] + + for _text, _flag in path_archive( + input_path, + file_name, + output_path, + arc_format, + mkdir_cb1, + split_by + ): + if not _flag: + yield _text, "\n".join(output_box) + + else: + output_box.append(_text) + + catcher = ["not", "Missing", "Invalid"] + + if any(asu in wc for asu in catcher for wc in output_box): + yield "Error", "\n".join(output_box) + + else: + yield "Done", "\n".join(output_box) + + return gr.update(), gr.State(output_box) + +#################################################################################### +#################################################################################### + +def extraction_win(input_path, output_path, format_type): + input_path_obj = Path(input_path) + output_path_obj = Path(output_path) + is_done = False + + yield f"Extracting: {input_path_obj}", False + + if format_type == 'zip': + _bar = '{n_fmt}/{total_fmt} | [{bar:26}]' + + with zipfile.ZipFile(input_path_obj, 'r') as zip_ref: + file_list = zip_ref.namelist() + total_files = len(file_list) + + with tqdm( + total=total_files, + unit='file', + bar_format=_bar, + ascii="▷▶" + ) as pbar: + for file_name in file_list: + zip_ref.extract(file_name, output_path_obj) + pbar.update(1) + yield pbar, False + + is_done = True + + elif format_type in ['tar.gz', 'tar.lz4']: + mode = 'r:gz' if format_type == 'tar.gz' else 'r|' if format_type == 'tar.lz4' else None + + if format_type == 'tar.lz4': + with open(input_path_obj, 'rb') as lz4_file: + with lz4.frame.open(lz4_file, mode='rb') as tar_lz4_file: + with tarfile.open(fileobj=tar_lz4_file, mode=mode) as tar: + tar.extractall(output_path_obj) + + elif format_type == 'tar.gz': + with tarfile.open(input_path_obj, mode=mode) as tar: + tar.extractall(output_path_obj) + + is_done = True + + if is_done: + yield f"Extracted To: {output_path}", True + + +def extraction(input_path, output_path, format_type): + input_path_obj = Path(input_path) + output_path_obj = Path(output_path) + is_done = False + + yield f"Extracting: {input_path_obj}", False + + if format_type == 'zip': + _bar = '{n_fmt}/{total_fmt} | [{bar:26}]' + + with zipfile.ZipFile(input_path_obj, 'r') as zip_ref: + file_list = zip_ref.namelist() + total_files = len(file_list) + + with tqdm( + total=total_files, + unit='file', + bar_format=_bar, + ascii="▷▶" + ) as pbar: + for file_name in file_list: + zip_ref.extract(file_name, output_path_obj) + pbar.update(1) + yield pbar, False + is_done = True + + elif format_type in ['tar.gz', 'tar.lz4']: + _pv = ['pv', str(input_path_obj)] + _type = ['gzip', '-d'] if format_type == 'tar.gz' else ['lz4', '-d'] + _tar = ['tar', 'xf', '-', '-C', str(output_path_obj)] + + ayu, rika = pty.openpty() + + p_pv = subprocess.Popen( + _pv, + stdout=subprocess.PIPE, + stderr=rika, + text=True + ) + + p_type = subprocess.Popen( + _type, + stdin=p_pv.stdout, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + p_tar = subprocess.Popen( + _tar, + stdin=p_type.stdout, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + os.close(rika) + + while True: + try: + temenan, _, _ = select.select([ayu], [], []) + if temenan: + ketemuan = os.read(ayu, 8192) + if not ketemuan: + break + + yield ketemuan.decode('utf-8'), False + is_done = True + + except OSError: + break + + p_pv.stdout.close() + p_type.stdout.close() + + _ = p_pv.wait() + _ = p_type.wait() + _ = p_tar.wait() + + if is_done: + yield f"Extracted To: {output_path}", True + + +def path_extract(input_path, output_path, mkdir_cb2): + input_path = input_path.strip('"').strip("'") + output_path = output_path.strip('"').strip("'") + + params = [ + name for name, value in zip( + ["Input Path", "Output Path"], + [input_path, output_path] + ) if not value + ] + + missing = ', '.join(params) + + if missing: + yield f"Missing: [ {missing} ]", True + return + + tag_tag = hub_path() + + for i, path_str in enumerate([input_path, output_path]): + if path_str.startswith('$'): + tag_key, _, subpath_or_file = path_str[1:].partition('/') + resolved_path = tag_tag.get(tag_key) + + if resolved_path is None: + yield f"{tag_key}\nInvalid tag.", True + return + + resolved_path = Path(resolved_path, subpath_or_file) + if i == 0: + input_path = resolved_path + else: + output_path = resolved_path + + input_path_obj = Path(input_path) + output_path_obj = Path(output_path) + + if not input_path_obj.exists(): + yield f"{input_path}\nInput Path does not exist.", True + return + + if output_path_obj.suffix: + yield f"{output_path}\nOutput Path is not a directory.", True + return + + if mkdir_cb2: + output_path_obj.mkdir(parents=True, exist_ok=True) + else: + if not output_path_obj.exists(): + yield f"{output_path}\nOutput Path does not exist.", True + return + + select_ext = {'.zip': 'zip', '.tar.gz': 'tar.gz', '.tar.lz4': 'tar.lz4'} + input_ext = ''.join(input_path_obj.suffixes) + format_type = select_ext.get(input_ext) + + if not format_type: + yield f"Unsupported format: {input_ext}", True + return + + ext_func = extraction_win if sys.platform == 'win32' else extraction + + for output in ext_func(input_path, output_path, format_type): + yield output + + +def extract(input_path, output_path, mkdir_cb2, box_state=gr.State()): + output_box = box_state if box_state else [] + + for _text, _flag in path_extract( + input_path, + output_path, + mkdir_cb2 + ): + if not _flag: + yield _text, "\n".join(output_box) + + else: + output_box.append(_text) + + catcher = ["not", "Missing", "Invalid", "Unsupported"] + + if any(asu in wc for asu in catcher for wc in output_box): + yield "Error", "\n".join(output_box) + + else: + yield "Done", "\n".join(output_box) + + return gr.update(), gr.State(output_box) diff --git a/extensions/sd-hub/sd_hub/downloader.py b/extensions/sd-hub/sd_hub/downloader.py new file mode 100644 index 0000000000000000000000000000000000000000..011a1003231d5eaaea858c24bbc9ece4c3583199 --- /dev/null +++ b/extensions/sd-hub/sd_hub/downloader.py @@ -0,0 +1,384 @@ +from urllib.parse import urlparse +from pathlib import Path +import gradio as gr +import subprocess, re, sys, requests, time + +from sd_hub.paths import hub_path +from sd_hub.version import xyz + +def gdrown(url, target_path=None, fn=None): + gfolder = "drive.google.com/drive/folders" in url + cli = xyz('gdown.exe') if sys.platform == 'win32' else xyz('gdown') + cmd = cli + ["--fuzzy", url] + + if fn: + cmd += ["-O", fn] + if gfolder: + cmd.append("--folder") + + cwd = target_path if target_path else Path.cwd() + + p = subprocess.Popen( + cmd, + cwd=cwd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + bufsize=1, + text=True + ) + + gdown_output = "" + gdown_progress = None + starting_line = time.time() + failure = False + + while True: + output = p.stdout.readline() + if not output: + break + gdown_output += output + + if "Failed to retrieve file url" in output: + failure = True + + if re.search(r'\d{1,3}%', output): + gdown_progress = output.strip() + + if gdown_progress and time.time() - starting_line >= 1: + yield gdown_progress, False + starting_line = time.time() + + if failure: + failed = gdown_output.find("Failed to retrieve file url") + lines = gdown_output[failed:] + yield lines, False + + for lines in gdown_output.split('\n'): + if lines.startswith("To:"): + completed = re.search(r'[^/]*$', lines) + if completed: + yield f"Saved To: {target_path}/{completed.group()}", True + + p.wait() + +def ariari(url, target_path=None, fn=None, token2=None, token3=None): + exe = Path(__file__).parent.parent / 'aria2c.exe' + aria2cmd = [str(exe)] if sys.platform == 'win32' else xyz('aria2c') + + if "huggingface.co" in url: + if "/blob/" in url: + url = url.replace("/blob/", "/resolve/") + + if token2: + aria2cmd.extend(["--header=User-Agent: Mozilla/5.0", + f"--header=Authorization: Bearer {token2}"]) + + elif "civitai.com" in url: + if token3: + aria2cmd.extend(["--header=User-Agent: Mozilla/5.0"]) + + if '?token=' in url: + url = url.split('?token=')[0] + if '?type=' in url: + url = url.replace('?type=', f'?token={token3}&type=') + else: + url = f"{url}?token={token3}" + + if "civitai.com/models/" in url: + try: + if '?modelVersionId=' in url: + version_id = url.split('?modelVersionId=')[1] + response = requests.get(f"https://civitai.com/api/v1/model-versions/{version_id}") + else: + model_id = url.split('/models/')[1].split('/')[0] + response = requests.get(f"https://civitai.com/api/v1/models/{model_id}") + + response.raise_for_status() + data = response.json() + + if "downloadUrl" in data: + download_url = data["downloadUrl"] + elif "modelVersions" in data and data["modelVersions"]: + download_url = data["modelVersions"][0].get("downloadUrl", "") + else: + yield f"Unable to find download URL for\n-> {url}\n", True + return + + url = f"{download_url}?token={token3}" if token3 else download_url + + except requests.exceptions.RequestException as e: + yield f"{str(e)}\n", True + return + + aria2cmd.extend([ + "--console-log-level=error", + "--allow-overwrite=true", + "--stderr=true", + "--summary-interval=1", + "-c", "-x16", "-s16", "-k1M", "-j5" + ] + ) + + if target_path: + aria2cmd.extend(["-d", target_path]) + + if fn: + aria2cmd.extend(["-o", fn]) + + aria2cmd.append(url) + + p = subprocess.Popen( + aria2cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + bufsize=1, + text=True + ) + + aria2_output = "" + break_line = False + error = False + + while True: + output = p.stdout.readline() + if not output: + break + + aria2_output += output + + if "errorCode=24" in output: + uri_pattern = re.search(r'URI=(https?://\S+)', output) + if uri_pattern: + uri = uri_pattern.group(1) + url_list = { + "huggingface.co": f"## Authorization Failed, Enter your Huggingface Token\n-> {url}\n", + "civitai.com": f"## Authorization Failed, Enter your Civitai API Key\n-> {url}\n" + } + + for domain, msg in url_list.items(): + if domain in uri: + yield msg, True + error = True + break + continue + + if "errorCode" in output: + arrow = aria2_output.find("->") + if arrow != -1: + lines = aria2_output.find("\n", arrow) + if lines != -1: + errorcode = aria2_output[arrow:lines] + uri_pattern = re.search(r'URI=(https?://\S+)', aria2_output) + if uri_pattern: + uri = uri_pattern.group(1) + errorcode += '\n' + uri + '\n' + + yield errorcode, True + error = True + continue + + for lines in output.splitlines(): + dl_line = re.match(r'\[#\w{6}\s(.*?)\((\d+\%)\).*?DL:(.*?)\s', lines) + if dl_line: + sizes = dl_line.group(1) + percent = dl_line.group(2) + speed = dl_line.group(3) + outputs = f"{percent} | {sizes} | {speed}/s" + yield outputs, False + + break_line = True + error = False + break + + if break_line: + pass + + if not error: + separator = aria2_output.find("======+====+===========") + if separator != -1: + for lines in aria2_output[separator:].splitlines(): + if "|" in lines: + pipe = lines.split('|') + if len(pipe) > 3: + output_dir = pipe[3].strip() + yield f"Saved To: {output_dir}", True + + p.wait() + +def get_fn(url): + fn_fn = urlparse(url) + + if "civitai.com" in fn_fn.netloc or "drive.google.com" in fn_fn.netloc: + return None + else: + fn = Path(fn_fn.path).name + return fn + +def url_check(url): + try: + url_parsed = urlparse(url) + + if not (url_parsed.scheme and url_parsed.netloc): + return False, "Invalid URL." + + supported = ["civitai.com", + "huggingface.co", + "github.com", + "drive.google.com"] + + if url_parsed.netloc not in supported: + supported_str = "\n".join(supported) + return False, f"Supported Domain :\n{supported_str}" + + return True, "" + except Exception as e: + return False, str(e) + +def input_process(url_line, current_path, tags_mappings): + if any(url_line.startswith(char) for char in ('/', '\\', '#')): + return None, None, None, "Invalid usage, Tag should start with $" + + if url_line.startswith('$'): + parts = url_line[1:].strip().split('/', 1) + tags_key = parts[0].lower() + subfolder = parts[1] if len(parts) > 1 else None + base_path = tags_mappings.get(tags_key) + if base_path is not None: + full_path = Path(base_path, subfolder) if subfolder else Path(base_path) + current_path = full_path + else: + return None, None, None, f"{tags_key}\nInvalid Tag." + return current_path, None, None, None + + parts = url_line.split(' ') + url = parts[0].strip() + + is_valid, error_message = url_check(url) + if not is_valid: + return None, None, None, error_message + + optional_path = None + optional_fn = None + + if len(parts) > 1: + if '-' in parts: + dash_index = parts.index('-') + optional_path_raw = ' '.join(parts[1:dash_index]).strip() + optional_fn = ' '.join(parts[dash_index + 1:]).strip() + else: + optional_path_raw = ' '.join(parts[1:]).strip() + + optional_path_raw = optional_path_raw.strip('"').strip("'") + optional_path = Path(optional_path_raw) if optional_path_raw else None + + if optional_path and optional_path.suffix: + return None, None, None, f"{optional_path}\nOutput path is not a path." + + if optional_fn: + optional_fn_path = Path(optional_fn) + if not optional_fn_path.suffix: + return None, None, None, f"{optional_fn}\nOutput filename is missing its extension." + + target_path = optional_path if optional_path else current_path + if target_path is None or not target_path.exists(): + return None, None, None, f"{target_path}\nDoes not exist." + + fn = get_fn(url) if not optional_fn else optional_fn + + return target_path, url, fn, None + +def surface(command, token2=None, token3=None): + if not command.strip(): + yield "Nothing To See Here.", True + return + + tags_mappings = hub_path() + current_path = None + urls = [url_line for url_line in command.strip().split('\n') if url_line.strip()] + + if len(urls) == 1 and urls[0].startswith('$'): + yield "Missing URL.", True + return + + for url_line in urls: + target_path, url, fn, error = input_process(url_line, current_path, tags_mappings) + + if error: + yield error, True + return + + if not url: + current_path = target_path + continue + + if "drive.google" in url: + for message, isError in gdrown(url, target_path, fn): + yield message, isError + continue + + for output in ariari( + url, + target_path, + fn, + token2, + token3 + ): + yield output + +def downloader(command, token2, token3, box_state=gr.State()): + output_box = box_state if box_state else [] + + yield "Now Downloading...", "" + + for _text, _flag in surface(command, token2, token3): + if not _flag: + if "Enter your" in _text: + yield "Error", "\n".join([_text] + output_box) + return gr.update(), gr.State(output_box) + + if "errorCode" in _text: + yield "Error", "\n".join([_text] + output_box) + return gr.update(), gr.State(output_box) + + if "Failed to retrieve" in _text: + yield "Error", "\n".join([_text] + output_box) + return gr.update(), gr.State(output_box) + + yield _text, "\n".join(output_box) + + else: + output_box.append(_text) + + catcher = [ + "exist", "Invalid", "Tag", "Output", "Nothing", + "URL", "filename", "Supported Domain:" + ] + + if any(asu in wc for asu in catcher for wc in output_box): + yield "Error", "\n".join(output_box) + else: + yield "Done", "\n".join(output_box) + + return gr.update(), gr.State(output_box) + +def read_txt(file, box): + text_box = [] + + if file is not None: + inputs = file.name + txt = "" + + with open(inputs, 'r') as content: + txt = content.read() + + if box.strip() == "": + result = txt + else: + result = box + '\n' + txt + + text_box.append(result) + output = '\n'.join(text_box) + + return output + + return '\n'.join(text_box) diff --git a/extensions/sd-hub/sd_hub/markdown.py b/extensions/sd-hub/sd_hub/markdown.py new file mode 100644 index 0000000000000000000000000000000000000000..ff66f8ee1eea24164bd3541ae8929cd2dee3a2c7 --- /dev/null +++ b/extensions/sd-hub/sd_hub/markdown.py @@ -0,0 +1,34 @@ +dl_md = """

+ +Enter your **Huggingface Token** with the role **READ** to download from your private repo. Get one [Here](https://huggingface.co/settings/tokens) +Enter your **Civitai API Key** if you encounter an Authorization failed error. Get your key [Here](https://civitai.com/user/account) +Save = To automatically load token upon Reload UI or Webui launch. +Load = Load token. + +Supported Domains: CivitaiHuggingfaceGithubDrive.Google +See usage [Here](https://github.com/gutris1/sd-hub/blob/master/README.md#downloader)

""" + +upl_md = """

+ +**Colab**: /content/stable-diffusion-webui/model.safetensors +**Kaggle**: /kaggle/working/stable-diffusion-webui/model.safetensors +**Sagemaker Studio Lab**: /home/studio-lab-user/stable-diffusion-webui/model.safetensors + +Get your **Huggingface Token** with the role **WRITE** from [Here](https://huggingface.co/settings/tokens) +See usage [Here](https://github.com/gutris1/sd-hub/blob/master/README.md#uploader)

""" + +arc_md1 = """

+ +**Archive :** +Name Name for the compressed file (excluding the file extension) +Input Path Path pointing a single file or folder containing multiple files +Output Path Path where the compressed file will be saved +Create Directory Automatically creates a new folder at the Output Path if not already existing +Split by Divide the compression into multiple files based on number of files in **Input Path**

""" + +arc_md2 = """

+ +**Extract :** +Input Path Path pointing to a compressed file +Output Path Path where the compressed file will be extracted +Create Directory Automatically creates a new folder at the Output Path if not already existing

""" diff --git a/extensions/sd-hub/sd_hub/paths.py b/extensions/sd-hub/sd_hub/paths.py new file mode 100644 index 0000000000000000000000000000000000000000..dcc81a78128cbb3335a141447ae971515a720294 --- /dev/null +++ b/extensions/sd-hub/sd_hub/paths.py @@ -0,0 +1,75 @@ +from modules.paths_internal import models_path, data_path +from pathlib import Path +import os + +_data = Path(data_path) +_model = Path(models_path) + +def hub_path(): + paths = path_path() + paths_dict = {} + + for _desc, full_path in paths: + paths_dict[_desc.lower()] = full_path + + return paths_dict + +def path_path(): + tags_list = { + "ckpt": ("stable-diffusion", "_model"), + "lora": ("lora", "_model"), + "vae": ("vae", "_model"), + "emb": ("embeddings", "_data"), + "ups": ("esrgan", "_model"), + "ups2": ("gfpgan", "_model"), + "ups3": ("realesrgan", "_model"), + "cn": ("controlnet", "_model"), + "hn": ("hypernetworks", "_model"), + "ad": ("adetailer", "_model"), + "cf": ("codeformer", "_model"), + "ext": ("extensions", "_data"), + } + + paths = [] + + for _desc, (_name, _in) in tags_list.items(): + if _name is not None: + if _in == "_model": + model_dir = next(os.walk(str(_model)))[1] + model_dir_lower = [d.lower() for d in model_dir] + if _name.lower() in model_dir_lower: + path_model = _model / model_dir[model_dir_lower.index(_name.lower())] + paths.append([_desc, path_model]) + + if _in == "_data": + data_dir = next(os.walk(str(_data)))[1] + data_dir_lower = [d.lower() for d in data_dir] + if _name.lower() in data_dir_lower: + path_data = _data / data_dir[data_dir_lower.index(_name.lower())] + paths.append([_desc, path_data]) + + if _desc == "cn": + cn_path = _data / "extensions/sd-webui-controlnet/models" + if cn_path.is_dir(): + cn_dir = next(os.walk(str(cn_path)))[1] + cn_dir_lower = [d.lower() for d in cn_dir] + if _name.lower() in cn_dir_lower: + path_cn = cn_path / cn_dir[cn_dir_lower.index(_name.lower())] + paths.append(["cn", path_cn]) + else: + paths.append(["cn", ""]) + + paths.append(["root", str(_data)]) + + env_list = { + 'COLAB_JUPYTER_TRANSPORT': '/content', + 'SAGEMAKER_INTERNAL_IMAGE_URI': '/home/studio-lab-user', + 'KAGGLE_DATA_PROXY_TOKEN': '/kaggle/working' + } + + for var, path in env_list.items(): + if var in os.environ: + paths.append(["home", path]) + break + + return paths diff --git a/extensions/sd-hub/sd_hub/scraper.py b/extensions/sd-hub/sd_hub/scraper.py new file mode 100644 index 0000000000000000000000000000000000000000..9c798619ad06a27b8fecdfbd4a8dececdbbfc7a8 --- /dev/null +++ b/extensions/sd-hub/sd_hub/scraper.py @@ -0,0 +1,185 @@ +from urllib.parse import urlparse +from pathlib import Path +import gradio as gr +import os, stat, sys, shutil, requests, subprocess + +def is_valid_url(url): + parsing = urlparse(url) + return all([parsing.scheme, parsing.netloc]) + +def remove_readonly(func, path, exc_info): + "Clear the readonly bit and reattempt the removal" + "https://bugs.python.org/issue43657#msg389724" + if func not in (os.unlink, os.rmdir) or exc_info[1].winerror != 5: + raise exc_info[1] + os.chmod(path, stat.S_IWRITE) + func(path) + +def scraping(input_string, token=None): + _lines = input_string.split('\n') + _outputs = [] + _h = {"User-Agent": "Mozilla/5.0"} + _base = Path(__file__).parent + _tmp = _base / "tmp" + + if _tmp.exists(): + if sys.platform == 'win32': + shutil.rmtree(_tmp, onerror=remove_readonly) + else: + os.system(f"rm -rf {_tmp}") + + if not input_string.strip(): + yield "Nothing To Scrape Here", True + + for line in _lines: + url = line.strip() + url = url.rstrip('/') + asd = 'huggingface.co' in url or 'pastebin.com' in url + + if 'huggingface.co' in url: + if '/tree/' not in url and '/resolve/' in url: + _outputs.append(line) + continue + elif '/tree/' not in url and '/resolve/' not in url: + _outputs.append(line) + yield "Input should be at least huggingface.co/username/repo/tree/main", True + continue + + else: + base_url = url + ext = None + + if ' - ' in url: + base_url, ext = url.split(' - ') + + response = requests.get(base_url, headers=_h) + if response.status_code == 401 and not token: + _outputs.append(line) + yield ( + f"{base_url}\n" + f"{response.status_code} {response.reason}\n" + "Please Enter your Huggingface Token with the role Read" + ), True + continue + + if response.status_code != 200 and response.status_code != 401: + _outputs.append(line) + yield ( + f"{base_url}\n" + f"{response.status_code} {response.reason}\n" + ), True + continue + + _tmp.mkdir(exist_ok=True) + _parts = urlparse(base_url).path.split('/') + _tree = _parts.index('tree') + _branch = _parts[_tree + 1] + _folder = '/'.join(_parts[_tree + 2:]) if len(_parts) > _tree + 2 else None + _url = base_url.split('/tree/')[0] + + if token: + _url = f"https://hf_user:{token}@huggingface.co/{_url.split('huggingface.co/')[1]}" + + if _branch != 'main': + subprocess.run(["git", "clone", "--no-checkout", "--depth=1", "-b", _branch, _url, _tmp], + check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + else: + subprocess.run(["git", "clone", "--no-checkout", "--depth=1", _url, _tmp], + check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + output = subprocess.run(["git", "--git-dir", _tmp / ".git", "ls-tree", "-r", _branch], + capture_output=True, text=True) + + _file_list = output.stdout.split('\n') + + if ext: + _ext_list = ext.split() + else: + _ext_list = ['.safetensors', '.bin', '.pth', '.pt', '.ckpt', '.yaml'] + + for _items in _file_list: + if not _items: + continue + + _file = " ".join(_items.split()[3:]) + _file_parts = _file.split('/') + + if ( + (_folder and _file.startswith(_folder)) + or (not _folder and len(_file_parts) == 1) + ): + if any(_file.endswith(ext) for ext in _ext_list): + if _folder and _file.startswith(_folder + '/') and _file.count('/') == _folder.count('/') + 1: + _file = _file[len(_folder)+1:] + elif not _folder and _file.count('/') == 0: + pass + else: + continue + + url_url = base_url.replace('/tree/', '/resolve/') + f'/{_file}' + + _outputs.append(url_url) + + if _tmp.exists(): + if sys.platform == 'win32': + shutil.rmtree(_tmp, onerror=remove_readonly) + else: + os.system(f"rm -rf {_tmp}") + + elif 'pastebin.com' in url: + p_url = url.replace('pastebin.com', 'pastebin.com/raw') + response = requests.get(p_url, headers=_h) + + if not response.status_code == 200: + _outputs.append(line) + yield ( + f"{url}\n" + f"{response.status_code} {response.reason}\n" + ), True + continue + + else: + _p_content = response.text + + tagz_list = {'#model': '$ckpt', + '#lora': '$lora', + '#vae': '$vae', + '#embed': '$emb', + '#hynet': '$hn', + '#cnet': '$cn', + '#ext': '$ext', + '#upscaler': '$ups', + '#lycoris': '$lora'} + + _replaced = _p_content + for _tags, to_replace in tagz_list.items(): + _replaced = _replaced.replace(_tags, to_replace) + + _outputs.append(_replaced) + + else: + if is_valid_url(url): + _outputs.append(line) + if not asd: + yield f"Unsupported domain: {url}\n\nSupported Domains:\n{'':<10}huggingface.co\n{'':<10}pastebin.com", True + else: + yield f"Supported Domains:\n{'':<10}huggingface.co\n{'':<10}pastebin.com", True + else: + _outputs.append(line) + + yield '\n'.join(_outputs), False + +def scraper(input_string, token, box_state=gr.State()): + output_box = box_state if box_state else [] + + for _text, _flag in scraping(input_string, token): + if not _flag: + if "Nothing" in _text or "should be" in _text or "Supported Domains" in _text: + yield _text, "\n".join(output_box) + else: + yield _text, "\n".join(output_box) + else: + output_box.append(_text) + + return gr.update(), gr.State(output_box) diff --git a/extensions/sd-hub/sd_hub/tokenizer.py b/extensions/sd-hub/sd_hub/tokenizer.py new file mode 100644 index 0000000000000000000000000000000000000000..74e93ee7d5e2584350cdbb5878c6593dabd453d3 --- /dev/null +++ b/extensions/sd-hub/sd_hub/tokenizer.py @@ -0,0 +1,62 @@ +from modules.paths_internal import data_path +from pathlib import Path +import json + +token_dir = Path(data_path) / "sd-hub-token.json" + +def load_token(): + try: + with open(token_dir, "r", encoding="utf-8") as file: + value = json.load(file) + + token1 = value.get("huggingface-token-write", "") + token2 = value.get("huggingface-token-read", "") + token3 = value.get("civitai-api-key", "") + + msg = [] + + if token1: + msg.append("Huggingface Token (WRITE) loaded") + + if token2: + msg.append("Huggingface Token (READ) loaded") + + if token3: + msg.append("Civitai API Key loaded") + + if not msg: + return "", "", "", "No Token Found", "No Token Found" + + joined = "\n".join(msg) + return token1, token2, token3, joined, joined + + except FileNotFoundError: + return "", "", "", f"{token_dir.name} Not Found", f"{token_dir.name} Not Found" + + +def save_token(token1=None, token2=None, token3=None): + try: + with open(token_dir, "r", encoding="utf-8") as file: + value = json.load(file) + + except FileNotFoundError: + value = { + "huggingface-token-write": "", + "huggingface-token-read": "", + "civitai-api-key": "" + } + + if token1 is not None: + value["huggingface-token-write"] = token1 + if token2 is not None: + value["huggingface-token-read"] = token2 + if token3 is not None: + value["civitai-api-key"] = token3 + + try: + with open(token_dir, "w", encoding="utf-8") as file: + json.dump(value, file, indent=4) + return f"Token Saved To: {token_dir}" + + except Exception as e: + return f"Error: {e}" diff --git a/extensions/sd-hub/sd_hub/uploader.py b/extensions/sd-hub/sd_hub/uploader.py new file mode 100644 index 0000000000000000000000000000000000000000..90f6b1236bf4cf4d73ecb7d9b4d4cfcb7fa34908 --- /dev/null +++ b/extensions/sd-hub/sd_hub/uploader.py @@ -0,0 +1,198 @@ +from huggingface_hub import model_info, create_repo, create_branch +from huggingface_hub.utils import RepositoryNotFoundError +from pathlib import Path +import gradio as gr +import subprocess, re, sys, time + +from sd_hub.paths import hub_path +from sd_hub.version import xyz + +def push_push(repo_id, file_path, file_name, token, branch, is_private=False, commit_msg="", ex_ext=None): + msg = commit_msg.replace('"', '\\"') + cli = xyz('huggingface-cli.exe') if sys.platform == 'win32' else xyz('huggingface-cli') + cmd = cli + [ + 'upload', repo_id, file_path, file_name, + '--token', token, + '--revision', branch, + '--commit-message', msg + ] + + if is_private: + cmd.append('--private') + + if ex_ext: + cmd.append('--exclude') + cmd.extend([f'*.{ext}' for ext in ex_ext]) + + p = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True + ) + + failed = False + error = "" + starting_line = time.time() + + for line in p.stdout: + output = line.strip() + + if "Bad request" in output: + failed = True + break + + kandang = output.split(':', 1) + if len(kandang) > 1: + asu = kandang[1].strip() + else: + continue + + lari = re.compile(r'\d+%|\d+M/\d+G|\d+\.\d+MB/s') + now_line = time.time() + if lari.search(asu): + if now_line - starting_line >= 1: + if "Consider using" in asu: + continue + yield asu, False + starting_line = now_line + + if failed: + error = output + while True: + part = p.stdout.readline() + if not part: + break + error += part + error = '.\n'.join(error.split('. ')) + yield error, True + + p.stdout.close() + p.wait() + + +def up_up(inputs, user, repo, branch, token, repo_radio): + input_lines = [line.strip() for line in inputs.strip().splitlines()] + + if not inputs.strip() or not all([user, repo, branch, token]): + params = [name for name, value in zip( + ["Input", "Username", "Repository", "Branch", "Token"], + [inputs.strip(), user, repo, branch, token]) if not value] + + missing = ', '.join(params) + + yield f"Missing: [ {missing} ]", True + return + + repo_id = f"{user}/{repo}" + tag_tag = hub_path() + task_task = [] + + for line in input_lines: + parts = line.split() + input_path = parts[0] + input_path = input_path.strip('"').strip("'") + + given_fn = None + ex_ext = None + + if '-' in parts: + given_fn_fn = parts.index('-') + 1 + if given_fn_fn < len(parts): + given_fn = parts[given_fn_fn] + else: + yield "Invalid usage\n[ - ]", True + return + + if '--' in parts: + ex_ext_ext = parts.index('--') + 1 + if ex_ext_ext < len(parts): + ex_ext = parts[ex_ext_ext:] + else: + yield "Invalid usage\n[ -- ]", True + return + + full_path = Path(input_path) if not input_path.startswith('$') else None + if input_path.startswith('$'): + tag_key, _, subpath_or_file = input_path[1:].partition('/') + resolved_path = tag_tag.get(tag_key) + if resolved_path is None: + yield f"{tag_key}\nInvalid tag.", True + return + full_path = Path(resolved_path, subpath_or_file) + + if full_path: + if full_path.is_file(): + type_ = "file" + elif full_path.is_dir(): + type_ = "folder" + else: + type_ = "unknown" + else: + yield f"{input_path}\nInput Path does not exist.", True + return + + if given_fn and not Path(given_fn).suffix and full_path.is_file(): + given_fn += full_path.suffix + + task_task.append((full_path, given_fn or full_path.name, type_)) + + for file_path, file_name, type_ in task_task: + yield f"Uploading: {file_name}", False + + try: + model_info(repo_id, token=token) + except RepositoryNotFoundError: + is_private = repo_radio == "Private" + create_repo(repo_id, private=is_private, token=token) + + create_branch(repo_id=repo_id, branch=branch, token=token, exist_ok=True) + repo_info = model_info(repo_id, token=token) + + erorr = False + + for output in push_push( + repo_id=repo_id, + file_path=str(file_path), + file_name=file_name, + token=token, + branch=branch, + is_private=repo_radio == "Private", + commit_msg=f"Upload {file_name} using SD-Hub extension", + ex_ext=ex_ext): + + yield output + + if output[1]: + erorr = True + break + + if not erorr: + details = ( + f"{repo_info.id}/{branch}\n" + f"{file_name}\n" + f"{repo_info.last_modified.strftime('%Y-%m-%d %H:%M:%S')}\n" + ) + + yield details, True + + +def uploader(inputs, user, repo, branch, token, repo_radio, box_state=gr.State()): + output_box = box_state if box_state else [] + + for _text, _flag in up_up(inputs, user, repo, branch, token, repo_radio): + if not _flag: + if "Uploading" in _text: + yield _text, "\n".join(output_box) + yield _text, "\n".join(output_box) + else: + output_box.append(_text) + + catcher = ["not", "Missing", "Error", "Invalid"] + + if any(asu in wc for asu in catcher for wc in output_box): + yield "Error", "\n".join(output_box) + else: + yield "Done", "\n".join(output_box) + + return gr.update(), gr.State(output_box) diff --git a/extensions/sd-hub/sd_hub/version.py b/extensions/sd-hub/sd_hub/version.py new file mode 100644 index 0000000000000000000000000000000000000000..8f8877dcf3386e077e70270703e150cc398771be --- /dev/null +++ b/extensions/sd-hub/sd_hub/version.py @@ -0,0 +1,14 @@ +version = "4.7.2" + +import os, sys +from pathlib import Path +from typing import List + +def xyz(y: str) -> List[str]: + if 'COLAB_JUPYTER_TRANSPORT' in os.environ: + x = Path('/usr/local/bin') + else: + x = Path(sys.executable).parent + + z = x / y + return [str(z)] diff --git a/extensions/sd-hub/style.css b/extensions/sd-hub/style.css new file mode 100644 index 0000000000000000000000000000000000000000..f000c6dcf365d42eab52a13e5480e3f8546f275a --- /dev/null +++ b/extensions/sd-hub/style.css @@ -0,0 +1,39 @@ +.hide-this { + visibility: hidden !important;} + +#hub-dl .dl-btn { + width: 100px !important;} +#hub-dl .dl-input { + margin-top: 0px !important;} +#hub-dl .dl-row { + margin-top: -7px !important;} + +#hub-up .up-btn { + width: 100px !important;} +#hub-up .up-input { + margin-top: -15px !important;} +#hub-up .up-row { + margin-top: -14px !important;} + +#hub-arc .arc-btn { + width: 100px !important;} +#hub-arc .arc-input { + margin-top: -15px !important;} +#hub-arc .arc-row { + margin-top: -9px !important;} +#hub-arc .cb { + margin-left: 60px !important;} + +#hub .hub-box { + border: transparent !important;} + +#btn .btn { + width: 100px !important;} + +#gap .gap { + margin-top: 7px !important;} + +#asu .hf-margin { + width: 50%; + margin-top: 10px; + margin-bottom: 10px;} \ No newline at end of file diff --git a/extensions/sd-perturbed-attention/.github/workflows/publish.yml b/extensions/sd-perturbed-attention/.github/workflows/publish.yml new file mode 100644 index 0000000000000000000000000000000000000000..d7334248801d0cdd1016da8368f99a2228793853 --- /dev/null +++ b/extensions/sd-perturbed-attention/.github/workflows/publish.yml @@ -0,0 +1,23 @@ +name: Publish to Comfy registry +on: + workflow_dispatch: + push: + branches: + - master + paths: + - "pyproject.toml" + +jobs: + publish-node: + name: Publish Custom Node to registry + runs-on: ubuntu-latest + # if this is a forked repository. Skipping the workflow. + if: github.event.repository.fork == false + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Publish Custom Node + uses: Comfy-Org/publish-node-action@main + with: + ## Add your own personal access token to your Github Repository secrets and reference it here. + personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} diff --git a/extensions/sd-perturbed-attention/.gitignore b/extensions/sd-perturbed-attention/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e0e8c990d47b4f5a4831182bb0108349e38cbe0a --- /dev/null +++ b/extensions/sd-perturbed-attention/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +.DS_Store diff --git a/extensions/sd-perturbed-attention/LICENSE b/extensions/sd-perturbed-attention/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2f449de21776a3fd1b777ef5bc5b52faa0792ff8 --- /dev/null +++ b/extensions/sd-perturbed-attention/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 pamparamm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/sd-perturbed-attention/README.md b/extensions/sd-perturbed-attention/README.md new file mode 100644 index 0000000000000000000000000000000000000000..df502d27f9aeb66b552a4e11d5df8e9f7d79e47b --- /dev/null +++ b/extensions/sd-perturbed-attention/README.md @@ -0,0 +1,81 @@ +# Perturbed-Attention Guidance and Smoothed Energy Guidance for ComfyUI / SD WebUI (Forge/reForge) + +Implementation of [Self-Rectifying Diffusion Sampling with Perturbed-Attention Guidance (D. Ahn et al.)](https://ku-cvlab.github.io/Perturbed-Attention-Guidance/) and [Smoothed Energy Guidance: Guiding Diffusion Models with Reduced Energy Curvature of Attention (Susung Hong)](https://arxiv.org/abs/2408.00760) as an extension for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) and [SD WebUI (Forge)](https://github.com/lllyasviel/stable-diffusion-webui-forge) / [SD WebUI (reForge)](https://github.com/Panchovix/stable-diffusion-webui-reForge). + +Works with SD1.5 and SDXL. + +> [!NOTE] +> Paper and demo suggest using CFG scale 4.0 with PAG scale 3.0 applied to U-Net's middle layer 0, but feel free to experiment. +> +> Sampling speed without `adaptive_scale` or `sigma_start` / `sigma_end` is similar to Self-Attention Guidance (x0.6 of usual it/s). + +## Installation + +### ComfyUI + +You can either: + +- `git clone https://github.com/pamparamm/sd-perturbed-attention.git` into `ComfyUI/custom-nodes/` folder. + +- Install it via [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) (search for custom node named "Perturbed-Attention Guidance"). + +- Install it via [comfy-cli](https://comfydocs.org/comfy-cli/getting-started) with `comfy node registry-install sd-perturbed-attention` + +### SD WebUI (Forge/reForge) + +`git clone https://github.com/pamparamm/sd-perturbed-attention.git` into `stable-diffusion-webui-forge/extensions/` folder. + +### SD WebUI (Auto1111) + +As an alternative for A1111 WebUI you can use PAG implementation from [sd-webui-incantations](https://github.com/v0xie/sd-webui-incantations) extension. + +## Guidance Nodes/Scripts + +### ComfyUI + +![comfyui-node-pag-basic](examples/comfyui-node-pag-basic.png) + +![comfyui-node-pag-advanced](examples/comfyui-node-pag-advanced.png) + +![comfyui-node-seg](examples/comfyui-node-seg.png) + +### SD WebUI (Forge/reForge) + +![forge-pag](examples/forge-pag.png) + +![forge-seg](examples/forge-seg.png) + +> [!NOTE] +> You can override `CFG Scale` and `PAG Scale`/`SEG Scale` for Hires. fix by opening/enabling `Override for Hires. fix` tab. +> To disable PAG during Hires. fix, you can set `PAG Scale` under Override to 0. + +### Inputs + +- `scale`: Guidance scale, higher values can both increase structural coherence of an image and oversaturate/fry it entirely. +- `adaptive_scale` (PAG only): PAG dampening factor, it penalizes PAG during late denoising stages, resulting in overall speedup: 0.0 means no penalty and 1.0 completely removes PAG. +- `blur_sigma` (SEG only): Normal deviation of Gaussian blur, higher values increase "clarity" of an image. Negative values set `blur_sigma` to infinity. +- `unet_block`: Part of U-Net to which Guidance is applied, original paper suggests to use `middle`. +- `unet_block_id`: Id of U-Net layer in a selected block to which Guidance is applied. Guidance can be applied only to layers containing Self-attention blocks. +- `sigma_start` / `sigma_end`: Guidance will be active only between `sigma_start` and `sigma_end`. Set both values to negative to disable this feature. +- `rescale`: Acts similar to RescaleCFG node - it prevents over-exposure on high `scale` values. Based on Algorithm 2 from [Common Diffusion Noise Schedules and Sample Steps are Flawed (Lin et al.)](https://arxiv.org/abs/2305.08891). Set to 0 to disable this feature. +- `rescale_mode`: + - `full` - takes into account both CFG and Guidance. + - `partial` - depends only on Guidance. + - `snf` - Saliency-adaptive Noise Fusion from [High-fidelity Person-centric Subject-to-Image Synthesis (Wang et al.)](https://arxiv.org/abs/2311.10329). Should increase image quality on high guidance scales. Ignores `rescale` value. +- `unet_block_list`: Optional input, replaces both `unet_block` and `unet_block_id` and allows you to select multiple U-Net layers separated with commas. SDXL U-Net has multiple indices for layers, you can specify them by using dot symbol (if not specified, Guidance will be applied to the whole layer). Example value: `m0,u0.4` (it applies Guidance to middle block 0 and to output block 0 with index 4) + - In terms of U-Net `d` means `input`, `m` means `middle` and `u` means `output`. + - SD1.5 U-Net has layers `d0`-`d5`, `m0`, `u0`-`u8`. + - SDXL U-Net has layers `d0`-`d3`, `m0`, `u0`-`u5`. In addition, each block except `d0` and `d1` has `0-9` index values (like `m0.7` or `u0.4`). `d0` and `d1` have `0-1` index values. + +## ComfyUI TensorRT PAG + +To use PAG together with [ComfyUI_TensorRT](https://github.com/comfyanonymous/ComfyUI_TensorRT), you'll need to: + +0. Have 24GB of VRAM. +1. Build static/dynamic TRT engine of a desired model. +2. Build static/dynamic TRT engine of the same model with the same TRT parameters, but with fixed PAG injection in selected UNET blocks (`TensorRT Attach PAG` node). +3. Use `TensorRT Perturbed-Attention Guidance` node with two model inputs: one for base engine and one for PAG engine. + +![trt-engines](examples/trt-engines.png) + +![trt-inference](examples/trt-inference.png) diff --git a/extensions/sd-perturbed-attention/__init__.py b/extensions/sd-perturbed-attention/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..588934ca6e42e16ca3912e4ee700967d7b1e361f --- /dev/null +++ b/extensions/sd-perturbed-attention/__init__.py @@ -0,0 +1,16 @@ +from .pag_nodes import PerturbedAttention, SmoothedEnergyGuidanceAdvanced +from .pag_trt_nodes import TRTAttachPag, TRTPerturbedAttention + +NODE_CLASS_MAPPINGS = { + "PerturbedAttention": PerturbedAttention, + "SmoothedEnergyGuidanceAdvanced": SmoothedEnergyGuidanceAdvanced, + "TRTAttachPag": TRTAttachPag, + "TRTPerturbedAttention": TRTPerturbedAttention, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "PerturbedAttention": "Perturbed-Attention Guidance (Advanced)", + "SmoothedEnergyGuidanceAdvanced": "Smoothed Energy Guidance (Advanced)", + "TRTAttachPag": "TensorRT Attach PAG", + "TRTPerturbedAttention": "TensorRT Perturbed-Attention Guidance", +} diff --git a/extensions/sd-perturbed-attention/__pycache__/pag_nodes.cpython-310.pyc b/extensions/sd-perturbed-attention/__pycache__/pag_nodes.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bb9bda2ff07070cba68854e8f8eb6ce91425472 Binary files /dev/null and b/extensions/sd-perturbed-attention/__pycache__/pag_nodes.cpython-310.pyc differ diff --git a/extensions/sd-perturbed-attention/__pycache__/pag_utils.cpython-310.pyc b/extensions/sd-perturbed-attention/__pycache__/pag_utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab5c22d8306f2b91a83d657d8e906b83608d3096 Binary files /dev/null and b/extensions/sd-perturbed-attention/__pycache__/pag_utils.cpython-310.pyc differ diff --git a/extensions/sd-perturbed-attention/examples/comfyui-node-pag-advanced.png b/extensions/sd-perturbed-attention/examples/comfyui-node-pag-advanced.png new file mode 100644 index 0000000000000000000000000000000000000000..9565fa804e4f6aad926d372377024c821bd08705 Binary files /dev/null and b/extensions/sd-perturbed-attention/examples/comfyui-node-pag-advanced.png differ diff --git a/extensions/sd-perturbed-attention/examples/comfyui-node-pag-basic.png b/extensions/sd-perturbed-attention/examples/comfyui-node-pag-basic.png new file mode 100644 index 0000000000000000000000000000000000000000..df7661fd425361d89d7d298718af05391d892b84 Binary files /dev/null and b/extensions/sd-perturbed-attention/examples/comfyui-node-pag-basic.png differ diff --git a/extensions/sd-perturbed-attention/examples/comfyui-node-seg.png b/extensions/sd-perturbed-attention/examples/comfyui-node-seg.png new file mode 100644 index 0000000000000000000000000000000000000000..80993eb32e1986f30c37778b24ecc8f0e356db7f Binary files /dev/null and b/extensions/sd-perturbed-attention/examples/comfyui-node-seg.png differ diff --git a/extensions/sd-perturbed-attention/examples/forge-pag.png b/extensions/sd-perturbed-attention/examples/forge-pag.png new file mode 100644 index 0000000000000000000000000000000000000000..36684b190faf1887f684fa6665d99da8725b5653 Binary files /dev/null and b/extensions/sd-perturbed-attention/examples/forge-pag.png differ diff --git a/extensions/sd-perturbed-attention/examples/forge-seg.png b/extensions/sd-perturbed-attention/examples/forge-seg.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe7784add64abe999cea00d0fc8e295714be76d Binary files /dev/null and b/extensions/sd-perturbed-attention/examples/forge-seg.png differ diff --git a/extensions/sd-perturbed-attention/examples/pag-adaptive-comparison.json b/extensions/sd-perturbed-attention/examples/pag-adaptive-comparison.json new file mode 100644 index 0000000000000000000000000000000000000000..f5555bf95cfccc08d247642a71397b76d3334e65 --- /dev/null +++ b/extensions/sd-perturbed-attention/examples/pag-adaptive-comparison.json @@ -0,0 +1,662 @@ +{ + "last_node_id": 13, + "last_link_id": 21, + "nodes": [ + { + "id": 6, + "type": "VAEDecode", + "pos": [ + -1800, + -370 + ], + "size": { + "0": 210, + "1": 46 + }, + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 5 + }, + { + "name": "vae", + "type": "VAE", + "link": 6 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 9 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "id": 7, + "type": "PreviewImage", + "pos": [ + -1490, + -660 + ], + "size": [ + 482.9536868704108, + 556.4499833503875 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 9 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 13, + "type": "PreviewImage", + "pos": [ + -986, + -659 + ], + "size": { + "0": 482.95367431640625, + "1": 556.4500122070312 + }, + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 17 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 11, + "type": "PerturbedAttention", + "pos": [ + -864, + -69 + ], + "size": { + "0": 315, + "1": 130 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 20 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 14 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "PerturbedAttention" + }, + "widgets_values": [ + 3, + 0, + "middle", + 0 + ] + }, + { + "id": 9, + "type": "PerturbedAttention", + "pos": [ + -1418, + -62 + ], + "size": { + "0": 315, + "1": 130 + }, + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 11 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 13 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "PerturbedAttention" + }, + "widgets_values": [ + 3, + 1, + "middle", + 0 + ] + }, + { + "id": 10, + "type": "KSampler", + "pos": [ + -873, + 112 + ], + "size": { + "0": 315, + "1": 474 + }, + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 14 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 15 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 16 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 21, + "slot_index": 3 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 18 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 0, + "fixed", + 20, + 4, + "dpmpp_2m_sde", + "karras", + 1 + ] + }, + { + "id": 12, + "type": "VAEDecode", + "pos": [ + -518, + -63 + ], + "size": { + "0": 210, + "1": 46 + }, + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 18 + }, + { + "name": "vae", + "type": "VAE", + "link": 19 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 17 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "id": 3, + "type": "KSampler", + "pos": [ + -1414, + 108 + ], + "size": { + "0": 315, + "1": 474 + }, + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 13 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 3 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 4 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 10, + "slot_index": 3 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 5 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 0, + "fixed", + 20, + 4, + "dpmpp_2m_sde", + "karras", + 1 + ] + }, + { + "id": 8, + "type": "EmptyLatentImage", + "pos": [ + -2024, + 330 + ], + "size": { + "0": 315, + "1": 106 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 10, + 21 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "EmptyLatentImage" + }, + "widgets_values": [ + 1024, + 1024, + 1 + ] + }, + { + "id": 5, + "type": "CLIPTextEncode", + "pos": [ + -2074, + 91 + ], + "size": { + "0": 400, + "1": 200 + }, + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 8 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 4, + 16 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 4, + "type": "CLIPTextEncode", + "pos": [ + -2099, + -152 + ], + "size": { + "0": 400, + "1": 200 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 7 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 3, + 15 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "a happy monkey" + ] + }, + { + "id": 2, + "type": "CheckpointLoaderSimple", + "pos": [ + -2251, + -370 + ], + "size": { + "0": 315, + "1": 98 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 11, + 20 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 7, + 8 + ], + "shape": 3, + "slot_index": 1 + }, + { + "name": "VAE", + "type": "VAE", + "links": [ + 6, + 19 + ], + "shape": 3, + "slot_index": 2 + } + ], + "properties": { + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": [ + "base/sd_xl_base_1.0.safetensors" + ] + } + ], + "links": [ + [ + 3, + 4, + 0, + 3, + 1, + "CONDITIONING" + ], + [ + 4, + 5, + 0, + 3, + 2, + "CONDITIONING" + ], + [ + 5, + 3, + 0, + 6, + 0, + "LATENT" + ], + [ + 6, + 2, + 2, + 6, + 1, + "VAE" + ], + [ + 7, + 2, + 1, + 4, + 0, + "CLIP" + ], + [ + 8, + 2, + 1, + 5, + 0, + "CLIP" + ], + [ + 9, + 6, + 0, + 7, + 0, + "IMAGE" + ], + [ + 10, + 8, + 0, + 3, + 3, + "LATENT" + ], + [ + 11, + 2, + 0, + 9, + 0, + "MODEL" + ], + [ + 13, + 9, + 0, + 3, + 0, + "MODEL" + ], + [ + 14, + 11, + 0, + 10, + 0, + "MODEL" + ], + [ + 15, + 4, + 0, + 10, + 1, + "CONDITIONING" + ], + [ + 16, + 5, + 0, + 10, + 2, + "CONDITIONING" + ], + [ + 17, + 12, + 0, + 13, + 0, + "IMAGE" + ], + [ + 18, + 10, + 0, + 12, + 0, + "LATENT" + ], + [ + 19, + 2, + 2, + 12, + 1, + "VAE" + ], + [ + 20, + 2, + 0, + 11, + 0, + "MODEL" + ], + [ + 21, + 8, + 0, + 10, + 3, + "LATENT" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file diff --git a/extensions/sd-perturbed-attention/examples/trt-engines.png b/extensions/sd-perturbed-attention/examples/trt-engines.png new file mode 100644 index 0000000000000000000000000000000000000000..d3314112caeb3d88790db58cd5b353b0165e5cf8 Binary files /dev/null and b/extensions/sd-perturbed-attention/examples/trt-engines.png differ diff --git a/extensions/sd-perturbed-attention/examples/trt-inference.png b/extensions/sd-perturbed-attention/examples/trt-inference.png new file mode 100644 index 0000000000000000000000000000000000000000..6b73da569f6c231edb307c1136ba80f2c4d70efa Binary files /dev/null and b/extensions/sd-perturbed-attention/examples/trt-inference.png differ diff --git a/extensions/sd-perturbed-attention/pag_nodes.py b/extensions/sd-perturbed-attention/pag_nodes.py new file mode 100644 index 0000000000000000000000000000000000000000..9ec53d2d351d8a9164208e78466865f48ae4df3d --- /dev/null +++ b/extensions/sd-perturbed-attention/pag_nodes.py @@ -0,0 +1,221 @@ +BACKEND = None + +try: + from comfy.model_patcher import ModelPatcher + from comfy.samplers import calc_cond_batch + from comfy.ldm.modules.attention import optimized_attention + from .pag_utils import ( + parse_unet_blocks, + perturbed_attention, + rescale_guidance, + seg_attention_wrapper, + snf_guidance, + ) + + try: + from comfy.model_patcher import set_model_options_patch_replace + except ImportError: + from .pag_utils import set_model_options_patch_replace + + BACKEND = "ComfyUI" +except ImportError: + from pag_utils import ( + parse_unet_blocks, + set_model_options_patch_replace, + perturbed_attention, + rescale_guidance, + seg_attention_wrapper, + snf_guidance, + ) + + try: + from ldm_patched.modules.model_patcher import ModelPatcher + from ldm_patched.modules.samplers import calc_cond_uncond_batch + from ldm_patched.ldm.modules.attention import optimized_attention + + BACKEND = "reForge" + except ImportError: + from backend.patcher.base import ModelPatcher + from backend.sampling.sampling_function import calc_cond_uncond_batch + from backend.attention import attention_function as optimized_attention + + BACKEND = "Forge" + + +class PerturbedAttention: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "scale": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}), + "adaptive_scale": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, "round": 0.0001}), + "unet_block": (["input", "middle", "output"], {"default": "middle"}), + "unet_block_id": ("INT", {"default": 0}), + "sigma_start": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 10000.0, "step": 0.01, "round": False}), + "sigma_end": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 10000.0, "step": 0.01, "round": False}), + "rescale": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "rescale_mode": (["full", "partial", "snf"], {"default": "full"}), + }, + "optional": { + "unet_block_list": ("STRING", {"default": ""}), + }, + } + + RETURN_TYPES = ("MODEL",) + FUNCTION = "patch" + + CATEGORY = "model_patches/unet" + + def patch( + self, + model: ModelPatcher, + scale: float = 3.0, + adaptive_scale: float = 0.0, + unet_block: str = "middle", + unet_block_id: int = 0, + sigma_start: float = -1.0, + sigma_end: float = -1.0, + rescale: float = 0.0, + rescale_mode: str = "full", + unet_block_list: str = "", + ): + m = model.clone() + + sigma_start = float("inf") if sigma_start < 0 else sigma_start + if unet_block_list: + blocks = parse_unet_blocks(model, unet_block_list) + else: + blocks = [(unet_block, unet_block_id, None)] + + def post_cfg_function(args): + """CFG+PAG""" + model = args["model"] + cond_pred = args["cond_denoised"] + uncond_pred = args["uncond_denoised"] + cond = args["cond"] + cfg_result = args["denoised"] + sigma = args["sigma"] + model_options = args["model_options"].copy() + x = args["input"] + + signal_scale = scale + if adaptive_scale > 0: + t = model.model_sampling.timestep(sigma)[0].item() + signal_scale -= scale * (adaptive_scale**4) * (1000 - t) + if signal_scale < 0: + signal_scale = 0 + + if signal_scale == 0 or not (sigma_end < sigma[0] <= sigma_start): + return cfg_result + + # Replace Self-attention with PAG + for block in blocks: + layer, number, index = block + model_options = set_model_options_patch_replace(model_options, perturbed_attention, "attn1", layer, number, index) + + if BACKEND == "ComfyUI": + (pag_cond_pred,) = calc_cond_batch(model, [cond], x, sigma, model_options) + if BACKEND in {"Forge", "reForge"}: + (pag_cond_pred, _) = calc_cond_uncond_batch(model, cond, None, x, sigma, model_options) + + pag = (cond_pred - pag_cond_pred) * signal_scale + + if rescale_mode == "snf": + if uncond_pred.any(): + return uncond_pred + snf_guidance(cfg_result - uncond_pred, pag) + return cfg_result + pag + + return cfg_result + rescale_guidance(pag, cond_pred, cfg_result, rescale, rescale_mode) + + m.set_model_sampler_post_cfg_function(post_cfg_function, rescale_mode == "snf") + + return (m,) + + +class SmoothedEnergyGuidanceAdvanced: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "scale": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}), + "blur_sigma": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 9999.0, "step": 0.01, "round": 0.001}), + "unet_block": (["input", "middle", "output"], {"default": "middle"}), + "unet_block_id": ("INT", {"default": 0}), + "sigma_start": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 10000.0, "step": 0.01, "round": False}), + "sigma_end": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 10000.0, "step": 0.01, "round": False}), + "rescale": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "rescale_mode": (["full", "partial", "snf"], {"default": "full"}), + }, + "optional": { + "unet_block_list": ("STRING", {"default": ""}), + }, + } + + RETURN_TYPES = ("MODEL",) + FUNCTION = "patch" + + CATEGORY = "model_patches/unet" + + def patch( + self, + model: ModelPatcher, + scale: float = 3.0, + blur_sigma: float = -1.0, + unet_block: str = "middle", + unet_block_id: int = 0, + sigma_start: float = -1.0, + sigma_end: float = -1.0, + rescale: float = 0.0, + rescale_mode: str = "full", + unet_block_list: str = "", + ): + m = model.clone() + + sigma_start = float("inf") if sigma_start < 0 else sigma_start + if unet_block_list: + blocks = parse_unet_blocks(model, unet_block_list) + else: + blocks = [(unet_block, unet_block_id, None)] + + def post_cfg_function(args): + """CFG+SEG""" + model = args["model"] + cond_pred = args["cond_denoised"] + uncond_pred = args["uncond_denoised"] + cond = args["cond"] + cfg_result = args["denoised"] + sigma = args["sigma"] + model_options = args["model_options"].copy() + x = args["input"] + + signal_scale = scale + + if signal_scale == 0 or not (sigma_end < sigma[0] <= sigma_start): + return cfg_result + + seg_attention = seg_attention_wrapper(optimized_attention, blur_sigma) + + # Replace Self-attention with SEG attention + for block in blocks: + layer, number, index = block + model_options = set_model_options_patch_replace(model_options, seg_attention, "attn1", layer, number, index) + + if BACKEND == "ComfyUI": + (seg_cond_pred,) = calc_cond_batch(model, [cond], x, sigma, model_options) + if BACKEND in {"Forge", "reForge"}: + (seg_cond_pred, _) = calc_cond_uncond_batch(model, cond, None, x, sigma, model_options) + + seg = (cond_pred - seg_cond_pred) * signal_scale + + if rescale_mode == "snf": + if uncond_pred.any(): + return uncond_pred + snf_guidance(cfg_result - uncond_pred, seg) + return cfg_result + seg + + return cfg_result + rescale_guidance(seg, cond_pred, cfg_result, rescale, rescale_mode) + + m.set_model_sampler_post_cfg_function(post_cfg_function, rescale_mode == "snf") + + return (m,) diff --git a/extensions/sd-perturbed-attention/pag_trt_nodes.py b/extensions/sd-perturbed-attention/pag_trt_nodes.py new file mode 100644 index 0000000000000000000000000000000000000000..dfb5ee4f837c7dc03d92bda0da6662bf51e54ec7 --- /dev/null +++ b/extensions/sd-perturbed-attention/pag_trt_nodes.py @@ -0,0 +1,110 @@ +from comfy.model_patcher import ModelPatcher +from comfy.samplers import calc_cond_batch +from .pag_utils import parse_unet_blocks, perturbed_attention, rescale_guidance + + +class TRTAttachPag: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "unet_block": (["input", "middle", "output"], {"default": "middle"}), + "unet_block_id": ("INT", {"default": 0}), + }, + "optional": { + "unet_block_list": ("STRING", {"default": ""}), + }, + } + + RETURN_TYPES = ("MODEL",) + FUNCTION = "attach" + + CATEGORY = "TensorRT" + + def attach( + self, + model: ModelPatcher, + unet_block: str = "middle", + unet_block_id: int = 0, + unet_block_list: str = "", + ): + m = model.clone() + + if unet_block_list: + blocks = parse_unet_blocks(model, unet_block_list) + else: + blocks = [(unet_block, unet_block_id, None)] + + # Replace Self-attention with PAG + for block in blocks: + layer, number, index = block + m.set_model_attn1_replace(perturbed_attention, layer, number, index) + + return (m,) + + +class TRTPerturbedAttention: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model_base": ("MODEL",), + "model_pag": ("MODEL",), + "scale": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}), + "adaptive_scale": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, "round": 0.0001}), + "sigma_start": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 10000.0, "step": 0.01, "round": False}), + "sigma_end": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 10000.0, "step": 0.01, "round": False}), + "rescale": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "rescale_mode": (["full", "partial"], {"default": "full"}), + }, + } + + RETURN_TYPES = ("MODEL",) + FUNCTION = "patch" + + CATEGORY = "TensorRT" + + def patch( + self, + model_base: ModelPatcher, + model_pag: ModelPatcher, + scale: float = 3.0, + adaptive_scale: float = 0.0, + sigma_start: float = -1.0, + sigma_end: float = -1.0, + rescale: float = 0.0, + rescale_mode: str = "full", + ): + m = model_base.clone() + + sigma_start = float("inf") if sigma_start < 0 else sigma_start + + def post_cfg_function(args): + """CFG+PAG""" + model = args["model"] + cond_pred = args["cond_denoised"] + cond = args["cond"] + cfg_result = args["denoised"] + sigma = args["sigma"] + x = args["input"] + + signal_scale = scale + if adaptive_scale > 0: + t = model.model_sampling.timestep(sigma)[0].item() + signal_scale -= scale * (adaptive_scale**4) * (1000 - t) + if signal_scale < 0: + signal_scale = 0 + + if signal_scale == 0 or not (sigma_end < sigma[0] <= sigma_start): + return cfg_result + + (pag_cond_pred,) = calc_cond_batch(model_pag.model, [cond], x, sigma, model_pag.model_options) + + pag = (cond_pred - pag_cond_pred) * signal_scale + + return cfg_result + rescale_guidance(pag, cond_pred, cfg_result, rescale, rescale_mode) + + m.set_model_sampler_post_cfg_function(post_cfg_function) + + return (m,) diff --git a/extensions/sd-perturbed-attention/pag_utils.py b/extensions/sd-perturbed-attention/pag_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..4f4c2297c5e65d4f0deebbb946e8358cbd5c75a9 --- /dev/null +++ b/extensions/sd-perturbed-attention/pag_utils.py @@ -0,0 +1,168 @@ +import math +import torch +from torch import Tensor +import torch.nn.functional as F +from itertools import groupby + + +def parse_unet_blocks(model, unet_block_list: str): + output: list[tuple[str, int, int | None]] = [] + + # Get all Self-attention blocks + input_blocks, middle_blocks, output_blocks = [], [], [] + for name, module in model.model.diffusion_model.named_modules(): + if module.__class__.__name__ == "CrossAttention" and name.endswith("attn1"): + parts = name.split(".") + block_name = parts[0] + block_id = int(parts[1]) + if block_name.startswith("input"): + input_blocks.append(block_id) + elif block_name.startswith("middle"): + middle_blocks.append(block_id - 1) + elif block_name.startswith("output"): + output_blocks.append(block_id) + + def group_blocks(blocks: list[int]): + return [(i, len(list(gr))) for i, gr in groupby(blocks)] + + input_blocks, middle_blocks, output_blocks = group_blocks(input_blocks), group_blocks(middle_blocks), group_blocks(output_blocks) + + unet_blocks = [b.strip() for b in unet_block_list.split(",")] + for block in unet_blocks: + name, indices = block[0], block[1:].split(".") + match name: + case "d": + layer, cur_blocks = "input", input_blocks + case "m": + layer, cur_blocks = "middle", middle_blocks + case "u": + layer, cur_blocks = "output", output_blocks + if len(indices) >= 2: + number, index = cur_blocks[int(indices[0])][0], int(indices[1]) + assert 0 <= index < cur_blocks[int(indices[0])][1] + else: + number, index = cur_blocks[int(indices[0])][0], None + output.append((layer, number, index)) + + return output + + +# Copied from https://github.com/comfyanonymous/ComfyUI/blob/719fb2c81d716ce8edd7f1bdc7804ae160a71d3a/comfy/model_patcher.py#L21 for backward compatibility +def set_model_options_patch_replace(model_options, patch, name, block_name, number, transformer_index=None): + to = model_options["transformer_options"].copy() + + if "patches_replace" not in to: + to["patches_replace"] = {} + else: + to["patches_replace"] = to["patches_replace"].copy() + + if name not in to["patches_replace"]: + to["patches_replace"][name] = {} + else: + to["patches_replace"][name] = to["patches_replace"][name].copy() + + if transformer_index is not None: + block = (block_name, number, transformer_index) + else: + block = (block_name, number) + to["patches_replace"][name][block] = patch + model_options["transformer_options"] = to + return model_options + + +def perturbed_attention(q: Tensor, k: Tensor, v: Tensor, extra_options, mask=None): + """Perturbed self-attention""" + return v + + +# Modified 'Algorithm 2 Classifier-Free Guidance with Rescale' from Common Diffusion Noise Schedules and Sample Steps are Flawed (Lin et al.). +def rescale_guidance(guidance: torch.Tensor, cond_pred: torch.Tensor, cfg_result: torch.Tensor, rescale=0.0, rescale_mode="full"): + if rescale == 0.0: + return guidance + + match rescale_mode: + case "full": + guidance_result = cfg_result + guidance + case _: + guidance_result = cond_pred + guidance + + std_cond = torch.std(cond_pred, dim=(1, 2, 3), keepdim=True) + std_guidance = torch.std(guidance_result, dim=(1, 2, 3), keepdim=True) + + factor = std_cond / std_guidance + factor = rescale * factor + (1.0 - rescale) + + return guidance * factor + + +# Gaussian blur +def gaussian_blur_2d(img, kernel_size, sigma): + height = img.shape[-1] + kernel_size = min(kernel_size, height - (height % 2 - 1)) + ksize_half = (kernel_size - 1) * 0.5 + + x = torch.linspace(-ksize_half, ksize_half, steps=kernel_size) + + pdf = torch.exp(-0.5 * (x / sigma).pow(2)) + + x_kernel = pdf / pdf.sum() + x_kernel = x_kernel.to(device=img.device, dtype=img.dtype) + + kernel2d = torch.mm(x_kernel[:, None], x_kernel[None, :]) + kernel2d = kernel2d.expand(img.shape[-3], 1, kernel2d.shape[0], kernel2d.shape[1]) + + padding = [kernel_size // 2, kernel_size // 2, kernel_size // 2, kernel_size // 2] + + img = F.pad(img, padding, mode="reflect") + img = F.conv2d(img, kernel2d, groups=img.shape[-3]) + + return img + + +def seg_attention_wrapper(attention, blur_sigma=1.0): + + def seg_attention(q: Tensor, k: Tensor, v: Tensor, extra_options, mask=None): + """Smoothed Energy Guidance self-attention""" + heads = extra_options["n_heads"] + bs, area, inner_dim = q.shape + + height_orig, width_orig = extra_options["original_shape"][2:4] + aspect_ratio = width_orig / height_orig + + if aspect_ratio >= 1.0: + height = round((area / aspect_ratio)**0.5) + q = q.permute(0, 2, 1).reshape(bs, inner_dim, height, -1) + else: + width = round((area * aspect_ratio)**0.5) + q = q.permute(0, 2, 1).reshape(bs, inner_dim, -1, width) + + if blur_sigma >= 0: + kernel_size = math.ceil(6 * blur_sigma) + 1 - math.ceil(6 * blur_sigma) % 2 + q = gaussian_blur_2d(q, kernel_size, blur_sigma) + else: + q[:] = q.mean(dim=(-2, -1), keepdim=True) + + q = q.reshape(bs, inner_dim, -1).permute(0, 2, 1) + + return attention(q, k, v, heads=heads) + + return seg_attention + + +# Saliency-adaptive Noise Fusion based on High-fidelity Person-centric Subject-to-Image Synthesis (Wang et al.) +# https://github.com/CodeGoat24/Face-diffuser/blob/edff1a5178ac9984879d9f5e542c1d0f0059ca5f/facediffuser/pipeline.py#L535-L562 +def snf_guidance(t_guidance: torch.Tensor, s_guidance: torch.Tensor): + b, c, h, w = t_guidance.shape + + t_omega = gaussian_blur_2d(torch.abs(t_guidance), 3, 1) + s_omega = gaussian_blur_2d(torch.abs(s_guidance), 3, 1) + t_softmax = torch.softmax(t_omega.reshape(b * c, h * w), dim=1).reshape(b, c, h, w) + s_softmax = torch.softmax(s_omega.reshape(b * c, h * w), dim=1).reshape(b, c, h, w) + + guidance_stacked = torch.stack([t_guidance, s_guidance], dim=0) + ts_softmax = torch.stack([t_softmax, s_softmax], dim=0) + + argeps = torch.argmax(ts_softmax, dim=0, keepdim=True) + + snf = torch.gather(guidance_stacked, dim=0, index=argeps).squeeze(0) + return snf diff --git a/extensions/sd-perturbed-attention/pyproject.toml b/extensions/sd-perturbed-attention/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..433331841a46c625f7c3aa0aa81b4605113cc008 --- /dev/null +++ b/extensions/sd-perturbed-attention/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "sd-perturbed-attention" +description = "Perturbed-Attention Guidance and Smoothed Energy Guidance for ComfyUI and SD Forge/reForge." +version = "1.1.5" +license = { text = "MIT License" } + +[project.urls] +Repository = "https://github.com/pamparamm/sd-perturbed-attention" +# Used by Comfy Registry https://comfyregistry.org + +[tool.comfy] +PublisherId = "pamparamm" +DisplayName = "sd-perturbed-attention" +Icon = "" diff --git a/extensions/sd-perturbed-attention/scripts/__pycache__/pag.cpython-310.pyc b/extensions/sd-perturbed-attention/scripts/__pycache__/pag.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90bf46b75a03a8d3ad775137a16697bbc9dc6f60 Binary files /dev/null and b/extensions/sd-perturbed-attention/scripts/__pycache__/pag.cpython-310.pyc differ diff --git a/extensions/sd-perturbed-attention/scripts/__pycache__/seg.cpython-310.pyc b/extensions/sd-perturbed-attention/scripts/__pycache__/seg.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f375f478224008d35cb8d80aa8a4297a4e53b370 Binary files /dev/null and b/extensions/sd-perturbed-attention/scripts/__pycache__/seg.cpython-310.pyc differ diff --git a/extensions/sd-perturbed-attention/scripts/pag.py b/extensions/sd-perturbed-attention/scripts/pag.py new file mode 100644 index 0000000000000000000000000000000000000000..2414ae4b34bb12d4d1fbb6ea7418f06065f05595 --- /dev/null +++ b/extensions/sd-perturbed-attention/scripts/pag.py @@ -0,0 +1,164 @@ +try: + import pag_nodes + + if pag_nodes.BACKEND in {"Forge", "reForge"}: + import gradio as gr + + from modules import scripts + from modules.ui_components import InputAccordion + + opPerturbedAttention = pag_nodes.PerturbedAttention() + + class PerturbedAttentionScript(scripts.Script): + def title(self): + return "Perturbed-Attention Guidance" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, *args, **kwargs): + with gr.Accordion(open=False, label=self.title()): + enabled = gr.Checkbox(label="Enabled", value=False) + scale = gr.Slider(label="PAG Scale", minimum=0.0, maximum=30.0, step=0.01, value=3.0) + with gr.Row(): + rescale_pag = gr.Slider(label="Rescale PAG", minimum=0.0, maximum=1.0, step=0.01, value=0.0) + rescale_mode = gr.Dropdown(choices=["full", "partial", "snf"], value="full", label="Rescale Mode") + adaptive_scale = gr.Slider(label="Adaptive Scale", minimum=0.0, maximum=1.0, step=0.001, value=0.0) + with InputAccordion(False, label="Override for Hires. fix") as hr_override: + hr_cfg = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label="CFG Scale", value=7.0) + hr_scale = gr.Slider(label="PAG Scale", minimum=0.0, maximum=30.0, step=0.01, value=3.0) + with gr.Row(): + hr_rescale_pag = gr.Slider(label="Rescale PAG", minimum=0.0, maximum=1.0, step=0.01, value=0.0) + hr_rescale_mode = gr.Dropdown(choices=["full", "partial", "snf"], value="full", label="Rescale Mode") + hr_adaptive_scale = gr.Slider(label="Adaptive Scale", minimum=0.0, maximum=1.0, step=0.001, value=0.0) + with gr.Row(): + block = gr.Dropdown(choices=["input", "middle", "output"], value="middle", label="U-Net Block") + block_id = gr.Number(label="U-Net Block Id", value=0, precision=0, minimum=0) + block_list = gr.Text(label="U-Net Block List") + with gr.Row(): + sigma_start = gr.Number(minimum=-1.0, label="Sigma Start", value=-1.0) + sigma_end = gr.Number(minimum=-1.0, label="Sigma End", value=-1.0) + + self.infotext_fields = ( + (enabled, lambda p: gr.Checkbox.update(value="pag_enabled" in p)), + (scale, "pag_scale"), + (rescale_pag, "pag_rescale"), + (rescale_mode, lambda p: gr.Dropdown.update(value=p.get("pag_rescale_mode", "full"))), + (adaptive_scale, "pag_adaptive_scale"), + (hr_override, lambda p: gr.Checkbox.update(value="hr_override" in p)), + (hr_cfg, "pag_hr_cfg"), + (hr_scale, "pag_hr_scale"), + (hr_rescale_pag, "pag_hr_rescale"), + (hr_rescale_mode, lambda p: gr.Dropdown.update(value=p.get("pag_hr_rescale_mode", "full"))), + (hr_adaptive_scale, "pag_hr_adaptive_scale"), + (block, lambda p: gr.Dropdown.update(value=p.get("pag_block", "middle"))), + (block_id, "pag_block_id"), + (block_list, lambda p: gr.Text.update(value=p.get("pag_block_list", ""))), + (sigma_start, "pag_sigma_start"), + (sigma_end, "pag_sigma_end"), + ) + + return enabled, scale, rescale_pag, rescale_mode, adaptive_scale, block, block_id, block_list, hr_override, hr_cfg, hr_scale, hr_rescale_pag, hr_rescale_mode, hr_adaptive_scale, sigma_start, sigma_end + + def process_before_every_sampling(self, p, *script_args, **kwargs): + ( + enabled, + scale, + rescale_pag, + rescale_mode, + adaptive_scale, + block, + block_id, + block_list, + hr_override, + hr_cfg, + hr_scale, + hr_rescale_pag, + hr_rescale_mode, + hr_adaptive_scale, + sigma_start, + sigma_end, + ) = script_args + + if not enabled: + return + + unet = p.sd_model.forge_objects.unet + + hr_enabled = getattr(p, "enable_hr", False) + + if hr_enabled and p.is_hr_pass and hr_override: + p.cfg_scale_before_hr = p.cfg_scale + p.cfg_scale = hr_cfg + unet = opPerturbedAttention.patch(unet, hr_scale, hr_adaptive_scale, block, block_id, sigma_start, sigma_end, hr_rescale_pag, hr_rescale_mode, block_list)[0] + else: + unet = opPerturbedAttention.patch(unet, scale, adaptive_scale, block, block_id, sigma_start, sigma_end, rescale_pag, rescale_mode, block_list)[0] + + p.sd_model.forge_objects.unet = unet + + p.extra_generation_params.update( + dict( + pag_enabled=enabled, + pag_scale=scale, + pag_rescale=rescale_pag, + pag_rescale_mode=rescale_mode, + pag_adaptive_scale=adaptive_scale, + pag_block=block, + pag_block_id=block_id, + pag_block_list=block_list, + ) + ) + if hr_enabled: + p.extra_generation_params["pag_hr_override"] = hr_override + if hr_override: + p.extra_generation_params.update( + dict( + pag_hr_cfg=hr_cfg, + pag_hr_scale=hr_scale, + pag_hr_rescale=hr_rescale_pag, + pag_hr_rescale_mode=hr_rescale_mode, + pag_hr_adaptive_scale=hr_adaptive_scale, + ) + ) + if sigma_start >= 0 or sigma_end >= 0: + p.extra_generation_params.update( + dict( + pag_sigma_start=sigma_start, + pag_sigma_end=sigma_end, + ) + ) + + return + + def post_sample(self, p, ps, *script_args): + ( + enabled, + scale, + rescale_pag, + rescale_mode, + adaptive_scale, + block, + block_id, + block_list, + hr_override, + hr_cfg, + hr_scale, + hr_rescale_pag, + hr_rescale_mode, + hr_adaptive_scale, + sigma_start, + sigma_end, + ) = script_args + + if not enabled: + return + + hr_enabled = getattr(p, "enable_hr", False) + + if hr_enabled and hr_override: + p.cfg_scale = p.cfg_scale_before_hr + + return + +except ImportError: + pass diff --git a/extensions/sd-perturbed-attention/scripts/seg.py b/extensions/sd-perturbed-attention/scripts/seg.py new file mode 100644 index 0000000000000000000000000000000000000000..87d18a03d549cbe2f27024c42a3e4c525b3e69a9 --- /dev/null +++ b/extensions/sd-perturbed-attention/scripts/seg.py @@ -0,0 +1,164 @@ +try: + import pag_nodes + + if pag_nodes.BACKEND in {"Forge", "reForge"}: + import gradio as gr + + from modules import scripts + from modules.ui_components import InputAccordion + + opSEG = pag_nodes.SmoothedEnergyGuidanceAdvanced() + + class SmoothedEnergyGuidanceScript(scripts.Script): + def title(self): + return "Smoothed Energy Guidance" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, *args, **kwargs): + with gr.Accordion(open=False, label=self.title()): + enabled = gr.Checkbox(label="Enabled", value=False) + scale = gr.Slider(label="SEG Scale", minimum=0.0, maximum=30.0, step=0.01, value=3.0) + with gr.Row(): + rescale_seg = gr.Slider(label="Rescale SEG", minimum=0.0, maximum=1.0, step=0.01, value=0.0) + rescale_mode = gr.Dropdown(choices=["full", "partial", "snf"], value="full", label="Rescale Mode") + blur_sigma = gr.Slider(label="Blur Sigma", minimum=-1.0, maximum=9999.0, step=0.01, value=-1.0) + with InputAccordion(False, label="Override for Hires. fix") as hr_override: + hr_cfg = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label="CFG Scale", value=7.0) + hr_scale = gr.Slider(label="SEG Scale", minimum=0.0, maximum=30.0, step=0.01, value=3.0) + with gr.Row(): + hr_rescale_seg = gr.Slider(label="Rescale SEG", minimum=0.0, maximum=1.0, step=0.01, value=0.0) + hr_rescale_mode = gr.Dropdown(choices=["full", "partial", "snf"], value="full", label="Rescale Mode") + hr_blur_sigma = gr.Slider(label="Blur Sigma", minimum=-1.0, maximum=9999.0, step=0.01, value=-1.0) + with gr.Row(): + block = gr.Dropdown(choices=["input", "middle", "output"], value="middle", label="U-Net Block") + block_id = gr.Number(label="U-Net Block Id", value=0, precision=0, minimum=0) + block_list = gr.Text(label="U-Net Block List") + with gr.Row(): + sigma_start = gr.Number(minimum=-1.0, label="Sigma Start", value=-1.0) + sigma_end = gr.Number(minimum=-1.0, label="Sigma End", value=-1.0) + + self.infotext_fields = ( + (enabled, lambda p: gr.Checkbox.update(value="seg_enabled" in p)), + (scale, "seg_scale"), + (rescale_seg, "seg_rescale"), + (rescale_mode, lambda p: gr.Dropdown.update(value=p.get("seg_rescale_mode", "full"))), + (blur_sigma, "seg_blur_sigma"), + (hr_override, lambda p: gr.Checkbox.update(value="hr_override" in p)), + (hr_cfg, "seg_hr_cfg"), + (hr_scale, "seg_hr_scale"), + (hr_rescale_seg, "seg_hr_rescale"), + (hr_rescale_mode, lambda p: gr.Dropdown.update(value=p.get("seg_hr_rescale_mode", "full"))), + (hr_blur_sigma, "seg_hr_blur_sigma"), + (block, lambda p: gr.Dropdown.update(value=p.get("seg_block", "middle"))), + (block_id, "seg_block_id"), + (block_list, lambda p: gr.Text.update(value=p.get("seg_block_list", ""))), + (sigma_start, "seg_sigma_start"), + (sigma_end, "seg_sigma_end"), + ) + + return enabled, scale, rescale_seg, rescale_mode, blur_sigma, block, block_id, block_list, hr_override, hr_cfg, hr_scale, hr_rescale_seg, hr_rescale_mode, hr_blur_sigma, sigma_start, sigma_end + + def process_before_every_sampling(self, p, *script_args, **kwargs): + ( + enabled, + scale, + rescale_seg, + rescale_mode, + blur_sigma, + block, + block_id, + block_list, + hr_override, + hr_cfg, + hr_scale, + hr_rescale_seg, + hr_rescale_mode, + hr_blur_sigma, + sigma_start, + sigma_end, + ) = script_args + + if not enabled: + return + + unet = p.sd_model.forge_objects.unet + + hr_enabled = getattr(p, "enable_hr", False) + + if hr_enabled and p.is_hr_pass and hr_override: + p.cfg_scale_before_hr = p.cfg_scale + p.cfg_scale = hr_cfg + unet = opSEG.patch(unet, hr_scale, hr_blur_sigma, block, block_id, sigma_start, sigma_end, hr_rescale_seg, hr_rescale_mode, block_list)[0] + else: + unet = opSEG.patch(unet, scale, blur_sigma, block, block_id, sigma_start, sigma_end, rescale_seg, rescale_mode, block_list)[0] + + p.sd_model.forge_objects.unet = unet + + p.extra_generation_params.update( + dict( + seg_enabled=enabled, + seg_scale=scale, + seg_rescale=rescale_seg, + seg_rescale_mode=rescale_mode, + seg_blur_sigma=blur_sigma, + seg_block=block, + seg_block_id=block_id, + seg_block_list=block_list, + ) + ) + if hr_enabled: + p.extra_generation_params["seg_hr_override"] = hr_override + if hr_override: + p.extra_generation_params.update( + dict( + seg_hr_cfg=hr_cfg, + seg_hr_scale=hr_scale, + seg_hr_rescale=hr_rescale_seg, + seg_hr_rescale_mode=hr_rescale_mode, + seg_hr_blur_sigma=hr_blur_sigma, + ) + ) + if sigma_start >= 0 or sigma_end >= 0: + p.extra_generation_params.update( + dict( + seg_sigma_start=sigma_start, + seg_sigma_end=sigma_end, + ) + ) + + return + + def post_sample(self, p, ps, *script_args): + ( + enabled, + scale, + rescale_seg, + rescale_mode, + blur_sigma, + block, + block_id, + block_list, + hr_override, + hr_cfg, + hr_scale, + hr_rescale_seg, + hr_rescale_mode, + hr_blur_sigma, + sigma_start, + sigma_end, + ) = script_args + + if not enabled: + return + + hr_enabled = getattr(p, "enable_hr", False) + + if hr_enabled and hr_override: + p.cfg_scale = p.cfg_scale_before_hr + + return + +except ImportError: + pass diff --git a/extensions/sd-webui-aspect-ratio-helper/.github/workflows/pytest.yml b/extensions/sd-webui-aspect-ratio-helper/.github/workflows/pytest.yml new file mode 100644 index 0000000000000000000000000000000000000000..1c57d08221beaffd96176da66384661b4c4c899a --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/.github/workflows/pytest.yml @@ -0,0 +1,31 @@ +name: Tests + +on: + pull_request: + push: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ['3.11', '3.10'] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox + + - name: Run tox + run: tox diff --git a/extensions/sd-webui-aspect-ratio-helper/.gitignore b/extensions/sd-webui-aspect-ratio-helper/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..689264bad5776046b9d205383c4983c599db144a --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/.gitignore @@ -0,0 +1,2 @@ +.idea +.pytest_cache diff --git a/extensions/sd-webui-aspect-ratio-helper/.pre-commit-config.yaml b/extensions/sd-webui-aspect-ratio-helper/.pre-commit-config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0e1a718fa24808c7f30521d606ab5251ddc2d284 --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: debug-statements + - id: double-quote-string-fixer + - id: name-tests-test + - repo: https://github.com/asottile/reorder_python_imports + rev: v3.9.0 + hooks: + - id: reorder-python-imports + args: [--py37-plus, --add-import, "from __future__ import annotations"] + - repo: https://github.com/asottile/add-trailing-comma + rev: v2.4.0 + hooks: + - id: add-trailing-comma + args: [--py36-plus] + - repo: https://github.com/asottile/pyupgrade + rev: v3.3.2 + hooks: + - id: pyupgrade + args: [--py37-plus] + - repo: https://github.com/pre-commit/mirrors-autopep8 + rev: v2.0.2 + hooks: + - id: autopep8 + - repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + args: + - --max-line-length=120 diff --git a/extensions/sd-webui-aspect-ratio-helper/README.md b/extensions/sd-webui-aspect-ratio-helper/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6236dd14fadf3e670d365aae1557562322d13123 --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/README.md @@ -0,0 +1,91 @@ +# Aspect Ratio Helper [![pytest](https://github.com/thomasasfk/sd-webui-aspect-ratio-helper/actions/workflows/pytest.yml/badge.svg?branch=main)](https://github.com/thomasasfk/sd-webui-aspect-ratio-helper/actions/workflows/pytest.yml) + +Simple extension to easily maintain aspect ratio while changing dimensions. + +Install via the extensions tab on the [AUTOMATIC1111 webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui). + +## Features + +**(note this list is a little out of date, will need to find time to update it)** + +- JavaScript aspect ratio controls + - Adds a dropdown of configurable aspect ratios, to which the dimensions will auto-scale + - When selected, you will only be able to modify the higher dimension + - The smaller or equivalent dimension will scale accordingly + - If "Lock/🔒" is selected, the aspect ratio of the current dimensions will be kept + - If "Image/🖼️" is selected, the aspect ratio of the current image will be kept (img2img only) + - If you click the "Swap/⇅" button, the current dimensions will swap + - Configurable aspect ratios will also flip, reducing the need for duplication of config + +https://user-images.githubusercontent.com/22506439/227396634-7a63671a-fd38-419a-b734-a3d26647cc1d.mp4 + +
+ +- Scale to maximum dimension + - Upon clicking, the width and height will scale according to the configured maximum value + - Aspect ratio will be retained, the smaller or equivalent dimension will be scaled to match +- Scale to aspect ratio + - Upon clicking, the current dimensions will be scaled to the given aspect ratio, using the highest width or height + - i.e `4:3 of 256x512 = 512x384` `9:16 of 512x256 = 288x512` `1:1 of 256x300 = 300x300` + - You can optionally toggle this to use the "Maximum dimension" slider value + - i.e `4:3 of 512 = 512x384` `9:16 of 512 = 288x512` `1:1 of 300 = 300x300` +- Scale by percentage + - Upon clicking, the current dimensions will be multiplied by the given percentage, with aspect ratio maintained + - i.e `-25% of 512x256 = 384x192` `+50% of 512x512 = 768x768` + - You can also change the display of these if you find it more intuitive + - i.e `75% of 512x256 = 384x192` `150% of 512x512 = 768x768` + - i.e `x0.75 of 512x256 = 384x192` `x1.5 of 512x512 = 768x768` + +![user-interface.png](docs%2Fui.png) + +## Settings + +- Hide accordion by default (`False`) +- Expand accordion by default (`False`) + - Determines whether the 'Aspect Ratio Helper' accordion expands by default +- UI Component order (`MaxDimensionScaler, PredefinedAspectRatioButtons, PredefinedPercentageButtons`) + - Determines the order in which the UI components will render +- Enable JavaScript aspect ratio controls +- JavaScript aspect ratio buttons `(1:1, 4:3, 16:9, 9:16, 21:9)` + - i.e `1:1, 4:3, 16:9, 9:16, 21:9` `2:3, 1:5, 3:5` +- Show maximum dimension button (`True`) +- Maximum dimension default (`1024`) +- Show pre-defined aspect ratio buttons (`True`) +- Use "Maximum dimension" for aspect ratio buttons (`False`) +- Pre-defined aspect ratio buttons (`1:1, 4:3, 16:9, 9:16, 21:9`) + - i.e `1:1, 4:3, 16:9, 9:16, 21:9` `2:3, 1:5, 3:5` +- Show pre-defined percentage buttons (`True`) +- Pre-defined percentage buttons (`25, 50, 75, 125, 150, 175, 200`) + - i.e `25, 50, 75, 125, 150, 175, 200` `50, 125, 300` +- Pre-defined percentage display format (`Incremental/decremental percentage (-50%, +50%)`) + - `Incremental/decremental percentage (-50%, +50%)` + - `Raw percentage (50%, 150%)` + - `Multiplication (x0.5, x1.5)` + +![settings.png](docs%2Fopts.png) + + +JavaScript & accordion aspect ratios _might_ not play well together - I don't think many users will use both simultaneously, but we'll see. + +## Contributing + +- Open an issue for suggestions +- Raise a pull request + +## Dependencies + +Developed using existing [AUTOMATIC1111 webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) dependencies. + +It's recommended to use the python version specified by A1111 webui. + +However - for running unit tests, we use pytest. + +```bash +pip install pytest +``` + +## Testing +From the root of the repository. +```bash +pytest +``` diff --git a/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_components.cpython-310.pyc b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_components.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0e45b39fa34fbe4c3adb124e7f1d45d9c9f3455 Binary files /dev/null and b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_components.cpython-310.pyc differ diff --git a/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_constants.cpython-310.pyc b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_constants.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3aa33082bf69e318e06725396aa3265ca80bb3e Binary files /dev/null and b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_constants.cpython-310.pyc differ diff --git a/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_settings.cpython-310.pyc b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_settings.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebebbd368f3ead4ba153b4402a1e6393c8e9f97a Binary files /dev/null and b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_settings.cpython-310.pyc differ diff --git a/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_util.cpython-310.pyc b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_util.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8a71a37df48fa133b52012116508a9414c7d941 Binary files /dev/null and b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/_util.cpython-310.pyc differ diff --git a/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/main.cpython-310.pyc b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/main.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ca4348bcfa7cddc2f7ac5c675a5a67d9ca18f71 Binary files /dev/null and b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/__pycache__/main.cpython-310.pyc differ diff --git a/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_components.py b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_components.py new file mode 100644 index 0000000000000000000000000000000000000000..2fae4e7f0e258024c3bfb868f88fa3ae040513dc --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_components.py @@ -0,0 +1,360 @@ +from __future__ import annotations + +from abc import ABC +from abc import abstractmethod +from functools import partial +from typing import Callable + +import gradio as gr + +import aspect_ratio_helper._constants as _constants +import aspect_ratio_helper._settings as _settings +import aspect_ratio_helper._util as _util + + +class ArhUIComponent(ABC): + + def __init__(self, script): + self.script = script + + @abstractmethod + def render(self): ... + + @staticmethod + @abstractmethod + def should_show() -> bool: ... + + @staticmethod + @abstractmethod + def add_options(): ... + + +class MaxDimensionScaler(ArhUIComponent): + + def render(self): + max_dim_default = _settings.safe_opt( + _constants.ARH_MAX_WIDTH_OR_HEIGHT_KEY, + ) + self.script.max_dimension = float(max_dim_default) + + inputs = outputs = [self.script.wc, self.script.hc] + + with gr.Row( + visible=self.should_show(), + ): + max_dim_default = _settings.safe_opt( + _constants.ARH_MAX_WIDTH_OR_HEIGHT_KEY, + ) + # todo: when using gr.Slider (not deprecated), the default value + # is somehow always 270?... can't figure out why. + # using legacy inputs.Slider for now as it doesn't have the issue. + max_dimension_slider = gr.inputs.Slider( + minimum=_constants.MIN_DIMENSION, + maximum=_constants.MAX_DIMENSION, + step=8, + default=max_dim_default, + label='Maximum dimension', + ) + + def _update_max_dimension(_max_dimension): + self.script.max_dimension = _max_dimension + + max_dimension_slider.change( + _update_max_dimension, + inputs=[max_dimension_slider], + show_progress=False, + ) + + gr.Button( + value='Scale to maximum dimension', + visible=self.should_show(), + ).click( + fn=_util.scale_dimensions_to_max_dim, + inputs=[*inputs, max_dimension_slider], + outputs=outputs, + ) + + @staticmethod + def should_show() -> bool: + return _settings.safe_opt(_constants.ARH_SHOW_MAX_WIDTH_OR_HEIGHT_KEY) + + @staticmethod + def add_options(shared): + shared.opts.add_option( + key=_constants.ARH_SHOW_MAX_WIDTH_OR_HEIGHT_KEY, + info=shared.OptionInfo( + default=_settings.OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_SHOW_MAX_WIDTH_OR_HEIGHT_KEY, + ), + label='Show maximum dimension button', + section=_constants.SECTION, + ), + ) + shared.opts.add_option( + key=_constants.ARH_MAX_WIDTH_OR_HEIGHT_KEY, + info=shared.OptionInfo( + default=_settings.OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_MAX_WIDTH_OR_HEIGHT_KEY, + ), + label='Maximum dimension default', + component=gr.Slider, + component_args={ + 'minimum': _constants.MIN_DIMENSION, + 'maximum': _constants.MAX_DIMENSION, + 'step': 8, + }, + section=_constants.SECTION, + ), + ) + + +class MinDimensionScaler(ArhUIComponent): + + def render(self): + min_dim_default = _settings.safe_opt( + _constants.ARH_MIN_WIDTH_OR_HEIGHT_KEY, + ) + self.script.min_dimension = float(min_dim_default) + + inputs = outputs = [self.script.wc, self.script.hc] + + with gr.Row( + visible=self.should_show(), + ): + min_dim_default = _settings.safe_opt( + _constants.ARH_MIN_WIDTH_OR_HEIGHT_KEY, + ) + # todo: when using gr.Slider (not deprecated), the default value + # is somehow always 270?... can't figure out why. + # using legacy inputs.Slider for now as it doesn't have the issue. + min_dimension_slider = gr.inputs.Slider( + minimum=_constants.MIN_DIMENSION, + maximum=_constants.MAX_DIMENSION, + step=8, + default=min_dim_default, + label='Minimum dimension', + ) + + def _update_min_dimension(_min_dimension): + self.script.min_dimension = _min_dimension + + min_dimension_slider.change( + _update_min_dimension, + inputs=[min_dimension_slider], + show_progress=False, + ) + + gr.Button( + value='Scale to minimum dimension', + visible=self.should_show(), + ).click( + fn=_util.scale_dimensions_to_min_dim, + inputs=[*inputs, min_dimension_slider], + outputs=outputs, + ) + + @staticmethod + def should_show() -> bool: + return _settings.safe_opt(_constants.ARH_SHOW_MIN_WIDTH_OR_HEIGHT_KEY) + + @staticmethod + def add_options(shared): + shared.opts.add_option( + key=_constants.ARH_SHOW_MIN_WIDTH_OR_HEIGHT_KEY, + info=shared.OptionInfo( + default=_settings.OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_SHOW_MIN_WIDTH_OR_HEIGHT_KEY, + ), + label='Show minimum dimension button', + section=_constants.SECTION, + ), + ) + shared.opts.add_option( + key=_constants.ARH_MIN_WIDTH_OR_HEIGHT_KEY, + info=shared.OptionInfo( + default=_settings.OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_MIN_WIDTH_OR_HEIGHT_KEY, + ), + label='Minimum dimension default', + component=gr.Slider, + component_args={ + 'minimum': _constants.MIN_DIMENSION, + 'maximum': _constants.MAX_DIMENSION, + 'step': 8, + }, + section=_constants.SECTION, + ), + ) + + +class PredefinedAspectRatioButtons(ArhUIComponent): + + def render(self): + use_max_dim_op = _settings.safe_opt( + _constants.ARH_PREDEFINED_ASPECT_RATIO_USE_MAX_DIM_KEY, + ) + aspect_ratios = _settings.safe_opt( + _constants.ARH_PREDEFINED_ASPECT_RATIOS_KEY, + ).split(',') + + with gr.Column( + variant='panel', + visible=self.should_show(), + ), gr.Row( + variant='compact', + visible=self.should_show(), + elem_classes='arh-btn-row', + ): + for ar_str in aspect_ratios: + w, h, *_ = (abs(float(d)) for d in ar_str.split(':')) + + inputs = [] + if use_max_dim_op: + ar_func = partial( + _util.scale_dimensions_to_max_dim_func, + width=w, height=h, + max_dim=lambda: self.script.max_dimension, + ) + else: + inputs.extend([self.script.wc, self.script.hc]) + ar_func = partial( + _util.scale_dimensions_to_ui_width_or_height, + arw=w, arh=h, + ) + + gr.Button( + value=self.display_func(ar_str) or ar_str, + visible=self.should_show(), + ).click( + fn=ar_func, + inputs=inputs, + outputs=[self.script.wc, self.script.hc], + ) + + @staticmethod + def should_show() -> bool: + return _settings.safe_opt( + _constants.ARH_SHOW_PREDEFINED_ASPECT_RATIOS_KEY, + ) + + @staticmethod + def add_options(shared): + shared.opts.add_option( + key=_constants.ARH_SHOW_PREDEFINED_ASPECT_RATIOS_KEY, + info=shared.OptionInfo( + default=_settings.OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_SHOW_PREDEFINED_ASPECT_RATIOS_KEY, + ), + label='Show pre-defined aspect ratio buttons', + section=_constants.SECTION, + ), + ) + shared.opts.add_option( + key=_constants.ARH_PREDEFINED_ASPECT_RATIO_USE_MAX_DIM_KEY, + info=shared.OptionInfo( + default=_settings.OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_PREDEFINED_ASPECT_RATIO_USE_MAX_DIM_KEY, + ), + label='Use "Maximum dimension" for aspect ratio buttons (by ' + 'default we use the max width or height)', + section=_constants.SECTION, + ), + ) + shared.opts.add_option( + key=_constants.ARH_PREDEFINED_ASPECT_RATIOS_KEY, + info=shared.OptionInfo( + default=_settings.OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_PREDEFINED_ASPECT_RATIOS_KEY, + ), + label='Pre-defined aspect ratio buttons ' + '(1:1, 4:3, 16:9, 9:16, 21:9)', + section=_constants.SECTION, + ), + ) + + @property + def display_func(self) -> Callable[[str], str]: + return lambda _: None # todo: different displays for aspect ratios. + + +class PredefinedPercentageButtons(ArhUIComponent): + + def render(self): + inputs = outputs = [self.script.wc, self.script.hc] + with gr.Column( + variant='panel', + visible=self.should_show(), + ), gr.Row( + variant='compact', + visible=self.should_show(), + elem_classes='arh-btn-row', + ): + pps = _settings.safe_opt(_constants.ARH_PREDEFINED_PERCENTAGES_KEY) + percentages = [abs(int(x)) for x in pps.split(',')] + + for percentage in percentages: + display = self.display_func(percentage) + gr.Button( + value=display, + visible=self.should_show(), + ).click( + fn=partial( + _util.scale_by_percentage, + pct=percentage / 100, + ), + inputs=inputs, + outputs=outputs, + ) + + @staticmethod + def should_show() -> bool: + return _settings.safe_opt( + _constants.ARH_SHOW_PREDEFINED_PERCENTAGES_KEY, + ) + + @staticmethod + def add_options(shared): + shared.opts.add_option( + key=_constants.ARH_SHOW_PREDEFINED_PERCENTAGES_KEY, + info=shared.OptionInfo( + default=_settings.OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_SHOW_PREDEFINED_PERCENTAGES_KEY, + ), + label='Show pre-defined percentage buttons', + section=_constants.SECTION, + ), + ) + shared.opts.add_option( + key=_constants.ARH_PREDEFINED_PERCENTAGES_KEY, + info=shared.OptionInfo( + default=_settings.OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_PREDEFINED_PERCENTAGES_KEY, + ), + label='Pre-defined percentage buttons (75, 125, 150)', + section=_constants.SECTION, + ), + ) + shared.opts.add_option( + key=_constants.ARH_PREDEFINED_PERCENTAGES_DISPLAY_KEY, + info=shared.OptionInfo( + default=_settings.OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_PREDEFINED_PERCENTAGES_DISPLAY_KEY, + ), + label='Pre-defined percentage display format', + component=gr.Dropdown, + component_args=lambda: { + 'choices': tuple( + _settings.PREDEFINED_PERCENTAGES_DISPLAY_MAP.keys(), + ), + }, + section=_constants.SECTION, + ), + ) + + @property + def display_func(self) -> Callable[[str], str]: + return _settings.PREDEFINED_PERCENTAGES_DISPLAY_MAP.get( + _settings.safe_opt( + _constants.ARH_PREDEFINED_PERCENTAGES_DISPLAY_KEY, + ), + ) diff --git a/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_constants.py b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_constants.py new file mode 100644 index 0000000000000000000000000000000000000000..a9a7c89c51415c63a579beaa959794469096dd4b --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_constants.py @@ -0,0 +1,27 @@ +from __future__ import annotations +EXTENSION_NAME = 'Aspect Ratio Helper' + +MAX_DIMENSION = 2048 +MIN_DIMENSION = 64 + +ARH_EXPAND_BY_DEFAULT_KEY = 'arh_expand_by_default' +ARH_HIDE_ACCORDION_BY_DEFAULT_KEY = 'arh_hide_accordion_by_default' +ARH_UI_COMPONENT_ORDER_KEY = 'arh_ui_component_order_key' +ARH_UI_JAVASCRIPT_SELECTION_METHOD = 'arh_ui_javascript_selection_method' +ARH_JAVASCRIPT_ASPECT_RATIO_SHOW_KEY = 'arh_javascript_aspect_ratio_show' +ARH_JAVASCRIPT_ASPECT_RATIOS_KEY = 'arh_javascript_aspect_ratio' +ARH_SHOW_MAX_WIDTH_OR_HEIGHT_KEY = 'arh_show_max_width_or_height' +ARH_SHOW_MIN_WIDTH_OR_HEIGHT_KEY = 'arh_show_min_width_or_height' +ARH_MAX_WIDTH_OR_HEIGHT_KEY = 'arh_max_width_or_height' +ARH_MIN_WIDTH_OR_HEIGHT_KEY = 'arh_min_width_or_height' +ARH_SHOW_PREDEFINED_PERCENTAGES_KEY = 'arh_show_predefined_percentages' +ARH_PREDEFINED_PERCENTAGES_KEY = 'arh_predefined_percentages' +ARH_SHOW_PREDEFINED_ASPECT_RATIOS_KEY = 'arh_show_predefined_aspect_ratios' +ARH_PREDEFINED_ASPECT_RATIOS_KEY = 'arh_predefined_aspect_ratios' +ARH_PREDEFINED_ASPECT_RATIO_USE_MAX_DIM_KEY \ + = 'arh_predefined_aspect_ratio_use_max_dim' +ARH_PREDEFINED_PERCENTAGES_DISPLAY_KEY \ + = 'arh_predefined_percentages_display_key' +DEFAULT_PERCENTAGES_DISPLAY_KEY \ + = 'Incremental/decremental percentage (-50%, +50%)' +SECTION = 'aspect_ratio_helper', EXTENSION_NAME diff --git a/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_settings.py b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_settings.py new file mode 100644 index 0000000000000000000000000000000000000000..075d8bf85f84b04868c3b52a9e1f596e810a5c93 --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_settings.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +import itertools + +import gradio as gr +from modules import shared + +import aspect_ratio_helper._components as _components +import aspect_ratio_helper._constants as _constants +import aspect_ratio_helper._util as _util + +PREDEFINED_PERCENTAGES_DISPLAY_MAP = { + _constants.DEFAULT_PERCENTAGES_DISPLAY_KEY: _util.display_minus_and_plus, + 'Raw percentage (50%, 150%)': _util.display_raw_percentage, + 'Multiplication (x0.5, x1.5)': _util.display_multiplication, +} + +COMPONENTS = ( + _components.MaxDimensionScaler, + _components.MinDimensionScaler, + _components.PredefinedAspectRatioButtons, + _components.PredefinedPercentageButtons, +) + +DEFAULT_UI_COMPONENT_ORDER_KEY_LIST = [e.__name__ for e in COMPONENTS] +DEFAULT_UI_COMPONENT_ORDER_KEY = ', '.join( + DEFAULT_UI_COMPONENT_ORDER_KEY_LIST, +) +OPT_KEY_TO_DEFAULT_MAP = { + _constants.ARH_EXPAND_BY_DEFAULT_KEY: False, + _constants.ARH_HIDE_ACCORDION_BY_DEFAULT_KEY: True, + _constants.ARH_UI_COMPONENT_ORDER_KEY: + DEFAULT_UI_COMPONENT_ORDER_KEY, + _constants.ARH_UI_JAVASCRIPT_SELECTION_METHOD: 'Aspect Ratios Dropdown', + _constants.ARH_JAVASCRIPT_ASPECT_RATIO_SHOW_KEY: True, + _constants.ARH_JAVASCRIPT_ASPECT_RATIOS_KEY: + '1:1, 3:2, 4:3, 5:4, 16:9', + _constants.ARH_SHOW_MAX_WIDTH_OR_HEIGHT_KEY: False, + _constants.ARH_MAX_WIDTH_OR_HEIGHT_KEY: + _constants.MAX_DIMENSION / 2, + _constants.ARH_SHOW_MIN_WIDTH_OR_HEIGHT_KEY: False, + _constants.ARH_MIN_WIDTH_OR_HEIGHT_KEY: + _constants.MAX_DIMENSION / 2, + _constants.ARH_SHOW_PREDEFINED_PERCENTAGES_KEY: False, + _constants.ARH_PREDEFINED_PERCENTAGES_KEY: + '25, 50, 75, 125, 150, 175, 200', + _constants.ARH_PREDEFINED_PERCENTAGES_DISPLAY_KEY: + _constants.DEFAULT_PERCENTAGES_DISPLAY_KEY, + _constants.ARH_SHOW_PREDEFINED_ASPECT_RATIOS_KEY: False, + _constants.ARH_PREDEFINED_ASPECT_RATIO_USE_MAX_DIM_KEY: False, + _constants.ARH_PREDEFINED_ASPECT_RATIOS_KEY: + '1:1, 4:3, 16:9, 9:16, 21:9', +} + + +def safe_opt(key): + return _util.safe_opt_util(shared.opts, key, OPT_KEY_TO_DEFAULT_MAP) + + +def sort_components_by_keys( + components: list[_components.ArhUIComponent], +) -> list[_components.ArhUIComponent]: + ordered_component_keys = safe_opt( + _constants.ARH_UI_COMPONENT_ORDER_KEY, + ).split(',') + + # this can happen if we add new components, but the user has old settings. + # if this happens, we find the missing components, and append them. + if len(ordered_component_keys) != len(COMPONENTS): + all_components = set(DEFAULT_UI_COMPONENT_ORDER_KEY_LIST) + missing_components = all_components - set(ordered_component_keys) + ordered_component_keys.extend(missing_components) + + try: + component_key_to_order_dict = { + key: order for order, key in enumerate( + [k.strip() for k in ordered_component_keys], + ) + } + return sorted( + components, + key=lambda c: component_key_to_order_dict.get( + c.__class__.__name__, + ), + ) + except ValueError: + print( + f'{_constants.EXTENSION_NAME} UI component order is erroneous. ' + f'Defaulting to regular order, to fix this, please use' + f'the intended syntax for the setting, i.e ' + f'"{DEFAULT_UI_COMPONENT_ORDER_KEY}"', + ) + return components + + +def on_ui_settings(): + # javascript ui options + shared.opts.add_option( + key=_constants.ARH_JAVASCRIPT_ASPECT_RATIO_SHOW_KEY, + info=shared.OptionInfo( + default=OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_JAVASCRIPT_ASPECT_RATIO_SHOW_KEY, + ), + label='Enable JavaScript aspect ratio controls', + section=_constants.SECTION, + ), + ) + shared.opts.add_option( + key=_constants.ARH_JAVASCRIPT_ASPECT_RATIOS_KEY, + info=shared.OptionInfo( + default=OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_JAVASCRIPT_ASPECT_RATIOS_KEY, + ), + label='JavaScript aspect ratio buttons' + ' (1:1, 4:3, 16:9, 9:16, 21:9)', + section=_constants.SECTION, + ), + ) + shared.opts.add_option( + key=_constants.ARH_UI_JAVASCRIPT_SELECTION_METHOD, + info=shared.OptionInfo( + default=OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_UI_JAVASCRIPT_SELECTION_METHOD, + ), + label='JavaScript selection method', + component=gr.Dropdown, + component_args=lambda: { + 'choices': [ + 'Aspect Ratios Dropdown', + 'Default Options Button', + ], + }, + section=_constants.SECTION, + ), + ) + + # accordion options + shared.opts.add_option( + key=_constants.ARH_HIDE_ACCORDION_BY_DEFAULT_KEY, + info=shared.OptionInfo( + default=OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_HIDE_ACCORDION_BY_DEFAULT_KEY, + ), + label='Hide accordion by default', + section=_constants.SECTION, + ), + ) + shared.opts.add_option( + key=_constants.ARH_EXPAND_BY_DEFAULT_KEY, + info=shared.OptionInfo( + default=OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_EXPAND_BY_DEFAULT_KEY, + ), + label='Expand accordion by default', + section=_constants.SECTION, + ), + ) + shared.opts.add_option( + key=_constants.ARH_UI_COMPONENT_ORDER_KEY, + info=shared.OptionInfo( + default=OPT_KEY_TO_DEFAULT_MAP.get( + _constants.ARH_UI_COMPONENT_ORDER_KEY, + ), + label='UI Component order', + component=gr.Dropdown, + component_args=lambda: { + 'choices': [ + ', '.join(p) for p in itertools.permutations( # TODO: Rethink this, exponential growth... + DEFAULT_UI_COMPONENT_ORDER_KEY_LIST, + ) + ], + }, + section=_constants.SECTION, + ), + ) + + for component in COMPONENTS: + component.add_options(shared) diff --git a/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_util.py b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_util.py new file mode 100644 index 0000000000000000000000000000000000000000..b10be805845ec58a21eafedc0f26842f13399ffc --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/_util.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import contextlib + +import aspect_ratio_helper._constants as _const + + +def safe_opt_util(shared_opts, key, default_key_map: dict[str, object]): + # attempt to retrieve key from shared options + with contextlib.suppress(AttributeError): + value = shared_opts.__getattr__(key) + if value is not None: + return value + + # attempt to retrieve default, and last resort the constant default + return shared_opts.get_default(key) or default_key_map.get(key) + + +def display_multiplication(num) -> str: + return f'x{round(num / 100, 3)}' + + +def display_raw_percentage(num) -> str: + return f'{num}%' + + +def display_minus_and_plus(num) -> str: + num -= 100 + if num > 0: + return f'+{num}%' + return f'{num}%' + + +def scale_by_percentage(width, height, pct) -> tuple[int, int]: + aspect_ratio = float(width) / float(height) + step = (pct - 1.0) + new_width = int(round(width * (1.0 + step))) + new_height = int(round(new_width / aspect_ratio)) + return clamp_to_boundaries(new_width, new_height, aspect_ratio) + + +def scale_dimensions_to_ui_width_or_height( + width, height, arw, arh, +) -> tuple[int, int]: + return scale_dimensions_to_max_dim(arw, arh, max(width, height)) + + +def scale_dimensions_to_max_dim_func( + width, height, max_dim: callable, +) -> tuple[int, int]: + return scale_dimensions_to_max_dim(width, height, max_dim()) + + +def scale_dimensions_to_max_dim( + width, height, max_dim, +) -> tuple[int, int]: + aspect_ratio = float(width) / float(height) + return scale_dimensions_to_ar(width, height, max_dim, aspect_ratio) + + +def scale_dimensions_to_min_dim( + width, height, min_dim, +) -> tuple[int, int]: + aspect_ratio = float(width) / float(height) + if width >= height: + max_dim = min_dim * aspect_ratio + else: + max_dim = min_dim / aspect_ratio + return scale_dimensions_to_ar(width, height, max_dim, aspect_ratio) + + +def scale_dimensions_to_ar( + width, height, max_dim, aspect_ratio, +) -> tuple[int, int]: + if width > height: + new_width = max_dim + new_height = int(round(max_dim / aspect_ratio)) + else: + new_height = max_dim + new_width = int(round(max_dim * aspect_ratio)) + return clamp_to_boundaries(new_width, new_height, aspect_ratio) + + +def round_to_multiple_of_8(value): + return int(round(value / 8.0)) * 8 + + +def clamp_to_boundaries(owidth, oheight, aspect_ratio) -> tuple[int, int]: + width, height = owidth, oheight + if width > _const.MAX_DIMENSION: + width = _const.MAX_DIMENSION + height = int(round(width / aspect_ratio)) + if height > _const.MAX_DIMENSION: + height = _const.MAX_DIMENSION + width = int(round(height * aspect_ratio)) + if width < _const.MIN_DIMENSION: + width = _const.MIN_DIMENSION + height = int(round(width / aspect_ratio)) + if height < _const.MIN_DIMENSION: + height = _const.MIN_DIMENSION + width = int(round(height * aspect_ratio)) + + width = round_to_multiple_of_8(width) + height = round_to_multiple_of_8(height) + # for insane aspect ratios we don't support... i.e 1:100 + # 64:6400 when run through this function, so we clamp to 64:2048 (‾◡◝) + # also. when the user does this it breaks the "scale to max" function + # on the ui until they change to another reasonable aspect ratio. + # todo: figure out why ^ and handle it better. + if width < _const.MIN_DIMENSION: + width = _const.MIN_DIMENSION + elif width > _const.MAX_DIMENSION: + width = _const.MAX_DIMENSION + if height < _const.MIN_DIMENSION: + height = _const.MIN_DIMENSION + elif height > _const.MAX_DIMENSION: + height = _const.MAX_DIMENSION + + return width, height diff --git a/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/main.py b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/main.py new file mode 100644 index 0000000000000000000000000000000000000000..814569e123b3d9c3e6d83cdece9b7f6b7523b468 --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/aspect_ratio_helper/main.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import gradio as gr +from modules import script_callbacks +from modules import scripts + +import aspect_ratio_helper._constants as _constants +import aspect_ratio_helper._settings as _settings + + +class AspectRatioStepScript(scripts.Script): + + def __init__(self): + self.t2i_w: gr.components.Slider | None = None + self.t2i_h: gr.components.Slider | None = None + self.i2i_w: gr.components.Slider | None = None + self.i2i_h: gr.components.Slider | None = None + self.wc: gr.components.Slider + self.hc: gr.components.Slider + self.max_dimension: float + + def title(self) -> str: + return _constants.EXTENSION_NAME + + def show(self, _) -> scripts.AlwaysVisible: + return scripts.AlwaysVisible + + def ui(self, is_img2img): + if is_img2img: + self.wc, self.hc = self.i2i_w, self.i2i_h + else: + self.wc, self.hc = self.t2i_w, self.t2i_h # noqa + + components = _settings.sort_components_by_keys( + [component(self) for component in _settings.COMPONENTS], + ) + + hide_accordion: bool = _settings.safe_opt( + _constants.ARH_HIDE_ACCORDION_BY_DEFAULT_KEY, + ) + + if hide_accordion or not any(c.should_show() for c in components): + return # no components should render, so just return. + + start_expanded: bool = _settings.safe_opt( + _constants.ARH_EXPAND_BY_DEFAULT_KEY, + ) + with gr.Group(), gr.Accordion( + _constants.EXTENSION_NAME, + open=start_expanded, + ): + for component in components: + # we deliberately DON'T check component.should_show() here. + # we need to call render to instantiate the components, we use + # the visible property on each component to hide them. + component.render() + + def after_component(self, component: gr.components.Component, **kwargs): + element_id = kwargs.get('elem_id') + + if isinstance(component, gr.components.Slider): + if element_id == 'txt2img_width': + self.t2i_w: gr.components.Slider = component + elif element_id == 'txt2img_height': + self.t2i_h: gr.components.Slider = component + elif element_id == 'img2img_width': + self.i2i_w: gr.components.Slider = component + elif element_id == 'img2img_height': + self.i2i_h: gr.components.Slider = component + + +script_callbacks.on_ui_settings(_settings.on_ui_settings) diff --git a/extensions/sd-webui-aspect-ratio-helper/docs/opts.png b/extensions/sd-webui-aspect-ratio-helper/docs/opts.png new file mode 100644 index 0000000000000000000000000000000000000000..76037916f8a8501e0d8209eac0b6ec8ac4f759e0 Binary files /dev/null and b/extensions/sd-webui-aspect-ratio-helper/docs/opts.png differ diff --git a/extensions/sd-webui-aspect-ratio-helper/docs/ui.png b/extensions/sd-webui-aspect-ratio-helper/docs/ui.png new file mode 100644 index 0000000000000000000000000000000000000000..51b1371f094efa2875be1559d009dd9fbabfb23e Binary files /dev/null and b/extensions/sd-webui-aspect-ratio-helper/docs/ui.png differ diff --git a/extensions/sd-webui-aspect-ratio-helper/javascript/aspectRatioController.js b/extensions/sd-webui-aspect-ratio-helper/javascript/aspectRatioController.js new file mode 100644 index 0000000000000000000000000000000000000000..c699fb708506d49b69e38bf0983e6da3a7cf3fe1 --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/javascript/aspectRatioController.js @@ -0,0 +1,474 @@ +// constants +const _OFF = "Off"; // Don't use the functionality at all +const _LOCK = '🔒'; // Aspect ratio is "locked" +const _IMAGE = '🖼️'; // Aspect ratio is "locked" to that of the image + +const _MAXIMUM_DIMENSION = 2048; +const _MINIMUM_DIMENSION = 64; + +const _IMAGE_INPUT_CONTAINER_IDS = [ + 'img2img_image', + 'img2img_sketch', + 'img2maskimg', + 'inpaint_sketch', + 'img_inpaint_base', +]; + +const getSelectedImage2ImageTab = () => { + const selectedButton = gradioApp().getElementById('mode_img2img').querySelector('button.selected'); + const allButtons = gradioApp().getElementById('mode_img2img').querySelectorAll('button'); + const selectedIndex = Array.prototype.indexOf.call(allButtons, selectedButton); + return selectedIndex; +} + +const getCurrentImage = () => { + const currentTabIndex = getSelectedImage2ImageTab(); + const currentTabImageId = _IMAGE_INPUT_CONTAINER_IDS[currentTabIndex]; + return document.getElementById(currentTabImageId).querySelector('img'); +} + +const roundToClosestMultiple = (num, multiple) => { + const rounded = Math.round(Number(num) / multiple) * multiple; + return rounded; +} + +const aspectRatioFromStr = (ar) => { + if (!ar.includes(':')) return; + return ar.split(':').map(x => Number(x)); +} + +const reverseAspectRatio = (ar) => { + if (!ar.includes(':')) return; + const [width, height] = ar.split(':'); + return `${height}:${width}`; +} + +const clampToBoundaries = (width, height) => { + const aspectRatio = width / height; + width = Math.max(Math.min(width, _MAXIMUM_DIMENSION), _MINIMUM_DIMENSION); + height = Math.max(Math.min(height, _MAXIMUM_DIMENSION), _MINIMUM_DIMENSION); + if (width / height > aspectRatio) { + height = Math.round(width / aspectRatio); + } else if (width / height < aspectRatio) { + width = Math.round(height * aspectRatio); + } + + if (width > _MAXIMUM_DIMENSION) { + width = _MAXIMUM_DIMENSION; + } else if (width < _MINIMUM_DIMENSION) { + width = _MINIMUM_DIMENSION; + } + + if (height < _MINIMUM_DIMENSION) { + height = _MINIMUM_DIMENSION; + } else if (height > _MAXIMUM_DIMENSION) { + height = _MAXIMUM_DIMENSION; + } + + return [width, height]; +} + +const getOptions = () => { + return window.opts.arh_javascript_aspect_ratio.split(',').map(o => o.trim()); +} + +const reverseAllOptions = () => { + const allAspectRatioOptions = Array.from(gradioApp().querySelectorAll('.ar-option')); + allAspectRatioOptions.forEach(el => { + const reversed = reverseAspectRatio(el.value); + if (reversed) { + el.value = reversed; + el.textContent = reversed; + } + }); +} + +class OptionPickingController { + constructor(page, defaultOptions, controller) { + this.page = page; + this.options = this.getOptions(defaultOptions); + this.switchButton = gradioApp().getElementById(page + '_res_switch_btn'); + + const wrapperDiv = document.createElement('div'); + wrapperDiv.setAttribute("id", `${this.page}_size_toolbox`); + wrapperDiv.setAttribute("class", "flex flex-col relative col gap-4"); + wrapperDiv.setAttribute("style", "min-width: min(320px, 100%); flex-grow: 0"); + wrapperDiv.innerHTML = this.getElementInnerHTML(); + + const parent = this.switchButton.parentNode; + parent.removeChild(this.switchButton); + wrapperDiv.appendChild(this.switchButton); + parent.insertBefore(wrapperDiv, parent.lastChild.previousElementSibling); + + this.getPickerElement().onchange = this.pickerChanged(controller); + this.switchButton.onclick = this.switchButtonOnclick(controller); + } + + getOptions(defaultOptions) { + return [...new Set([...defaultOptions, ...getOptions()])]; + } + + pickerChanged(controller) { + return () => { + const picked = this.getCurrentOption(); + if (_IMAGE === picked) { + // this.switchButton.disabled = true; + } else { + this.switchButton.removeAttribute('disabled') + } + + controller.setAspectRatio(picked); + }; + } + + switchButtonOnclick(controller) { + return () => { + reverseAllOptions(); + const picked = this.getCurrentOption(); + if (_LOCK === picked) { + controller.setAspectRatio(`${controller.heightRatio}:${controller.widthRatio}`); + } else { + controller.setAspectRatio(picked); + } + }; + } + + getElementInnerHTML() { + throw new Error('Not implemented'); + } + + getPickerElement() { + throw new Error('Not implemented'); + } + + getCurrentOption() { + throw new Error('Not implemented'); + } +} + + +class SelectOptionPickingController extends OptionPickingController { + constructor(page, defaultOptions, controller) { + super(page, defaultOptions, controller); + } + + getElementInnerHTML() { + return ` +
+ +
+ `; + } + + getPickerElement() { + return gradioApp().getElementById(`${this.page}_select_aspect_ratio`); + } + + getCurrentOption() { + const selectElement = this.getPickerElement(); + const options = Array.from(selectElement); + return options[selectElement.selectedIndex].value; + } +} + +class DefaultOptionsButtonOptionPickingController extends OptionPickingController { + constructor(page, defaultOptions, controller) { + super(page, defaultOptions, controller); + this.currentIndex = 0; + this.getPickerElement().onclick = this.pickerChanged(controller); + } + + pickerChanged(controller) { + return () => { + this.currentIndex = (this.currentIndex + 1) % this.options.length; + this.getPickerElement().querySelector('button').textContent = this.getCurrentOption(); + super.pickerChanged(controller)(); + } + } + + getElementInnerHTML() { + const classes = Array.from(this.switchButton.classList); + return ` +
+ +
+ `; + } + + getPickerElement() { + return gradioApp().getElementById(`${this.page}_ar_default_options_button`); + } + + getOptions(defaultOptions) { + return defaultOptions; + } + + getCurrentOption() { + return this.options[this.currentIndex || 0]; + } +} + + +class SliderController { + constructor(element) { + this.element = element; + this.numberInput = this.element.querySelector('input[type=number]'); + this.rangeInput = this.element.querySelector('input[type=range]'); + this.inputs = [this.numberInput, this.rangeInput]; + this.inputs.forEach(input => { + input.isWidth = element.isWidth; + }); + } + + getVal() { + return Number(this.numberInput.value); + } + + updateVal(value) { + this.inputs.forEach(input => { + input.value = Number(value) + }) + } + + updateMin(value) { + this.inputs.forEach(input => { + input.min = roundToClosestMultiple(Number(value), 8); + }) + } + + updateMax(value) { + this.inputs.forEach(input => { + input.max = roundToClosestMultiple(Number(value), 8); + }) + } + + triggerEvent(event) { + this.numberInput.dispatchEvent(event) + } + + setVal(value) { + value = Number(value) + const newValue = roundToClosestMultiple(value, 8) + this.updateVal(newValue); + } + +} + +class AspectRatioController { + constructor(page, widthContainer, heightContainer, defaultOptions) { + widthContainer.isWidth = true; + heightContainer.isWidth = false; + this.widthContainer = new SliderController(widthContainer); + this.heightContainer = new SliderController(heightContainer); + this.inputs = [...this.widthContainer.inputs, ...this.heightContainer.inputs]; + this.inputs.forEach(input => { + input.addEventListener('change', (e) => { + e.preventDefault() + this.maintainAspectRatio(input); + }); + }) + + if (window.opts.arh_ui_javascript_selection_method === 'Default Options Button') { + this.optionPickingControler = new DefaultOptionsButtonOptionPickingController(page, defaultOptions, this); + } else { + this.optionPickingControler = new SelectOptionPickingController(page, defaultOptions, this); + } + + this.setAspectRatio(_OFF); + } + + updateInputStates() { + if (this.isLandscapeOrSquare()) { + const AR = this.widthRatio / this.heightRatio; + + const minWidthByAr = Math.round(_MINIMUM_DIMENSION * AR); + const minWidth = Math.max(minWidthByAr, _MINIMUM_DIMENSION); + this.widthContainer.updateMin(minWidth); + this.heightContainer.updateMin(_MINIMUM_DIMENSION); + + const maxHeightByAr = Math.round(_MAXIMUM_DIMENSION / AR) + const maxHeight = Math.min(_MAXIMUM_DIMENSION, maxHeightByAr); + this.heightContainer.updateMax(maxHeight); + this.widthContainer.updateMax(_MAXIMUM_DIMENSION); + } else { + const AR = this.heightRatio / this.widthRatio; + + const minHeightByAr = Math.round(_MINIMUM_DIMENSION * AR) + const minHeight = Math.max(minHeightByAr, _MINIMUM_DIMENSION); + this.heightContainer.updateMin(minHeight); + this.widthContainer.updateMin(_MINIMUM_DIMENSION); + + const maxWidthByAr = Math.round(_MAXIMUM_DIMENSION / AR) + const maxWidth = Math.min(_MAXIMUM_DIMENSION, maxWidthByAr); + this.widthContainer.updateMax(maxWidth); + this.heightContainer.updateMax(_MAXIMUM_DIMENSION); + } + } + + disable() { + this.widthContainer.updateMin(_MINIMUM_DIMENSION); + this.heightContainer.updateMin(_MINIMUM_DIMENSION); + this.widthContainer.updateMax(_MAXIMUM_DIMENSION); + this.heightContainer.updateMax(_MAXIMUM_DIMENSION); + } + + isLandscapeOrSquare() { + return this.widthRatio >= this.heightRatio; + } + + setAspectRatio(aspectRatio) { + this.aspectRatio = aspectRatio; + + let wR, hR; + if (aspectRatio === _OFF) { + return this.disable(); + } else if (aspectRatio === _IMAGE) { + const img = getCurrentImage(); + wR = img && img.naturalWidth || 1; + hR = img && img.naturalHeight || 1; + } else if (aspectRatio === _LOCK) { + wR = this.widthContainer.getVal(); + hR = this.heightContainer.getVal(); + } else { + [wR, hR] = aspectRatioFromStr(aspectRatio); + } + + [wR, hR] = clampToBoundaries(wR, hR); + + this.widthRatio = wR; + this.heightRatio = hR; + this.updateInputStates(); + this.maintainAspectRatio(); + } + + maintainAspectRatio(changedElement) { + if (this.aspectRatio === _OFF) return; + if (!changedElement) { + const allValues = Object.values(this.inputs).map(x => Number(x.value)); + changedElement = {value: Math.max(...allValues)}; + } + + const aspectRatio = this.widthRatio / this.heightRatio; + let w, h; + + if (changedElement.isWidth === undefined) { + if (this.isLandscapeOrSquare()) { + if (changedElement.isWidth) {} + w = Math.round(changedElement.value); + h = Math.round(changedElement.value / aspectRatio); + } else { + h = Math.round(changedElement.value); + w = Math.round(changedElement.value * aspectRatio); + } + } else { + if (changedElement.isWidth) { + w = Math.round(changedElement.value); + h = Math.round(changedElement.value / aspectRatio); + } else { + h = Math.round(changedElement.value); + w = Math.round(changedElement.value * aspectRatio); + } + } + + const [width, height] = clampToBoundaries(w, h) + + const inputEvent = new Event("input", {bubbles: true}); + this.widthContainer.setVal(width); + this.widthContainer.triggerEvent(inputEvent); + this.heightContainer.setVal(height); + this.heightContainer.triggerEvent(inputEvent); + this.heightContainer.inputs.forEach(input => { + dimensionChange({target: input}, false, true); + }); + this.widthContainer.inputs.forEach(input => { + dimensionChange({target: input}, true, false); + }); + } + + static observeStartup(key, page, defaultOptions, postSetup = (_) => { + }) { + let observer = new MutationObserver(() => { + const widthContainer = gradioApp().querySelector(`#${page}_width`); + const heightContainer = gradioApp().querySelector(`#${page}_height`); + + // wait for width and height containers to exist. + if (widthContainer && heightContainer && window.opts && window.opts.arh_javascript_aspect_ratio_show !== undefined) { + observer.disconnect(); + if (!window.opts.arh_javascript_aspect_ratio_show) { + return; + } + + const controller = new AspectRatioController( + page, + widthContainer, + heightContainer, + defaultOptions, + ); + + postSetup(controller); + window[key] = controller; + } + }); + + observer.observe(gradioApp(), {childList: true, subtree: true}); + } + +} + +const addImg2ImgTabSwitchClickListeners = (controller) => { + const img2imgTabButtons = Array.from(document.querySelectorAll('#img2img_settings > div > div > button:not(.selected):not(.hasTabSwitchListener)')); + img2imgTabButtons.forEach(button => { + button.addEventListener('click', (_) => { + // set aspect ratio is RECALLED to change to the image specific to the newly selected tab. + if (controller.optionPickingControler.getCurrentOption() === _IMAGE) { + controller.setAspectRatio(_IMAGE); + } + + addImg2ImgTabSwitchClickListeners(controller); + }); + + button.classList.add('hasTabSwitchListener'); + }); +} + +const postImageControllerSetupFunction = (controller) => { + const scaleToImg2ImgImage = (e) => { + const picked = controller.optionPickingControler.getCurrentOption(); + if (picked !== _IMAGE) return; + const files = e.dataTransfer ? e.dataTransfer.files : e.target.files; + const img = new Image(); + img.src = URL.createObjectURL(files[0]); + img.onload = () => { + controller.setAspectRatio(`${img.naturalWidth}:${img.naturalHeight}`) + }; + } + + _IMAGE_INPUT_CONTAINER_IDS.forEach(imageContainerId => { + const imageContainer = document.getElementById(imageContainerId); + const inputElement = imageContainer.querySelector('input'); + inputElement.parentElement.addEventListener('drop', scaleToImg2ImgImage); + inputElement.addEventListener('change', scaleToImg2ImgImage); + }) + + addImg2ImgTabSwitchClickListeners(controller); +} + +document.addEventListener("DOMContentLoaded", () => { + AspectRatioController.observeStartup( + "__txt2imgAspectRatioController", + "txt2img", + [_OFF, _LOCK] + ); + AspectRatioController.observeStartup( + "__img2imgAspectRatioController", + "img2img", + [_OFF, _LOCK, _IMAGE], + postImageControllerSetupFunction + ); +}); diff --git a/extensions/sd-webui-aspect-ratio-helper/scripts/__pycache__/sd_webui_aspect_ratio_helper.cpython-310.pyc b/extensions/sd-webui-aspect-ratio-helper/scripts/__pycache__/sd_webui_aspect_ratio_helper.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..415c0cd711bce8940ca64f0aea637bc506c56ab4 Binary files /dev/null and b/extensions/sd-webui-aspect-ratio-helper/scripts/__pycache__/sd_webui_aspect_ratio_helper.cpython-310.pyc differ diff --git a/extensions/sd-webui-aspect-ratio-helper/scripts/sd_webui_aspect_ratio_helper.py b/extensions/sd-webui-aspect-ratio-helper/scripts/sd_webui_aspect_ratio_helper.py new file mode 100644 index 0000000000000000000000000000000000000000..ed78b20d6a33f4f180b0f04392dfbb08f72ec1a8 --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/scripts/sd_webui_aspect_ratio_helper.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from aspect_ratio_helper.main import AspectRatioStepScript + +__all__ = ['AspectRatioStepScript'] diff --git a/extensions/sd-webui-aspect-ratio-helper/style.css b/extensions/sd-webui-aspect-ratio-helper/style.css new file mode 100644 index 0000000000000000000000000000000000000000..d6a3378cdb00d7d716570823ad5e49d8d091f39e --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/style.css @@ -0,0 +1,36 @@ +#txt2img_size_toolbox, #img2img_size_toolbox { + min-width: unset !important; + gap: 0; +} + +#txt2img_ratio, #img2img_ratio { + padding: 0px; + min-width: unset; + max-width: fit-content; +} + +#txt2img_ratio select, #img2img_ratio select { + background: var(--background-fill-primary); + border: 1px solid var(--block-border-color); + color: var(--block-label-text-color); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-image: none; + tab-size: 4; + font-size: 14px; + line-height: 1.4; + box-sizing: border-box; + position: relative; + border-radius: 8px; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + padding-right: 1px; + margin-bottom: 10px; + padding-left: 2px; + text-align: center; + min-width: 2.5em; +} + +.arh-btn-row button { + min-width: fit-content; +} diff --git a/extensions/sd-webui-aspect-ratio-helper/tests/__init__.py b/extensions/sd-webui-aspect-ratio-helper/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/extensions/sd-webui-aspect-ratio-helper/tests/_util_test.py b/extensions/sd-webui-aspect-ratio-helper/tests/_util_test.py new file mode 100644 index 0000000000000000000000000000000000000000..4d736c15376638b53e564b0d8449e49d00f0bcb1 --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/tests/_util_test.py @@ -0,0 +1,291 @@ +from __future__ import annotations + +import pytest + +import aspect_ratio_helper._constants as _constants +import aspect_ratio_helper._util as _util + + +@pytest.mark.parametrize( + 'num, expected', + [ + (50, 'x0.5'), + (150, 'x1.5'), + (175, 'x1.75'), + (250, 'x2.5'), + ], +) +def test_display_multiplication(num, expected): + assert _util.display_multiplication(num) == expected + + +@pytest.mark.parametrize( + 'num, expected', + [ + (50, '50%'), + (75, '75%'), + (100, '100%'), + (150, '150%'), + (250, '250%'), + ], +) +def test_display_raw_percentage(num, expected): + assert _util.display_raw_percentage(num) == expected + + +@pytest.mark.parametrize( + 'num, expected_output', [ + (150, '+50%'), + (100, '0%'), + (50, '-50%'), + (0, '-100%'), + (200, '+100%'), + (75, '-25%'), + ], +) +def test_display_minus_and_plus(num, expected_output): + assert _util.display_minus_and_plus(num) == expected_output + + +@pytest.mark.parametrize( + 'width, height, pct, expected', + [ + pytest.param(200, 400, 0.5, (96, 200), id='50_percent_scale_down'), + pytest.param(100, 200, 2.0, (200, 400), id='200_percent_scale_up'), + pytest.param(100, 200, 1.1, (112, 224), id='10_percent_scale_up'), + pytest.param(100, 200, 0.9, (88, 176), id='10_percent_scale_down'), + pytest.param(100, 200, 0.0, (64, 128), id='scale_full_down'), + pytest.param( + _constants.MIN_DIMENSION - 1, + _constants.MIN_DIMENSION - 1, + 0.5, + (_constants.MIN_DIMENSION, _constants.MIN_DIMENSION), + id='scale_belowMIN_DIMENSION', + ), + pytest.param( + _constants.MAX_DIMENSION + 1, + _constants.MAX_DIMENSION + 1, + 2.0, + (_constants.MAX_DIMENSION, _constants.MAX_DIMENSION), + id='scale_aboveMAX_DIMENSION', + ), + ], +) +def test_scale_by_percentage( + width, height, pct, expected, +): + assert _util.scale_by_percentage( + width, height, pct, + ) == expected + + +@pytest.mark.parametrize( + 'width, height, max_dim, expected', + [ + # pytest.param( + # 100, 200, 400, (200, 400), + # id='scale_up_to_max_dimension_horizontally', + # ), + # pytest.param( + # 200, 100, 400, (400, 200), + # id='scale_up_to_max_dimension_vertically', + # ), + # pytest.param( + # 400, 64, 400, (400, 64), + # id='no_scale_up_needed_with_max_dimension_width', + # ), + # pytest.param( + # 64, 400, 400, (64, 400), + # id='no_scale_up_needed_with_max_dimension_height', + # ), + # pytest.param( + # _constants.MIN_DIMENSION, + # _constants.MIN_DIMENSION, + # _constants.MAX_DIMENSION, + # (_constants.MAX_DIMENSION, _constants.MAX_DIMENSION), + # id='scale_from_min_to_max', + # ), + # pytest.param( + # _constants.MAX_DIMENSION, + # _constants.MAX_DIMENSION, + # _constants.MIN_DIMENSION, + # (_constants.MIN_DIMENSION, _constants.MIN_DIMENSION), + # id='scale_from_max_to_min', + # ), + pytest.param( + _constants.MIN_DIMENSION, 32, _constants.MIN_DIMENSION, + (128, _constants.MIN_DIMENSION), + id='scale_below_min_height_dimension_clamps_retains_ar', + ), + pytest.param( + 32, _constants.MIN_DIMENSION, _constants.MIN_DIMENSION, + (_constants.MIN_DIMENSION, 128), + id='scale_below_min_width_dimension_clamps_retains_ar', + ), + # pytest.param( + # _constants.MAX_DIMENSION, 4096, _constants.MAX_DIMENSION, + # (1024, _constants.MAX_DIMENSION), + # id='scale_above_max_height_dimension_clamps_retains_ar', + # ), + # pytest.param( + # 4096, _constants.MAX_DIMENSION, _constants.MAX_DIMENSION, + # (_constants.MAX_DIMENSION, 1024), + # id='scale_above_max_width_dimension_clamps_retains_ar', + # ), + # pytest.param( + # 64, 64, _constants.MIN_DIMENSION - 1, + # (_constants.MIN_DIMENSION, _constants.MIN_DIMENSION), + # id='scale_dimension_below_min_dimension_clamps_retains_ar', + # ), + # pytest.param( + # 64, 64, _constants.MAX_DIMENSION + 1, + # (_constants.MAX_DIMENSION, _constants.MAX_DIMENSION), + # id='scale_dimension_above_max_dimension_clamps_retains_ar', + # ), + ], +) +def test_scale_dimensions_to_max_dim( + width, height, max_dim, expected, +): + assert _util.scale_dimensions_to_max_dim( + width, height, max_dim, + ) == expected + + +@pytest.mark.parametrize( + 'width, height, min_dim, expected', + [ + pytest.param( + 100, 200, 400, (400, 800), + id='scale_up_to_min_dimension_with_ar_preservation', + ), + pytest.param( + 200, 100, 400, (800, 400), + id='scale_up_to_min_dimension_with_ar_preservation', + ), + pytest.param( + 100, 100, 400, (400, 400), + id='no_scale_up_needed_with_min_dimension', + ), + pytest.param( + _constants.MIN_DIMENSION, _constants.MIN_DIMENSION, _constants.MAX_DIMENSION, + (_constants.MAX_DIMENSION, _constants.MAX_DIMENSION), + id='scale_up_to_max_dimension_with_ar_preservation', + ), + pytest.param( + _constants.MAX_DIMENSION, _constants.MAX_DIMENSION, _constants.MIN_DIMENSION, + (_constants.MIN_DIMENSION, _constants.MIN_DIMENSION), + id='scale_down_to_min_dimension_with_ar_preservation', + ), + pytest.param( + 100, 100, _constants.MAX_DIMENSION, + (_constants.MAX_DIMENSION, _constants.MAX_DIMENSION), + id='scale_up_to_max_dimension_with_ar_preservation', + ), + ], +) +def test_scale_dimensions_to_min_dim( + width, height, min_dim, expected, +): + assert _util.scale_dimensions_to_min_dim( + width, height, min_dim, + ) == expected + + +class SharedOpts: + def __init__(self, options=None, defaults=None): + self.options = options or {} + self.defaults = defaults or {} + + def __getattr__(self, key): + try: + return self.options[key] + except KeyError: + raise AttributeError() + + def get_default(self, key): + return self.defaults.get(key, None) + + +def test_safe_opt_util(): + shared_opts = SharedOpts(options={'key': 'value'}) + assert _util.safe_opt_util(shared_opts, 'key', {}) == 'value' + + +def test_safe_opt_util_none(): + shared_opts = SharedOpts(options={'key': None}, defaults={'key': 'value'}) + assert _util.safe_opt_util(shared_opts, 'key', {}) == 'value' + + +def testsafe_opt_util_default_a(): + shared_opts = SharedOpts( + defaults={'key': 'default_a'}, + ) + assert _util.safe_opt_util( + shared_opts, 'key', {'key': 'default_b'}, + ) == 'default_a' + + +def test_safe_opt_util_default_b(): + shared_opts = SharedOpts(defaults={'key': None}) + assert _util.safe_opt_util( + shared_opts, 'key', {'key': 'default_b'}, + ) == 'default_b' + + +@pytest.mark.parametrize( + 'options', ({'key': None}, {}), +) +def test_safe_opt_safe_return_no_defaults_b(options): + shared_opts = SharedOpts(options=options) + assert _util.safe_opt_util(shared_opts, 'unknown_key', {}) is None + + +@pytest.mark.parametrize( + 'value, expected', [ + (0, 0), + (7, 8), + (10, 8), + (16, 16), + (23, 24), + (32, 32), + (33, 32), + (100, 96), + (10.5, 8), + (15.3, 16), + (21.8, 24), + (33.9, 32), + (98.7, 96), + ], +) +def test_round_to_multiple_of_8(value, expected): + assert _util.round_to_multiple_of_8(value) == expected + + +@pytest.mark.parametrize( + 'width, height, aspect_ratio, expected', [ + (100, 100, 1.0, (96, 96)), + (3000, 2000, 1.5, (2048, 1368)), + (500, 8000, 0.5, (1024, 2048)), + (500, 300, 2.0, (496, 304)), + (100, 200, 0.5, (96, 200)), + (500, 500, 1.2, (496, 496)), + (2048, 2048, 1.0, (2048, 2048)), + (2049, 2048, 1.0, (2048, 2048)), + (2048, 2049, 1.0, (2048, 2048)), + (2049, 2049, 1.0, (2048, 2048)), + (63, 63, 1.0, (64, 64)), + (2050, 2050, 1.0, (2048, 2048)), + (63, 64, 1.0, (64, 64)), + (64, 63, 1.0, (64, 64)), + (64, 64, 1.0, (64, 64)), + (63, 63, 1.0, (64, 64)), + (2050, 63, 1.0, (2048, 2048)), + (63, 2050, 1.0, (2048, 2048)), + (2050, 2050, 0.01, (64, 2048)), + (100.5, 100.5, 1.0, (104, 104)), + (200.3, 100.7, 0.5, (200, 104)), + ], +) +def test_clamp_to_boundaries(width, height, aspect_ratio, expected): + assert _util.clamp_to_boundaries(width, height, aspect_ratio) == expected diff --git a/extensions/sd-webui-aspect-ratio-helper/tox.ini b/extensions/sd-webui-aspect-ratio-helper/tox.ini new file mode 100644 index 0000000000000000000000000000000000000000..4c6cac4b77bc3e9d6f668da6c3ce61e3c1551929 --- /dev/null +++ b/extensions/sd-webui-aspect-ratio-helper/tox.ini @@ -0,0 +1,6 @@ +[tox] +envlist = py310, py311 + +[testenv] +deps = pytest +commands = pytest diff --git a/extensions/sd-webui-cads/LICENSE b/extensions/sd-webui-cads/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7 --- /dev/null +++ b/extensions/sd-webui-cads/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/extensions/sd-webui-cads/README.md b/extensions/sd-webui-cads/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ba05315cc3ce5bf5c0bd8f6f3f4aa6b44688e263 --- /dev/null +++ b/extensions/sd-webui-cads/README.md @@ -0,0 +1,31 @@ +# sd-webui-cads +### An implementation of the method in *CADS: Unleashing the Diversity of Diffusion Models through Condition-Annealed Sampling* in Automatic1111 WebUI. +CADS greatly increases diversity of generated images by adding scheduled noise to the conditioning at inference time. + +![image](samples/comparison.png) + + +### PR's are welcome! + +## Feature / To-do List +- [x] SD XL support +- [x] SD 1.5 support +- [x] Hi-res fix support +- [x] Support restoring parameter values from infotext (Send to Txt2Img, Send to Img2Img, etc.) +- [x] Write infotext to image grids +- [x] X/Y/Z plot support +- [ ] ControlNet support + +## Credits +- The authors of the original paper for their method (https://arxiv.org/abs/2310.17347): + ``` + @inproceedings{ + sadat2024cads, + title={{CADS}: Unleashing the Diversity of Diffusion Models through Condition-Annealed Sampling}, + author={Seyedmorteza Sadat and Jakob Buhmann and Derek Bradley and Otmar Hilliges and Romann M. Weber}, + booktitle={The Twelfth International Conference on Learning Representations}, + year={2024}, + url={https://openreview.net/forum?id=zMoNrajk2X} + } + ``` +- @udon-universe's extension templates (https://github.com/udon-universe/stable-diffusion-webui-extension-templates) diff --git a/extensions/sd-webui-cads/samples/comparison.png b/extensions/sd-webui-cads/samples/comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..1ef25bec6b1b9134c5382c6ad6373229a9a5c5f4 --- /dev/null +++ b/extensions/sd-webui-cads/samples/comparison.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c63aefad6f1a0ecf02c76a7e9d4c5795785e79cd53df6a9191fd2a0aac95f59 +size 3784688 diff --git a/extensions/sd-webui-cads/samples/grid-7069.png b/extensions/sd-webui-cads/samples/grid-7069.png new file mode 100644 index 0000000000000000000000000000000000000000..a4833224992e03865ee88d14ee1a8875735fd453 --- /dev/null +++ b/extensions/sd-webui-cads/samples/grid-7069.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94b2c319d96925defb9ddbffc83408d912b7db31151c76da1676d890c09aa68a +size 6414416 diff --git a/extensions/sd-webui-cads/samples/grid-7070.png b/extensions/sd-webui-cads/samples/grid-7070.png new file mode 100644 index 0000000000000000000000000000000000000000..fb3db264f95267d2695276d29807a45beab83c41 --- /dev/null +++ b/extensions/sd-webui-cads/samples/grid-7070.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04f444d0bcd26f8ede53a84ffb2443260f3837b387b874c0abeecaba3f9cdea9 +size 6050876 diff --git a/extensions/sd-webui-cads/scripts/__pycache__/cads.cpython-310.pyc b/extensions/sd-webui-cads/scripts/__pycache__/cads.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f81d7954ec4512891471901ddedb6e27b830428e Binary files /dev/null and b/extensions/sd-webui-cads/scripts/__pycache__/cads.cpython-310.pyc differ diff --git a/extensions/sd-webui-cads/scripts/cads.py b/extensions/sd-webui-cads/scripts/cads.py new file mode 100644 index 0000000000000000000000000000000000000000..e32561a124d98f5c5373cfa96cfbb3abd748af16 --- /dev/null +++ b/extensions/sd-webui-cads/scripts/cads.py @@ -0,0 +1,278 @@ +import logging +from os import environ +import modules.scripts as scripts +import gradio as gr +import numpy as np +from collections import OrderedDict +from typing import Union + +from modules import script_callbacks +from modules.script_callbacks import CFGDenoiserParams +try: + from modules.rng import randn_like +except ImportError: + from torch import randn_like + +import torch + +logger = logging.getLogger(__name__) +logger.setLevel(environ.get("SD_WEBUI_LOG_LEVEL", logging.INFO)) + +""" + +An implementation of CADS: Unleashing the Diversity of Diffusion Models through Condition-Annealed Sampling for Automatic1111 Webui + +@inproceedings{ + sadat2024cads, + title={{CADS}: Unleashing the Diversity of Diffusion Models through Condition-Annealed Sampling}, + author={Seyedmorteza Sadat and Jakob Buhmann and Derek Bradley and Otmar Hilliges and Romann M. Weber}, + booktitle={The Twelfth International Conference on Learning Representations}, + year={2024}, + url={https://openreview.net/forum?id=zMoNrajk2X} +} + +Author: v0xie +GitHub URL: https://github.com/v0xie/sd-webui-cads + +""" +class CADSExtensionScript(scripts.Script): + # Extension title in menu UI + def title(self): + return "CADS" + + # Decide to show menu in txt2img or img2img + def show(self, is_img2img): + return scripts.AlwaysVisible + + # Setup menu ui detail + def ui(self, is_img2img): + with gr.Accordion('CADS', open=False): + active = gr.Checkbox(value=False, default=False, label="Active", elem_id='cads_active') + rescale = gr.Checkbox(value=True, default=True, label="Rescale CFG", elem_id = 'cads_rescale') + with gr.Row(): + step_start = gr.Slider(value = 0, minimum = 0, maximum = 50, step = 1, label="Tau 1 Step", elem_id = 'cads_tau1_step', info="Step to start. (0=disable)") + step_stop = gr.Slider(value = 0, minimum = 0, maximum = 50, step = 1, label="Tau 2 Step", elem_id = 'cads_tau1_step', info="Step to start. (0=disable)") + with gr.Row(): + t1 = gr.Slider(value = 0.6, minimum = 0.0, maximum = 1.0, step = 0.05, label="Tau 1", elem_id = 'cads_tau1', info="Step to start interpolating from full strength. Default 0.6") + t2 = gr.Slider(value = 0.9, minimum = 0.0, maximum = 1.0, step = 0.05, label="Tau 2", elem_id = 'cads_tau2', info="Step to stop affecting image. Default 0.9") + with gr.Row(): + noise_scale = gr.Slider(value = 0.25, minimum = 0.0, maximum = 1.0, step = 0.01, label="Noise Scale", elem_id = 'cads_noise_scale', info='Scale of noise injected at every time step, default 0.25, recommended <= 0.3') + mixing_factor= gr.Slider(value = 1.0, minimum = 0.0, maximum = 1.0, step = 0.01, label="Mixing Factor", elem_id = 'cads_mixing_factor', info='Regularization factor, lowering this will increase the diversity of the images with more chance of divergence, default 1.0') + with gr.Accordion('Experimental', open=False): + apply_to_hr_pass = gr.Checkbox(value=False, default=False, label="Apply to Hires. Fix", elem_id='cads_hr_fix_active', info='Requires a very high denoising value to work. Default False') + active.do_not_save_to_config = True + rescale.do_not_save_to_config = True + t1.do_not_save_to_config = True + t2.do_not_save_to_config = True + noise_scale.do_not_save_to_config = True + mixing_factor.do_not_save_to_config = True + apply_to_hr_pass.do_not_save_to_config = True + self.infotext_fields = [ + (active, lambda d: gr.Checkbox.update(value='CADS Active' in d)), + (rescale, 'CADS Rescale'), + (step_start, 'CADS Tau 1 Step'), + (step_stop, 'CADS Tau 2 Step'), + (t1, 'CADS Tau 1'), + (t2, 'CADS Tau 2'), + (noise_scale, 'CADS Noise Scale'), + (mixing_factor, 'CADS Mixing Factor'), + (apply_to_hr_pass, 'CADS Apply To Hires. Fix'), + ] + self.paste_field_names = [ + 'cads_active', + 'cads_rescale', + 'cads_tau1', + 'cads_tau2', + 'cads_noise_scale', + 'cads_mixing_factor', + 'cads_hr_fix_active', + ] + return [active, step_start, step_stop, t1, t2, noise_scale, mixing_factor, rescale, apply_to_hr_pass] + + def before_process_batch(self, p, active, step_start, step_stop, t1, t2, noise_scale, mixing_factor, rescale, apply_to_hr_pass, *args, **kwargs): + self.unhook_callbacks() + active = getattr(p, "cads_active", active) + if active is False: + return + steps = getattr(p, "steps", -1) + if step_start != 0: + step_start = getattr(p, "cads_tau1_step", step_start) + t1 = max(min(step_start / steps, 1.0), 0.0) + else: + t1 = getattr(p, "cads_tau1", t1) + + if step_stop != 0: + step_stop = getattr(p, "cads_tau2_step", step_stop) + t2 = max(min(step_stop / steps, 1.0), 0.0) + else: + t2 = getattr(p, "cads_tau2", t2) + + noise_scale = getattr(p, "cads_noise_scale", noise_scale) + mixing_factor = getattr(p, "cads_mixing_factor", mixing_factor) + rescale = getattr(p, "cads_rescale", rescale) + apply_to_hr_pass = getattr(p, "cads_hr_fix_active", apply_to_hr_pass) + + first_pass_steps = getattr(p, "steps", -1) + if first_pass_steps <= 0: + logger.error("Steps not set, disabling CADS") + return + + p.extra_generation_params.update({ + "CADS Active": active, + "CADS Tau 1 Step": step_start, + "CADS Tau 2 Step": step_stop, + "CADS Tau 1": t1, + "CADS Tau 2": t2, + "CADS Noise Scale": noise_scale, + "CADS Mixing Factor": mixing_factor, + "CADS Rescale": rescale, + "CADS Apply To Hires. Fix": apply_to_hr_pass, + }) + self.create_hook(p, active, t1, t2, noise_scale, mixing_factor, rescale, first_pass_steps) + + def create_hook(self, p, active, t1, t2, noise_scale, mixing_factor, rescale, total_sampling_steps, *args, **kwargs): + # Use lambda to call the callback function with the parameters to avoid global variables + y = lambda params: self.on_cfg_denoiser_callback(params, t1=t1, t2=t2, noise_scale=noise_scale, mixing_factor=mixing_factor, rescale=rescale, total_sampling_steps=total_sampling_steps) + + logger.debug('Hooked callbacks') + script_callbacks.on_cfg_denoiser(y) + script_callbacks.on_script_unloaded(self.unhook_callbacks) + + def postprocess_batch(self, p, active, t1, t2, noise_scale, mixing_factor, rescale, apply_to_hr_pass, *args, **kwargs): + self.unhook_callbacks() + + def unhook_callbacks(self): + logger.debug('Unhooked callbacks') + script_callbacks.remove_current_script_callbacks() + + def cads_linear_schedule(self, t, tau1, tau2): + """ CADS annealing schedule function """ + if t <= tau1: + return 1.0 + if t>= tau2: + return 0.0 + gamma = (tau2-t)/(tau2-tau1) + return gamma + + def add_noise(self, y, gamma, noise_scale, psi, rescale=False): + """ CADS adding noise to the condition + + Arguments: + y: Input conditioning + gamma: Noise level w.r.t t + noise_scale (float): Noise scale + psi (float): Rescaling factor + rescale (bool): Rescale the condition + """ + y_mean, y_std = torch.mean(y), torch.std(y) + y = np.sqrt(gamma) * y + noise_scale * np.sqrt(1-gamma) * randn_like(y) + if rescale: + y_scaled = (y - torch.mean(y)) / torch.std(y) * y_std + y_mean + if not torch.isnan(y_scaled).any(): + y = psi * y_scaled + (1 - psi) * y + else: + logger.debug("Warning: NaN encountered in rescaling") + return y + + def on_cfg_denoiser_callback(self, params: CFGDenoiserParams, t1, t2, noise_scale, mixing_factor, rescale, total_sampling_steps): + sampling_step = params.sampling_step + total_sampling_step = total_sampling_steps + text_cond = params.text_cond + text_uncond = params.text_uncond + + t = 1.0 - max(min(sampling_step / total_sampling_step, 1.0), 0.0) # Algorithms assumes we start at 1.0 and go to 0.0 + gamma = self.cads_linear_schedule(t, t1, t2) + # SD 1.5 + if isinstance(text_cond, torch.Tensor) and isinstance(text_uncond, torch.Tensor): + params.text_cond = self.add_noise(text_cond, gamma, noise_scale, mixing_factor, rescale) + params.text_uncond = self.add_noise(text_uncond, gamma, noise_scale, mixing_factor, rescale) + # SDXL + elif isinstance(text_cond, Union[dict, OrderedDict]) and isinstance(text_uncond, Union[dict, OrderedDict]): + params.text_cond['crossattn'] = self.add_noise(text_cond['crossattn'], gamma, noise_scale, mixing_factor, rescale) + params.text_uncond['crossattn'] = self.add_noise(text_uncond['crossattn'], gamma, noise_scale, mixing_factor, rescale) + params.text_cond['vector'] = self.add_noise(text_cond['vector'], gamma, noise_scale, mixing_factor, rescale) + params.text_uncond['vector'] = self.add_noise(text_uncond['vector'], gamma, noise_scale, mixing_factor, rescale) + else: + logger.error('Unknown text_cond type') + pass + + def before_hr(self, p, *args): + self.unhook_callbacks() + + params = getattr(p, "extra_generation_params", None) + if not params: + logger.error("Missing attribute extra_generation_params") + return + + active = params.get("CADS Active", False) + if active is False: + return + + apply_to_hr_pass = params.get("CADS Apply To Hires. Fix", False) + if apply_to_hr_pass is False: + logger.debug("Disabled for hires. fix") + return + + t1 = params.get("CADS Tau 1", None) + t2 = params.get("CADS Tau 2", None) + noise_scale = params.get("CADS Noise Scale", None) + mixing_factor = params.get("CADS Mixing Factor", None) + rescale = params.get("CADS Rescale", None) + + if t1 is None or t2 is None or noise_scale is None or mixing_factor is None or rescale is None: + logger.error("Missing needed parameters for Hires. fix") + return + + hr_pass_steps = getattr(p, "hr_second_pass_steps", -1) + if hr_pass_steps < 0: + logger.error("Attribute hr_second_pass_steps not found") + return + if hr_pass_steps == 0: + logger.debug("Using first pass step count for hires. fix") + hr_pass_steps = getattr(p, "steps", -1) + + logger.debug("Enabled for hi-res fix with %i steps, re-hooking CADS", hr_pass_steps) + self.create_hook(p, active, t1, t2, noise_scale, mixing_factor, rescale, hr_pass_steps) + + +# XYZ Plot +# Based on @mcmonkey4eva's XYZ Plot implementation here: https://github.com/mcmonkeyprojects/sd-dynamic-thresholding/blob/master/scripts/dynamic_thresholding.py +def cads_apply_override(field, boolean: bool = False): + def fun(p, x, xs): + if boolean: + x = True if x.lower() == "true" else False + setattr(p, field, x) + return fun + +def cads_apply_field(field): + def fun(p, x, xs): + if not hasattr(p, "cads_active"): + setattr(p, "cads_active", True) + setattr(p, field, x) + + return fun + +def make_axis_options(): + xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ in ("xyz_grid.py", "scripts.xyz_grid")][0].module + # Add the boolean choice function to SD.Next XYZ Grid script + if not hasattr(xyz_grid, "boolean_choice"): + xyz_grid.boolean_choice = lambda reverse=False: ["True", "False"] if not reverse else ["False", "True"] + extra_axis_options = { + xyz_grid.AxisOption("[CADS] Active", str, cads_apply_override('cads_active', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[CADS] Rescale CFG", str, cads_apply_override('cads_rescale', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[CADS] Tau 1", float, cads_apply_field("cads_tau1")), + xyz_grid.AxisOption("[CADS] Tau 2", float, cads_apply_field("cads_tau2")), + xyz_grid.AxisOption("[CADS] Noise Scale", float, cads_apply_field("cads_noise_scale")), + xyz_grid.AxisOption("[CADS] Mixing Factor", float, cads_apply_field("cads_mixing_factor")), + xyz_grid.AxisOption("[CADS] Apply to Hires. Fix", str, cads_apply_override('cads_hr_fix_active', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + } + if not any("[CADS]" in x.label for x in xyz_grid.axis_options): + xyz_grid.axis_options.extend(extra_axis_options) + +def callback_before_ui(): + try: + make_axis_options() + except: + logger.exception("CADS: Error while making axis options") + +script_callbacks.on_before_ui(callback_before_ui) diff --git a/extensions/sd-webui-color-enhance/.gitignore b/extensions/sd-webui-color-enhance/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ed8ebf583f771da9150c35db3955987b7d757904 --- /dev/null +++ b/extensions/sd-webui-color-enhance/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/extensions/sd-webui-color-enhance/LICENSE b/extensions/sd-webui-color-enhance/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d41c0bd98feb776ef7d81efbc1b5447e0284a187 --- /dev/null +++ b/extensions/sd-webui-color-enhance/LICENSE @@ -0,0 +1,232 @@ +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS + +0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on the Program. + +To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. + +A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. +A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. + +14. Revised Versions of this License. +The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . diff --git a/extensions/sd-webui-color-enhance/README.md b/extensions/sd-webui-color-enhance/README.md new file mode 100644 index 0000000000000000000000000000000000000000..68305c4df1af9748f67fd68c9cd07865fd7f8ff6 --- /dev/null +++ b/extensions/sd-webui-color-enhance/README.md @@ -0,0 +1,27 @@ +(Forked from https://git.mmaker.moe/mmaker/sd-webui-color-enhance in order to host on GitHub.) + +# Color Enhance + +Script for [AUTOMATIC1111/stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) to enhance colors. + +This is the same algorithm GIMP/GEGL uses for color enhancement. The gist of this implementation is that it converts the color space to [CIELCh(ab)](https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_(CIELCh)) and normalizes the chroma (or ["colorfulness"](https://en.wikipedia.org/wiki/Colorfulness)) component. Original source can be found in the link below. + +https://gitlab.gnome.org/GNOME/gegl/-/blob/master/operations/common/color-enhance.c + +In my personal (and possibly subjective) opinion, this removes the need to select a VAE based solely on color, and instead select one on it's true merit of accurately converting the latent space to a pixel representation of the image. + +### Example + +Left is the raw output (with a manually selected VAE!), right is with the enhancement (at a full strength of `1`). + +![Comparison](https://files.catbox.moe/4ze471.jpg) + +### Notes + +To use this directly in the txt2img and img2img tabs, go to Settings -> Postprocessing, and add this script to the `Enable postprocessing operations in txt2img and img2img tabs` option. Save and restart the web UI to apply the changes. + +This should not cause any issues with hires fix, inpainting, etc, as the postprocessing pipeline in the web UI only applies postprocessing scripts directly before returning the final image(s). There should also be no quality degradation from re-using the same image multiple times (as in inpainting), as operations are performed in floating point before being converted back to uint8. If you want to have peace of mind, you can always run this postprocessing script in the `Extras` tab as a last step. + +If you were to test this out with an image you believe is already heavily saturated/colorful, and apply this at full strength, you should see essentially 0 change to the image, an indication of how working in this color space removes the need to worry about oversaturating or blowing out an image. + +As far as I know, this technique dates back to the early 2000s. Examples can be seen [here](https://en.wikipedia.org/wiki/Colorfulness#Chroma) and [here](https://en.wikipedia.org/wiki/HSL_and_HSV#Disadvantages) which show how this is an improvement over traditional HSL/HSV color grading. There has since been [many](https://en.wikipedia.org/wiki/Color_appearance_model#Color_appearance_models) new developments in this area, but I haven't quite yet looked into these, and this already seems to perform very well. An improvement may not be seen in new techniques anyway, as SD only outputs 24-bit RGB images, ultimately leaving color grading options limited. \ No newline at end of file diff --git a/extensions/sd-webui-color-enhance/scripts/__pycache__/color_enhance.cpython-310.pyc b/extensions/sd-webui-color-enhance/scripts/__pycache__/color_enhance.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b68ef8f0c4dcdf137a3826ace8335b4aaf8d852d Binary files /dev/null and b/extensions/sd-webui-color-enhance/scripts/__pycache__/color_enhance.cpython-310.pyc differ diff --git a/extensions/sd-webui-color-enhance/scripts/color_enhance.py b/extensions/sd-webui-color-enhance/scripts/color_enhance.py new file mode 100644 index 0000000000000000000000000000000000000000..339567e1a217d426b3d681737e122a4bbbcc4ed5 --- /dev/null +++ b/extensions/sd-webui-color-enhance/scripts/color_enhance.py @@ -0,0 +1,38 @@ +import gradio as gr +import imageio.core.util +import numpy as np +import skimage.color +from PIL import Image + +from modules import scripts_postprocessing +from modules.ui_components import FormRow + + +imageio.core.util._precision_warn = lambda *args, **kwargs: None + + +class ScriptPostprocessingColorEnhance(scripts_postprocessing.ScriptPostprocessing): + name = "Color Enhance" + order = 30000 + + def ui(self): + with FormRow(): + strength = gr.Slider(label="Color Enhance strength", minimum=0, maximum=1, step=0.01, value=0) + return { "strength": strength } + + def process(self, pp: scripts_postprocessing.PostprocessedImage, strength): + if strength == 0: + return + + info_bak = {} if not hasattr(pp.image, "info") else pp.image.info + pp.image = self._color_enhance(pp.image, strength) + pp.image.info = info_bak + pp.info["Color Enhance"] = strength + + def _lerp(self, a: float, b: float, t: float) -> float: + return (1 - t) * a + t * b + + def _color_enhance(self, arr, strength: float = 1) -> Image.Image: + lch = skimage.color.lab2lch(lab=skimage.color.rgb2lab(rgb=np.array(arr, dtype=np.uint8))) + lch[:, :, 1] *= 100/(self._lerp(100, lch[:, :, 1].max(), strength)) # Normalize chroma component + return Image.fromarray(np.array(skimage.color.lab2rgb(lab=skimage.color.lch2lab(lch=lch)) * 255, dtype=np.uint8)) \ No newline at end of file diff --git a/extensions/sd-webui-colorful-noise/LICENSE b/extensions/sd-webui-colorful-noise/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7 --- /dev/null +++ b/extensions/sd-webui-colorful-noise/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/extensions/sd-webui-colorful-noise/README.md b/extensions/sd-webui-colorful-noise/README.md new file mode 100644 index 0000000000000000000000000000000000000000..51f6586f57c73d573d3a0505574e70db5811b6b3 --- /dev/null +++ b/extensions/sd-webui-colorful-noise/README.md @@ -0,0 +1,7 @@ +# sd-webui-colorful-noise +Colored Noise Extension (TESTING) + +--- +![image3](https://github.com/antis0007/sd-webui-colorful-noise/assets/31860133/67c5c412-4a9a-428b-8cec-9528c02c9d9e) +![image](https://github.com/antis0007/sd-webui-colorful-noise/assets/31860133/6a963401-eb5d-49a3-ae28-d89c95494e9a) +image2 diff --git a/extensions/sd-webui-colorful-noise/scripts/__pycache__/colorful_noise.cpython-310.pyc b/extensions/sd-webui-colorful-noise/scripts/__pycache__/colorful_noise.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00aa6f9489fd69ab8794d8dfe3e2062b4dd7c59c Binary files /dev/null and b/extensions/sd-webui-colorful-noise/scripts/__pycache__/colorful_noise.cpython-310.pyc differ diff --git a/extensions/sd-webui-colorful-noise/scripts/colorful_noise.py b/extensions/sd-webui-colorful-noise/scripts/colorful_noise.py new file mode 100644 index 0000000000000000000000000000000000000000..6a8a70b29abfb2473236f469e41a520238e6dae0 --- /dev/null +++ b/extensions/sd-webui-colorful-noise/scripts/colorful_noise.py @@ -0,0 +1,260 @@ +import modules.scripts as scripts +import gradio as gr +import os +import torch +import numpy as np + +from modules import images, devices, script_callbacks +import modules.processing as processing +from modules.shared import opts, cmd_opts, state +import modules.shared as shared + +enabled = False +normalize = False +normalize_weights = [0,0] + +color = [255,255,255] +strength = 1.0 + +def normalize_noise(noise, weight): + print("Normalize: " + str(normalize)) + #per channel normalization + for ix, channel in enumerate(noise): + #we noticed a big shift in the i2i dynamic range, so we are normalizing each channel separately to fix this + #all channels should be normally distributed, so we can use the mean and std to normalize + print("Channel: " + str(ix)) + print("Mean: " + str(torch.mean(channel))) + print("Std: " + str(torch.std(channel))) + channel_norm = (channel - torch.mean(channel)) / torch.std(channel) + print("Final Mean: " + str(torch.mean(channel_norm))) + print("Final Std: " + str(torch.std(channel_norm))) + #mix in the normalized channel with the original channel + noise[ix] = (channel_norm * weight) + (channel * (1-weight)) + return noise + +def create_random_tensors(shape, seeds, subseeds=None, subseed_strength=0.0, seed_resize_from_h=0, seed_resize_from_w=0, p=None): + eta_noise_seed_delta = opts.eta_noise_seed_delta or 0 + xs = [] + if p is not None and p.sampler is not None and (len(seeds) > 1 and opts.enable_batch_seeds or eta_noise_seed_delta > 0): + sampler_noises = [[] for _ in range(p.sampler.number_of_needed_noises(p))] + else: + sampler_noises = None + + for i, seed in enumerate(seeds): + noise_shape = shape if seed_resize_from_h <= 0 or seed_resize_from_w <= 0 else (shape[0], seed_resize_from_h//8, seed_resize_from_w//8) + + subnoise = None + if subseeds is not None: + subseed = 0 if i >= len(subseeds) else subseeds[i] + + subnoise = devices.randn(subseed, noise_shape) + + noise = devices.randn(seed, noise_shape) + global enabled + global normalize + global normalize_weights + global color + + #Normalize before: + if normalize == True: + noise = normalize_noise(noise, normalize_weights[0]) + print("Enabled: " + str(enabled)) + if enabled == True: + + #R - [+0.9, -0.9, -0.9], #L2 + #G - [-0.9, +0.9, -0.9], #L2 + #B - [-0.9, -0.9, +0.9], #L2 + + #C - [-0.9, +0.9, +0.9], #L2 + #M - [+0.9, -0.9, +0.9], #L2 + #Y - [+0.9, +0.9, -0.9], #L2 + + #Half-Strength Colors + #Purple - [0, -0.9, +0.9], #L2 (128,0,255) + #Lime - [0, +0.9, -0.9], #L2 (0,255,128) + + #Red-Yellow - [+0.9, 0, -0.9], #L2 (255,128,0) (ORANGE) + #Blue-Green - [-0.9, 0, +0.9], #L2 (0,128,255) + + #Green-Blue - [-0.9, +0.9, 0], #L2 (0,255,128) + #Red-Purple - [+0.9, -0.9, 0], #L2 (255,0,128) + + + + + + #Orange is composed of Red and Yellow + #Orange - [-0.9, 0, +0.9], #L2 + + #Rl, Gl, and Bl will be functions + #so the incoming L2 row would be picked off and go thru the function and then the coeffs could be einsummed, I guess + #Rl = (-0.007 * R + 0.9226) + #Gl = (-0.007 * G + 0.9226) + #Bl = (-0.007 * B + 0.9226) + l = 0.18215; c = 0.28; s = 0.17 + R = color[0] + G = color[1] + B = color[2] + vR = R / 255 + vG = G / 255 + vB = B / 255 + Rl = 0.9 * (R/255) - 0.9 * (1-(R/255)) + Gl = 0.9 * (G/255) - 0.9 * (1-(G/255)) + Bl = 0.9 * (B/255) - 0.9 * (1-(B/255)) + + #calculate how much + + #calculate the luminance + #lum = (0.2126 *vR) + (0.7152 * vG) + (0.0722 * vB) + lum = (R + G + B) / (3 * 255) + #scale to between -1 and 1 + lum = (lum * 2) - 1 + lum = lum * 0.6 + print("Lum: " + str(lum)) + #scale lum to between 0 and 1 + #lum = lum / 255 + + #calculate the Rl, Gl, and Bl values including the luminance + + + + + print("Rl: " + str(Rl)) + print("Gl: " + str(Gl)) + print("Bl: " + str(Bl)) + # R G B + #[ +, -, -], #L0 - Cyan <-> Red + #[ +, +, -], #L1 - Blue <-> Yellow + #[ -, +, +], #L2 - Red <-> Cyan + #[ -, +, -], #L3 - Magenta <-> Green + + #l0 = (G + B) to R + #l1 = B to (R + G) + #l2 = R to (G + B) + #l3 = G to (R + B) + coefs = torch.tensor([ + # +/- R G B + [ +l*Rl+lum, -l*Gl+lum, -l*Bl+lum], #L0 + [ +c*Rl+lum, +c*Gl+lum, -c*Bl+lum], #L1 + [ -Rl, +Gl, +Bl], #L2 + [ -s*Rl, +s*Gl, -s*Bl], #L3 + #[ s, -s, s], #L3 + #[ -Rl, +Gl, -Bl], #L3 + ]).to(noise.device) + """ coefs = torch.tensor([ + # +/- R G B + [ -l, +l, +l], #L0 + [ -c, -c, +c], #L1 + [ +Rl, -Gl, -Bl], #L2 + [ +s, -s, +s] #L3 + ]).to(noise.device) """ + offsets = [0, 0, 0, 0] + #coefs = coefs * strength + offsets[0] = sum(coefs[0]*strength) + offsets[1] = sum(coefs[1]*strength) + offsets[2] = sum(coefs[2]*strength) + offsets[3] = sum(coefs[3]*strength) + print(coefs) + print(offsets) + offsets = torch.tensor(offsets).to(noise.device) + #apply each layer of offsets to each of the respective layers of noise + for i in range(0,4): + noise[i] = noise[i] + offsets[i] + + + #Normalize after: + if normalize == True: + noise = normalize_noise(noise, normalize_weights[1]) + + if subnoise is not None: + noise = processing.slerp(subseed_strength, noise, subnoise) + + if noise_shape != shape: + x = devices.randn(seed, shape) + dx = (shape[2] - noise_shape[2]) // 2 + dy = (shape[1] - noise_shape[1]) // 2 + w = noise_shape[2] if dx >= 0 else noise_shape[2] + 2 * dx + h = noise_shape[1] if dy >= 0 else noise_shape[1] + 2 * dy + tx = 0 if dx < 0 else dx + ty = 0 if dy < 0 else dy + dx = max(-dx, 0) + dy = max(-dy, 0) + + x[:, ty:ty+h, tx:tx+w] = noise[:, dy:dy+h, dx:dx+w] + noise = x + + if sampler_noises is not None: + cnt = p.sampler.number_of_needed_noises(p) + + if eta_noise_seed_delta > 0: + torch.manual_seed(seed + eta_noise_seed_delta) + + for j in range(cnt): + sampler_noises[j].append(devices.randn_without_seed(tuple(noise_shape))) + + xs.append(noise) + + if sampler_noises is not None: + p.sampler.sampler_noises = [torch.stack(n).to(shared.device) for n in sampler_noises] + + x = torch.stack(xs).to(shared.device) + return x + +class ColorfulNoiseScript(scripts.Script): + def title(self): + return "Colorful Noise" + def show(self, is_img2img): + return scripts.AlwaysVisible + def ui(self, is_img2img): + with gr.Accordion('Colorful Noise', open=False): + #enable or disable the extension, checkbox + with gr.Row(): + enabled_button = gr.Checkbox(label="Enable CN", value=False) + normalize_button = gr.Checkbox(label="Enable Normalization", value=False) + color_button = gr.ColorPicker(label="ColorSelector") + with gr.Row(): + strength_slider = gr.Slider(-1, 2, step=0.1, label="Strength Slider",value=1) + with gr.Row(): + normalize_before_slider = gr.Slider(0, 1, step=0.01, label="Normalize Before", value=1) + normalize_after_slider = gr.Slider(0, 1, step=0.01, label="Normalize After", value=0.9) + return [enabled_button, normalize_button, color_button, strength_slider, normalize_before_slider, normalize_after_slider] + + def process(self, p, enabled_button, normalize_button, color_button, strength_slider, normalize_before_slider, normalize_after_slider): + global color + global enabled + global strength + global normalize + global normalize_weights + + + + if normalize_button == True: + normalize = True + normalize_weights = [normalize_before_slider, normalize_after_slider] + else: + normalize = False + normalize_weights = [0,0] + + + if enabled_button == True: + enabled = True + + strength = strength_slider + h = color_button.lstrip('#') + color = list(int(h[i:i+2], 16) for i in (0, 2, 4)) + print(color) + + #need to override the create_random_tensors function in processing.py, but it's not a class, it's just a function + #doing this just causes a recursion depth error + #fix the recursion error and override the function + + else: + enabled = False + strength = 0 + + processing.create_random_tensors = create_random_tensors + p.create_random_tensors = create_random_tensors + #proc = process_images(p) + #return proc + + diff --git a/extensions/sd-webui-convenience-util/.gitattributes b/extensions/sd-webui-convenience-util/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..dfe0770424b2a19faf507a501ebfc23be8f54e7b --- /dev/null +++ b/extensions/sd-webui-convenience-util/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/extensions/sd-webui-convenience-util/scripts/__pycache__/conv_ui.cpython-310.pyc b/extensions/sd-webui-convenience-util/scripts/__pycache__/conv_ui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35b156eb253f0f534ef0292a24b21f3522f3780c Binary files /dev/null and b/extensions/sd-webui-convenience-util/scripts/__pycache__/conv_ui.cpython-310.pyc differ diff --git a/extensions/sd-webui-convenience-util/scripts/conv_ui.py b/extensions/sd-webui-convenience-util/scripts/conv_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..b97b47c0b1b93642dbdbf67e39e61963676d82fb --- /dev/null +++ b/extensions/sd-webui-convenience-util/scripts/conv_ui.py @@ -0,0 +1,73 @@ +import gradio as gr +from PIL import Image + +def calculate_ratio(image, shorter_side_length: int): + width, height = image.size + if width < height: + new_width = shorter_side_length + new_height = int(shorter_side_length * height / width) + else: + new_height = shorter_side_length + new_width = int(shorter_side_length * width / height) + return new_width, new_height + +def calculate_pixel_ratios(image:Image.Image): + """ + Calculates required ratio (with respecting original aspect ratio) to match pixel count + """ + results = [] + original_pixel_count = image.size[0] * image.size[1] + for pixel_counts in [768**2, 1024**2]: + ratio_to_divide_squared = original_pixel_count / pixel_counts + ratio_to_divide = ratio_to_divide_squared ** 0.5 + new_width = int(image.size[0] / ratio_to_divide) + new_height = int(image.size[1] / ratio_to_divide) + results.append(f"{new_width} x {new_height} ({pixel_counts} pixels) (matched for {int(pixel_counts**0.5)} pixels)") + return results + +def calculate_ratios(image): + results = [] + for side_length in [512, 768, 896]: + new_width, new_height = calculate_ratio(image, side_length) + shorter_side = min(new_width, new_height) + longer_side = max(new_width, new_height) + ratio = longer_side / shorter_side + # get 1:x or x:1 ratio + if new_width == shorter_side: + ratio_str = f"1:{ratio}" + else: + ratio_str = f"{ratio}:1" + results.append(f"{new_width} x {new_height} ({ratio_str})") + return results + +def calculate_and_concat_ratios(image): + r1 = calculate_ratios(image) + r2 = calculate_pixel_ratios(image) + return r1 + r2 + +def on_ui_tab_called(): + with gr.Blocks() as calculator_interface: + with gr.Row(): + image = gr.Image(type="pil",source="upload") + ratio_results_512 = gr.Textbox(lines=1, label="Results for 512") + ratio_results_768 = gr.Textbox(lines=1, label="Results for 768") + ratio_results_1024 = gr.Textbox(lines=1, label="Results for 1024") + + pixel_ratio_result_768 = gr.Textbox(lines=1, label="Pixel Ratios for 768**2") + pixel_ratio_result_1024 = gr.Textbox(lines=1, label="Pixel Ratios for 1024**2") + #button = gr.Button(text="Calculate") + image.upload( + fn=calculate_and_concat_ratios, + inputs=[image], + outputs=[ratio_results_512, ratio_results_768, ratio_results_1024, pixel_ratio_result_768, pixel_ratio_result_1024] + ) + + return (calculator_interface, "calculator", "calculator_interface"), +try: + from modules import script_callbacks, postprocessing + script_callbacks.on_ui_tabs(on_ui_tab_called) +except (ImportError, ModuleNotFoundError): + # context not in webui, run as separate script + if __name__ == "__main__": + interface, _, _ = on_ui_tab_called()[0] + interface.launch() diff --git a/extensions/sd-webui-crop-extension/.gitignore b/extensions/sd-webui-crop-extension/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..1670801e7193c0b44430e3a8369f4a57102468cd --- /dev/null +++ b/extensions/sd-webui-crop-extension/.gitignore @@ -0,0 +1,2 @@ + +*.pyc \ No newline at end of file diff --git a/extensions/sd-webui-crop-extension/install.py b/extensions/sd-webui-crop-extension/install.py new file mode 100644 index 0000000000000000000000000000000000000000..46fb87ba6df2f2e482982862f238e6b0eb042825 --- /dev/null +++ b/extensions/sd-webui-crop-extension/install.py @@ -0,0 +1,6 @@ +import launch +# launch is imported in context of webui +if not launch.is_installed("dghs-imgutils") and not launch.is_installed("dghs-imgutils[gpu]"): + import torch.cuda as cuda + print("Installing dghs-imgutils") + launch.run_pip("install dghs-imgutils[gpu]" if cuda.is_available() else "install dghs-imgutils") \ No newline at end of file diff --git a/extensions/sd-webui-crop-extension/scripts/__pycache__/ui.cpython-310.pyc b/extensions/sd-webui-crop-extension/scripts/__pycache__/ui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6721c54d0882512e817e4fe89c86c8bee4c6bb7b Binary files /dev/null and b/extensions/sd-webui-crop-extension/scripts/__pycache__/ui.cpython-310.pyc differ diff --git a/extensions/sd-webui-crop-extension/scripts/ui.py b/extensions/sd-webui-crop-extension/scripts/ui.py new file mode 100644 index 0000000000000000000000000000000000000000..d7a250a2b083dab1711b6cbc2775b4b2abc644b8 --- /dev/null +++ b/extensions/sd-webui-crop-extension/scripts/ui.py @@ -0,0 +1,32 @@ +from modules import script_callbacks +import gradio as gr +from PIL import Image +import numpy as np +from imgutils.detect import detect_halfbody, detect_heads + + +def on_ui_tab_called(): + with gr.Blocks() as transparent_interface: + with gr.Row(): + with gr.Tabs(): + with gr.TabItem("CropHead"): + image_upload_input = gr.Image(label="Upload Image", source="upload",type="pil") + button = gr.Button(label="Convert") + image_upload_output = gr.Image(label="Output Image",type="numpy") + + def convert_image(image:Image.Image): + # first convert to RGB + if image.mode == "RGBA": + # convert transparent pixels to white + white_image = Image.new("RGB", image.size, (255, 255, 255)) + white_image.paste(image, mask=image.split()[3]) + image = white_image + else: + image = image.convert("RGB") + result = detect_heads(image) + new_image = image.crop(result[0][0]) + return new_image # return the new image + button.click(convert_image, inputs=[image_upload_input], outputs=[image_upload_output]) + return (transparent_interface, "CropHead", "script_crophead_interface"), + +script_callbacks.on_ui_tabs(on_ui_tab_called) diff --git a/extensions/sd-webui-curtains/LICENSE b/extensions/sd-webui-curtains/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..91b74bef797843ad1245014dc2865ff6cd4ac688 --- /dev/null +++ b/extensions/sd-webui-curtains/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Haoming + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/sd-webui-curtains/README.md b/extensions/sd-webui-curtains/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d335e8500e82045da7c4476a285c9d45f0a821a1 --- /dev/null +++ b/extensions/sd-webui-curtains/README.md @@ -0,0 +1,12 @@ +# SD Webui Curtains +This is an Extension for the [Automatic1111 Webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui), which adds a *curtain*. + +## Wut +When you don't want the passerby to see what you are generating, just add a curtain over the results that folds away when hovered~ + +## How to Use +By default, the curtain is folded away on the **right**. Simply click on it to let it expand. Click on it again to fold it once more. + +
+ +~~Did I spend too much effort making the curtains look good?~~ diff --git a/extensions/sd-webui-curtains/javascript/curtains.js b/extensions/sd-webui-curtains/javascript/curtains.js new file mode 100644 index 0000000000000000000000000000000000000000..521188558079bb7c3bf5d6cc700e550c4e65134d --- /dev/null +++ b/extensions/sd-webui-curtains/javascript/curtains.js @@ -0,0 +1,40 @@ +onUiLoaded(async () => { + ['txt', 'img'].forEach((tab) => { + + const result = document.getElementById(`${tab}2img_gallery`); + const curtain = document.createElement('div'); + curtain.classList.add('curtains'); + curtain.classList.add('curtain-off'); + + curtain.addEventListener('click', () => { + if (curtain.classList.contains('curtain-on')) { + curtain.classList.remove('curtain-on'); + curtain.classList.add('curtain-off'); + } else { + curtain.classList.remove('curtain-off'); + curtain.classList.add('curtain-on'); + } + }); + + result.appendChild(curtain); + + }); + + const tabs = document.getElementById('mode_img2img'); + const curtain = document.createElement('div'); + curtain.classList.add('curtains'); + curtain.classList.add('curtain-off'); + + curtain.addEventListener('click', () => { + if (curtain.classList.contains('curtain-on')) { + curtain.classList.remove('curtain-on'); + curtain.classList.add('curtain-off'); + } else { + curtain.classList.remove('curtain-off'); + curtain.classList.add('curtain-on'); + } + }); + + tabs.appendChild(curtain); + +}); diff --git a/extensions/sd-webui-curtains/style.css b/extensions/sd-webui-curtains/style.css new file mode 100644 index 0000000000000000000000000000000000000000..bba2857fb5f67f71fdf8b76db157d59e1f54c2cd --- /dev/null +++ b/extensions/sd-webui-curtains/style.css @@ -0,0 +1,42 @@ +#txt2img_gallery, #img2img_gallery { + overflow: visible !important; +} + +#txt2img_results > .progressDiv, #img2img_results > .progressDiv { + z-index: 1000; +} + + +.curtains { + position: absolute; + top: 0%; + left: 0%; + width: 100%; + height: 100%; + z-index: 500; + transition: 0.075s ease-out; + background: repeating-linear-gradient(90deg, var(--background-fill-primary), var(--background-fill-secondary) 8%); + border: 1px dotted var(--border-color-accent); +} + +.curtains.curtain-off { + width: 2%; + left: 100%; +} + +.curtains.curtain-off:hover { + transition: cursor 0.1s ease; + cursor: zoom-out; +} + +.curtains.curtain-on:hover { + transition: cursor 0.1s ease; + cursor: zoom-in; +} + +#txt2img_gallery:hover .curtains.curtain-on, +#img2img_gallery:hover .curtains.curtain-on, +#mode_img2img:hover .curtains.curtain-on { + width: 2%; + left: 100%; +} diff --git a/extensions/sd-webui-detail-daemon/.gitattributes b/extensions/sd-webui-detail-daemon/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..dfe0770424b2a19faf507a501ebfc23be8f54e7b --- /dev/null +++ b/extensions/sd-webui-detail-daemon/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/extensions/sd-webui-detail-daemon/.gitignore b/extensions/sd-webui-detail-daemon/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c18dd8d83ceed1806b50b0aaa46beb7e335fff13 --- /dev/null +++ b/extensions/sd-webui-detail-daemon/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/extensions/sd-webui-detail-daemon/LICENSE b/extensions/sd-webui-detail-daemon/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..17549f2a830a05a46deb26141f7be6d54860ddbb --- /dev/null +++ b/extensions/sd-webui-detail-daemon/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Sahand Ahmadian + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/sd-webui-detail-daemon/README.md b/extensions/sd-webui-detail-daemon/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ebd9ab20d4964fa6f5ed0f48d7f8ff872de86b1a --- /dev/null +++ b/extensions/sd-webui-detail-daemon/README.md @@ -0,0 +1,82 @@ +# Detail Daemon +This is an extension for [Stable Diffusion Web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui), which allows users to adjust the amount of detail/smoothness in an image, during the sampling steps. + +It uses no LORAs, ControlNets, etc., and as a result its performance is not biased towards any certain style and it introduces no new stylistic or semantic features of its own into the generation. This also means that it can work with any model and on any type of image. + +*Model: SSD-1B*
+![a close up portrait of a cyberpunk knight-1Lv-0](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/561c33d9-9a5d-4cfc-bee8-de9126b280c1) +*Left: Less detail, Middle: Original, Right: More detail*
+ +*Model: SD 1.5 (finetuned)*
+![face of a cute cat love heart symbol-Zn6-0](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/9fbfb39f-81fb-4951-8f32-20eab410020a) +*Left: Less detail, Middle: Original, Right: More detail*
+ + +## How It Works +Detail Daemon works by manipulating the original noise levels at every sampling step, according to a customizable schedule. + +### In Theory +The noise levels (sigmas, i.e. the standard deviation of the noise) tell the model how much noise it should expect, and try to remove, at each denoising step. A higher sigma value at a certain denoising step tells the model to denoise more aggressively at that step and vice versa. + +With a common sigmas schedule, the sigmas start at very high values at the beginning of the denoising process, then quickly fall to low values in the middle, and to very low values towards the end of the process. This curve (along with the timesteps schedule, but that's a story for another day) is what makes it so that larger features (low frequencies) of the image are defined at the earlier steps, and towards the end of the process you can only see minor changes in the smaller features (high frequencies). We'll get back to this later. + +Now, if we pass the model a sigmas schedule with values lower than the original, at each step the model will denoise less, resulting a noisier output latent at that step. But then in the steps after that, the model does its best to make sense of this extra noise and turn it into image features. So in theory, *when done in modesty*, this would result in a more detailed image. If you push it too hard, the model won't be able to handle the extra noise added at each step and the end result will devolve into pure noise. So modesty is key. + +### But in Practice +Modesty only gets you so far! Also, wtf are those? As the examples below show, you can't really add that much detail to the image before it either breaks down, and/or becomes a totally different thing. + +*SD 1.5*
+![Modesty](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/2f011a28-0948-48f8-b171-350add6fdd67) +Original sigmas (left) multiplied by .9, .85, .8
+ +*SDXL*
+![1](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/eff2356e-a6dd-4a4e-9c7e-861dec7713eb) +Original sigmas (left) multiplied by .95, .9, .85, .875, .8
+ +That's because: +1. We're constantly adding noise and not giving the model enough time to deal with it +2. We are manipulating the early steps where the low frequency features of the image (color, composition, etc.) are defined + +### Enter the Schedule +What we usually mean by "detail" falls within the mid to high frequency range, which correspond to the middle to late steps in the sampling process. So if we skip the early steps to leave the main features of the image intact, and the late steps to give the model some time to turn the extra noise into useful detail, we'll have something like this: + +![3](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/cd47e882-8b56-4321-8c47-c0d689562780) + +Then we could make our schedule a bit fancier and have it target specific steps corresponding to different sized details: + +![4](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/ea5027d2-3359-4733-afb4-5ae4a1218f38) + +Which steps correspond to which exact frequency range depends on the model you're using, the sampler, your prompt (specially if you're using Prompt Editing and stuff), and probably a bunch of other things. There are also fancier things you can (and should) do with the schedule, like pushing the sigmas too low for some heavy extra noise and then too high to clean up the excess and leave some nice details. So you need to do some tweaking to figure out the best schedule for each image you generate, or at least the ones that need their level of detail adjusted. But ideally you should be spending countless hours of your life sculpting the perfect detail adjustment schedule for every image, cuz that's why we're here. + +I'll soon provide specific examples addressing different scenarios and some of the techniques I've come up with. (note to self: move these to the wiki page) + +## Installation +Open SD WebUI > Go to Extensions tab > Go to Available tab > Click Load from: > Find Detail Daemon > Click Install + +Or Go to Install from URL tab > Paste this repo's URL into the first field > Click Install + +Or go to your WebUI folder and manually clone this repo into your extensions folder: + +`git clone "https://github.com/muerrilla/sd-webui-detail-daemon" extensions/sd-webui-detail-daemon` + +## Getting Started +After installation you can find the extension in your txt2img and img2img tabs. +![2024-07-08 01_43_21-011366](https://github.com/muerrilla/sd-webui-detail-daemon/assets/48160881/045574cb-465c-4991-83c4-d02f803a330b) +### Sliders: +The sliders (and that one checkbox) set the amount of adjustment (positive values → add detail, negative values → remove detail) and the sampling steps during which it is applied (i.e. the schedule). So the X axis of the graph is your sampling steps, normalized to the (0,1) range, and the Y axis is the amount of adjustment. The rest is pretty self-explanatory I think. Just drag things and look at the graph for changes. +### Numbers: +The three number inputs at the buttom are provided because sometimes the slider max/mins are too limiting. +### Modes: +The `cond` and `uncond` modes affect only their respective latents, while `both` affects both (duh!). The `cond` and `uncond` modes are less intense and also allow changes to be applied at earlier steps without diverging too far from the original generation, since the other latent stays intact. + +There's also a minor twist: in the `both` mode if `detail amount` is positive both cond and uncond latents become more detailed. So the more detailed cond latent will try to push the generation towards more detail, while the more detailed uncond latent will try to push towards less detail. This causes more new features/artifacts to pop into the image in this mode. + +### Tips: +I'll write up some proper docs on how best to set the parameters, as soon as possible. For now you gotta play around with the sliders and figure out how the shape of the schedule affects the image. I suggest you set your live preview update period to every frame, or every other frame, so you could see clearly what's going on at every step of the sampling process and how Detail Daemon affects it, till you get a good grasp of how this thing works. + +## Notes: +- Doesn't support Compositional Diffusion (i.e. the AND syntax) properly. Specially if you have a batch size > 1 or negative weights in your prompts, and the mode is set to `cond` or `uncond`. +- It's probably impossible to use or very hard to control with few-step models (Turbo, Lightning, etc.). Edit: It's managable. +- It works with Forge (`cond` and `uncond` modes are not supported). +- It's not the same as AlignYourSteps, FreeU, etc. +- It is similar (in what it sets out to do, not in how it does it) to the [ReSharpen Extension](https://github.com/Haoming02/sd-webui-resharpen) by Haoming. diff --git a/extensions/sd-webui-detail-daemon/scripts/__pycache__/detail_daemon.cpython-310.pyc b/extensions/sd-webui-detail-daemon/scripts/__pycache__/detail_daemon.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..510d77d0e6441fcea19b8728517c9aadb2a02a12 Binary files /dev/null and b/extensions/sd-webui-detail-daemon/scripts/__pycache__/detail_daemon.cpython-310.pyc differ diff --git a/extensions/sd-webui-detail-daemon/scripts/detail_daemon.py b/extensions/sd-webui-detail-daemon/scripts/detail_daemon.py new file mode 100644 index 0000000000000000000000000000000000000000..d63d9900ab1d8a9223fd397cf64d062889907f2d --- /dev/null +++ b/extensions/sd-webui-detail-daemon/scripts/detail_daemon.py @@ -0,0 +1,313 @@ +import os +import gradio as gr +import numpy as np +from tqdm import tqdm + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt + +import modules.scripts as scripts +from modules.script_callbacks import on_cfg_denoiser, remove_callbacks_for_function, on_infotext_pasted +from modules.ui_components import InputAccordion + + +def parse_infotext(infotext, params): + try: + d = {} + for s in params['Detail Daemon'].split(','): + k, _, v = s.partition(':') + d[k.strip()] = v.strip() + params['Detail Daemon'] = d + except Exception: + pass + + +on_infotext_pasted(parse_infotext) + + +class Script(scripts.Script): + + def title(self): + return "Detail Daemon" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with InputAccordion(False, label="Detail Daemon", elem_id=self.elem_id('detail-daemon')) as gr_enabled: + with gr.Row(): + with gr.Column(scale=2): + gr_amount_slider = gr.Slider(minimum=-1.00, maximum=1.00, step=.01, value=0.10, label="Detail Amount") + gr_start = gr.Slider(minimum=0.0, maximum=1.0, step=.01, value=0.2, label="Start") + gr_end = gr.Slider(minimum=0.0, maximum=1.0, step=.01, value=0.8, label="End") + gr_bias = gr.Slider(minimum=0.0, maximum=1.0, step=.01, value=0.5, label="Bias") + with gr.Column(scale=1, min_width=275): + preview = self.visualize(False, 0.2, 0.8, 0.5, 0.1, 1, 0, 0, 0, True) + gr_vis = gr.Plot(value=preview, elem_classes=['detail-daemon-vis'], show_label=False) + with gr.Accordion("More Knobs:", elem_classes=['detail-daemon-more-accordion'], open=False): + with gr.Row(): + with gr.Column(scale=2): + with gr.Row(): + gr_start_offset_slider = gr.Slider(minimum=-1.00, maximum=1.00, step=.01, value=0.00, label="Start Offset", min_width=60) + gr_end_offset_slider = gr.Slider(minimum=-1.00, maximum=1.00, step=.01, value=0.00, label="End Offset", min_width=60) + with gr.Row(): + gr_exponent = gr.Slider(minimum=0.0, maximum=10.0, step=.05, value=1.0, label="Exponent", min_width=60) + gr_fade = gr.Slider(minimum=0.0, maximum=1.0, step=.05, value=0.0, label="Fade", min_width=60) + # Because the slider max and min are sometimes too limiting: + with gr.Row(): + gr_amount = gr.Number(value=0.10, precision=4, step=.01, label="Amount", min_width=60) + gr_start_offset = gr.Number(value=0.0, precision=4, step=.01, label="Start Offset", min_width=60) + gr_end_offset = gr.Number(value=0.0, precision=4, step=.01, label="End Offset", min_width=60) + with gr.Column(scale=1, min_width=275): + gr_mode = gr.Dropdown(["both", "cond", "uncond"], value="uncond", label="Mode", show_label=True, min_width=60, elem_classes=['detail-daemon-mode']) + gr_smooth = gr.Checkbox(label="Smooth", value=True, min_width=60, elem_classes=['detail-daemon-smooth']) + gr.Markdown("## [Ⓗ Help](https://github.com/muerrilla/sd-webui-detail-daemon)", elem_classes=['detail-daemon-help']) + + gr_amount_slider.release(None, gr_amount_slider, gr_amount, _js="(x) => x") + gr_amount.change(None, gr_amount, gr_amount_slider, _js="(x) => x") + + gr_start_offset_slider.release(None, gr_start_offset_slider, gr_start_offset, _js="(x) => x") + gr_start_offset.change(None, gr_start_offset, gr_start_offset_slider, _js="(x) => x") + + gr_end_offset_slider.release(None, gr_end_offset_slider, gr_end_offset, _js="(x) => x") + gr_end_offset.change(None, gr_end_offset, gr_end_offset_slider, _js="(x) => x") + + vis_args = [gr_enabled, gr_start, gr_end, gr_bias, gr_amount, gr_exponent, gr_start_offset, gr_end_offset, gr_fade, gr_smooth] + for vis_arg in vis_args: + if isinstance(vis_arg, gr.components.Slider): + vis_arg.release(fn=self.visualize, show_progress=False, inputs=vis_args, outputs=[gr_vis]) + else: + vis_arg.change(fn=self.visualize, show_progress=False, inputs=vis_args, outputs=[gr_vis]) + + def extract_infotext(d: dict, key, old_key): + if 'Detail Daemon' in d: + return d['Detail Daemon'].get(key) + return d.get(old_key) + + self.infotext_fields = [ + (gr_enabled, lambda d: True if ('Detail Daemon' in d or 'DD_enabled' in d) else False), + (gr_mode, lambda d: extract_infotext(d, 'mode', 'DD_mode')), + (gr_amount, lambda d: extract_infotext(d, 'amount', 'DD_amount')), + (gr_start, lambda d: extract_infotext(d, 'st', 'DD_start')), + (gr_end, lambda d: extract_infotext(d, 'ed', 'DD_end')), + (gr_bias, lambda d: extract_infotext(d, 'bias', 'DD_bias')), + (gr_exponent, lambda d: extract_infotext(d, 'exp', 'DD_exponent')), + (gr_start_offset, lambda d: extract_infotext(d, 'st_offset', 'DD_start_offset')), + (gr_end_offset, lambda d: extract_infotext(d, 'ed_offset', 'DD_end_offset')), + (gr_fade, lambda d: extract_infotext(d, 'fade', 'DD_fade')), + (gr_smooth, lambda d: extract_infotext(d, 'smooth', 'DD_smooth')), + ] + return [gr_enabled, gr_mode, gr_start, gr_end, gr_bias, gr_amount, gr_exponent, gr_start_offset, gr_end_offset, gr_fade, gr_smooth] + + def process(self, p, enabled, mode, start, end, bias, amount, exponent, start_offset, end_offset, fade, smooth): + + enabled = getattr(p, "DD_enabled", enabled) + mode = getattr(p, "DD_mode", mode) + amount = getattr(p, "DD_amount", amount) + start = getattr(p, "DD_start", start) + end = getattr(p, "DD_end", end) + bias = getattr(p, "DD_bias", bias) + exponent = getattr(p, "DD_exponent", exponent) + start_offset = getattr(p, "DD_start_offset", start_offset) + end_offset = getattr(p, "DD_end_offset", end_offset) + fade = getattr(p, "DD_fade", fade) + smooth = getattr(p, "DD_smooth", smooth) + + if enabled: + if p.sampler_name == "DPM adaptive": + tqdm.write(f'\033[33mWARNING:\033[0m Detail Daemon does not work with {p.sampler_name}') + return + # Restart can be handled better, later maybe + + actual_steps = (p.steps * 2 - 1) if p.sampler_name in ['DPM++ SDE', 'DPM++ 2S a', 'Heun', 'DPM2', 'DPM2 a', 'Restart'] else p.steps + self.schedule = self.make_schedule(actual_steps, start, end, bias, amount, exponent, start_offset, end_offset, fade, smooth) + self.mode = mode + self.cfg_scale = p.cfg_scale + self.batch_size = p.batch_size + on_cfg_denoiser(self.denoiser_callback) + self.callback_added = True + p.extra_generation_params['Detail Daemon'] = f'mode:{mode},amount:{amount},st:{start},ed:{end},bias:{bias},exp:{exponent},st_offset:{start_offset},ed_offset:{end_offset},fade:{fade},smooth:{1 if smooth else 0}' + tqdm.write('\033[32mINFO:\033[0m Detail Daemon is enabled') + else: + if hasattr(self, 'callback_added'): + remove_callbacks_for_function(self.denoiser_callback) + delattr(self, 'callback_added') + # tqdm.write('\033[90mINFO: Detail Daemon callback removed\033[0m') + + def before_process_batch(self, p, *args, **kwargs): + self.is_hires = False + + def postprocess(self, p, processed, *args): + if hasattr(self, 'callback_added'): + remove_callbacks_for_function(self.denoiser_callback) + delattr(self, 'callback_added') + # tqdm.write('\033[90mINFO: Detail Daemon callback removed\033[0m') + + def before_hr(self, p, *args): + self.is_hires = True + enabled = args[0] + if enabled: + tqdm.write(f'\033[33mINFO:\033[0m Detail Daemon does not work during Hires Fix') + + def denoiser_callback(self, params): + if self.is_hires: + return + idx = params.denoiser.step + multiplier = self.schedule[idx] * .1 + mode = self.mode + if params.sigma.size(0) == 1: + mode = "both" + if idx == 0: + tqdm.write(f'\033[33mWARNING:\033[0m Forge does not support `cond` and `uncond` modes, using `both` instead') + if mode == "cond": + for i in range(self.batch_size): + params.sigma[i] *= 1 - multiplier + elif mode == "uncond": + for i in range(self.batch_size): + params.sigma[self.batch_size + i] *= 1 + multiplier + else: + params.sigma *= 1 - multiplier * self.cfg_scale + + def make_schedule(self, steps, start, end, bias, amount, exponent, start_offset, end_offset, fade, smooth): + start = min(start, end) + mid = start + bias * (end - start) + multipliers = np.zeros(steps) + + start_idx, mid_idx, end_idx = [int(round(x * (steps - 1))) for x in [start, mid, end]] + + start_values = np.linspace(0, 1, mid_idx - start_idx + 1) + if smooth: + start_values = 0.5 * (1 - np.cos(start_values * np.pi)) + start_values = start_values ** exponent + if start_values.any(): + start_values *= (amount - start_offset) + start_values += start_offset + + end_values = np.linspace(1, 0, end_idx - mid_idx + 1) + if smooth: + end_values = 0.5 * (1 - np.cos(end_values * np.pi)) + end_values = end_values ** exponent + if end_values.any(): + end_values *= (amount - end_offset) + end_values += end_offset + + multipliers[start_idx:mid_idx+1] = start_values + multipliers[mid_idx:end_idx+1] = end_values + multipliers[:start_idx] = start_offset + multipliers[end_idx+1:] = end_offset + multipliers *= 1 - fade + + return multipliers + + def visualize(self, enabled, start, end, bias, amount, exponent, start_offset, end_offset, fade, smooth): + try: + steps = 50 + values = self.make_schedule(steps, start, end, bias, amount, exponent, start_offset, end_offset, fade, smooth) + mean = sum(values)/steps + peak = np.clip(max(abs(values)), -1, 1) + if start > end: + start = end + mid = start + bias * (end - start) + opacity = .1 + (1 - fade) * 0.7 + plot_color = (0.5, 0.5, 0.5, opacity) if not enabled else ((1 - peak)**2, 1, 0, opacity) if mean >= 0 else (1, (1 - peak)**2, 0, opacity) + plt.rcParams.update({ + "text.color": plot_color, + "axes.labelcolor": plot_color, + "axes.edgecolor": plot_color, + "figure.facecolor": (0.0, 0.0, 0.0, 0.0), + "axes.facecolor": (0.0, 0.0, 0.0, 0.0), + "ytick.labelsize": 6, + "ytick.labelcolor": plot_color, + "ytick.color": plot_color, + }) + fig, ax = plt.subplots(figsize=(2.15, 2.00), layout="constrained") + ax.plot(range(steps), values, color=plot_color) + ax.axhline(y=0, color=plot_color, linestyle='dotted') + ax.axvline(x=mid * (steps - 1), color=plot_color, linestyle='dotted') + ax.tick_params(right=False, color=plot_color) + ax.set_xticks([i * (steps - 1) / 10 for i in range(10)][1:]) + ax.set_xticklabels([]) + ax.set_ylim([-1, 1]) + ax.set_xlim([0, steps-1]) + plt.close() + self.last_vis = fig + return fig + except Exception: + if self.last_vis is not None: + return self.last_vis + return + + +def xyz_support(): + for scriptDataTuple in scripts.scripts_data: + if os.path.basename(scriptDataTuple.path) == 'xyz_grid.py': + xy_grid = scriptDataTuple.module + + def confirm_mode(p, xs): + for x in xs: + if x not in ['both', 'cond', 'uncond']: + raise RuntimeError(f'Invalid Detail Daemon Mode: {x}') + mode = xy_grid.AxisOption( + '[Detail Daemon] Mode', + str, + xy_grid.apply_field('DD_mode'), + confirm=confirm_mode + ) + amount = xy_grid.AxisOption( + '[Detail Daemon] Amount', + float, + xy_grid.apply_field('DD_amount') + ) + start = xy_grid.AxisOption( + '[Detail Daemon] Start', + float, + xy_grid.apply_field('DD_start') + ) + end = xy_grid.AxisOption( + '[Detail Daemon] End', + float, + xy_grid.apply_field('DD_end') + ) + bias = xy_grid.AxisOption( + '[Detail Daemon] Bias', + float, + xy_grid.apply_field('DD_bias') + ) + exponent = xy_grid.AxisOption( + '[Detail Daemon] Exponent', + float, + xy_grid.apply_field('DD_exponent') + ) + start_offset = xy_grid.AxisOption( + '[Detail Daemon] Start Offset', + float, + xy_grid.apply_field('DD_start_offset') + ) + end_offset = xy_grid.AxisOption( + '[Detail Daemon] End Offset', + float, + xy_grid.apply_field('DD_end_offset') + ) + fade = xy_grid.AxisOption( + '[Detail Daemon] Fade', + float, + xy_grid.apply_field('DD_fade') + ) + xy_grid.axis_options.extend([ + mode, + amount, + start, + end, + bias, + exponent, + start_offset, + end_offset, + fade, + ]) + + +try: + xyz_support() +except Exception as e: + print(f'Error trying to add XYZ plot options for Detail Daemon', e) diff --git a/extensions/sd-webui-detail-daemon/style.css b/extensions/sd-webui-detail-daemon/style.css new file mode 100644 index 0000000000000000000000000000000000000000..b91d21367b3cea8a3f82a47dca2dc8ed8a94bf12 --- /dev/null +++ b/extensions/sd-webui-detail-daemon/style.css @@ -0,0 +1,20 @@ +.detail-daemon-more-accordion { + margin-top: 1em !important; +} + +.detail-daemon-mode { + margin-left: 3em !important; + width: 75% !important; +} + +.detail-daemon-smooth { + margin-left: 3em !important; +} + +.detail-daemon-vis { + margin: auto !important; +} + +.detail-daemon-help { + margin: auto 1.5em !important; +} diff --git a/extensions/sd-webui-dycfg/.gitignore b/extensions/sd-webui-dycfg/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..9f94e5dd988b2944cb27eeaa23ca1cf68e869d8c --- /dev/null +++ b/extensions/sd-webui-dycfg/.gitignore @@ -0,0 +1,2 @@ +.vscode +__pycache__ diff --git a/extensions/sd-webui-dycfg/LICENSE b/extensions/sd-webui-dycfg/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..50e4748d7a38c33807c023c1a475abfda3b7fabd --- /dev/null +++ b/extensions/sd-webui-dycfg/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 hnmr293 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/sd-webui-dycfg/README.md b/extensions/sd-webui-dycfg/README.md new file mode 100644 index 0000000000000000000000000000000000000000..be78c9cab8e3b42476e58d3811d36e5916c0689b --- /dev/null +++ b/extensions/sd-webui-dycfg/README.md @@ -0,0 +1,75 @@ +# DyCFG - Dynamic CFG Scale + +## Cover + +CFG scale 8.0 to 2.0. + +https://github.com/hnmr293/sd-webui-dycfg/assets/120772120/3dd60565-5fcc-4a65-b345-ecd9d71da820 + +https://github.com/hnmr293/sd-webui-dycfg/assets/120772120/eb8505ab-f75e-4c6c-b346-7120d106ec98 + +## What is this? + +This is an extension for [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) which lets you to change CFG scale dynamically. + +## Usage + +1. Select `Enabled` checkbox. +2. Put the first step, the last step and CFG scale to the input boxes. +3. Generate images. + +![UI](./images/ui.png) + +``` +Start: the starting step of specified CFG scale. +End: the last step of specified CFG scale (inclusive). 0 is treated as max steps. +CFG scale: the scale value which will be used in Start..End steps. +Interpolation: the CFG scale interpolation mode (described below). +``` + +If both of `Start` and `End` are set to 0, the row will be ignored. + +### Interpolation modes + +Specify CFG scale interpolation mode for `End+1`..`Start-1` steps. + +- `Default`: use default CFG scale. +- `Linear`: linear interpolation between `End+1` and `Start-1`. +- `Fixed`: use last (`End+1`) CFG scale. + +for example, with following setting, CFG scale values will be: +`[7.0, 6.0, 6.0, 6.75, 7.5, 8.25, 9.0, 9.0, 9.0, 9.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0]` + +![scale example](./images/scale_examples.png) + +1. Step 1 is not specified and the first interpolation mode is `Default`. Then webui's CFG scale 7.0 is used. +2. Step 2 and 3 is specified, CFG scale = 6.0. +3. Step 4 to 6 is not specified and the second interpolation mode is `Linear`. The last CFG scale (at step 3) is 6.0 and the next CFG scale (at step 7) is 9.0. Then we get CFG scales 6.75 (at step 4), 7.50 (at step 5) and 8.25 (at step 6). +4. Step 7 and 8 is specified, CFG scale = 9.0. +5. Step 9 and 10 is not specifed and the third interpolation mode is `Fixed`. The last CFG scale (at step 8) is 9.0. Then we get CFG scales 9.0 (at step 9) and 9.0 (at step 10). +6. Step 11 to 20 is specified, CFG scale = 4.0. Note that 0 means the last step. + + +## Examples + +``` +Sampler: DPM++ 3M SDE +Sampling steps: 30 +Size: 768x768 +Prompt: close up of a cute girls sitting in flower garden, clear anime face, insanely frilled white dress, small silver tiara, absurdly long brown hair, long sleeves highneck dress, evil smile +Negative Prompt: (low quality, worst quality:1.4), maid, opened mouth, sleeveless, dutch angle +``` + +![sample 01](./images/05.png) + + +``` +Model: 7th_anime_v3_A +Sampler: DPM++ 2M Karras +Sampling steps: 20 +Size: 768x768 +Prompt: close up of a cute girls sitting in flower garden, clear anime face, insanely frilled dress, small tiara, absurdly long brown hair, long sleeves highneck dress, evil smile +Negative Prompt: (low quality, worst quality:1.4), maid, opened mouth, sleeveless, dutch angle +``` + +![sample 02](./images/09.png) diff --git a/extensions/sd-webui-dycfg/images/05.png b/extensions/sd-webui-dycfg/images/05.png new file mode 100644 index 0000000000000000000000000000000000000000..b7ac93f17ac77f8f827f94d3b0ff13315c2ce6b2 --- /dev/null +++ b/extensions/sd-webui-dycfg/images/05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e6c013ed87e68fbecda85bea75ba0fefb85120b84f2fe5ebc5ba7f43b9bea1e +size 5322608 diff --git a/extensions/sd-webui-dycfg/images/09.png b/extensions/sd-webui-dycfg/images/09.png new file mode 100644 index 0000000000000000000000000000000000000000..b1258cbe2af8c0f8b675afb1078961f93d753fed --- /dev/null +++ b/extensions/sd-webui-dycfg/images/09.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c84dfbcd3da75eaf632f52bc53b00fafdc899d23a8ee017d44168ca001ffbaf6 +size 5051476 diff --git a/extensions/sd-webui-dycfg/images/scale_examples.png b/extensions/sd-webui-dycfg/images/scale_examples.png new file mode 100644 index 0000000000000000000000000000000000000000..c2e75ef07022e974c64aa3b49ace2258b51aa765 Binary files /dev/null and b/extensions/sd-webui-dycfg/images/scale_examples.png differ diff --git a/extensions/sd-webui-dycfg/images/ui.png b/extensions/sd-webui-dycfg/images/ui.png new file mode 100644 index 0000000000000000000000000000000000000000..f06293eb3a3851b03b84347c412d1f37ffde815f Binary files /dev/null and b/extensions/sd-webui-dycfg/images/ui.png differ diff --git a/extensions/sd-webui-dycfg/scripts/__pycache__/dycfg.cpython-310.pyc b/extensions/sd-webui-dycfg/scripts/__pycache__/dycfg.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdfec084538c72e488178bd0fd5f3ad0a205f1f2 Binary files /dev/null and b/extensions/sd-webui-dycfg/scripts/__pycache__/dycfg.cpython-310.pyc differ diff --git a/extensions/sd-webui-dycfg/scripts/__pycache__/dycfg_xyz.cpython-310.pyc b/extensions/sd-webui-dycfg/scripts/__pycache__/dycfg_xyz.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a82d9e97a759ce5c8b6d7116811f4922dba22523 Binary files /dev/null and b/extensions/sd-webui-dycfg/scripts/__pycache__/dycfg_xyz.cpython-310.pyc differ diff --git a/extensions/sd-webui-dycfg/scripts/dycfg.py b/extensions/sd-webui-dycfg/scripts/dycfg.py new file mode 100644 index 0000000000000000000000000000000000000000..e44a451ae1ad5ada9c560f28e5fe62abcacaa51f --- /dev/null +++ b/extensions/sd-webui-dycfg/scripts/dycfg.py @@ -0,0 +1,196 @@ +import gradio as gr + +from modules.processing import StableDiffusionProcessing +from modules import scripts +from modules.sd_samplers_cfg_denoiser import CFGDenoiser +from scripts.dycfg_xyz import init_xyz + + +NAME = "DyCFG" + +ORIGINAL = "combine_denoised" +SAVED = f"_{NAME}_original_{ORIGINAL}" +DENOISER_CLASS = CFGDenoiser + + +def to_f(v): + return float(v) + + +def to_i(v): + return int(float(v)) + + +class Script(scripts.Script): + def title(self): + return NAME + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + with gr.Group(): + with gr.Accordion(NAME, open=False): + enabled = gr.Checkbox(label="Enabled", value=False) + + elems = [] + for _ in range(3): + with gr.Row(): + start = gr.Number( + value=0, + label="Start", + precision=0, + minimum=0, + maximum=500, + step=1, + elem_classes=[f'{NAME}_number'], + ) + end = gr.Number( + value=0, + label="End (inclusive)", + precision=0, + minimum=0, + maximum=500, + step=1, + elem_classes=[f'{NAME}_number'], + ) + value = gr.Number( + value=7.0, + label="CFG scale", + precision=2, + minimum=1.0, + maximum=100.0, + step=0.01, + elem_classes=[f'{NAME}_number'], + ) + intp = gr.Radio( + choices=["Default", "Linear", "Fixed"], + value="Default", + label="Interpolation", + ) + elems.extend([start, end, value, intp]) + + return [ + enabled, + *elems, + ] + + def process( + self, + p: StableDiffusionProcessing, + enabled: bool, + *elems, + ): + original, _ = self.unhook(DENOISER_CLASS) + + if not enabled: + return + + scales = ["Default" for _ in range(p.steps)] + + elems = [elems[i : i + 4] for i in range(0, len(elems), 4)] + for start, end, value, intp in elems: + start = to_i(start) + end = to_i(end) + value = to_f(value) + + if start == 0 and end == 0: + continue + + start = max(1, start) + + if end <= 0: + end = p.steps + if end < start: + continue + + value = max(1, value) + + for i in range(start - 1, end): + # start-1 .. end-1 + scales[i] = value + for i in range(start - 2, -1, -1): + if isinstance(scales[i], str): + scales[i] = intp + else: + break + + scales = ["Default", *scales, "Default"] # add sentinels + + # scales = [ Default 7 7 7 Default 9 9 9 9 Fixed Fixed 4 4 Linear Linear Linear 3 3 3 Linear Linear Linear Default ] + # while any(isinstance(x, str) for x in scales): + default = float(p.cfg_scale) + prev = default + for i in range(len(scales)): + if scales[i] == "Default": + scales[i] = default + # scales = [ * 7 7 7 * 9 9 9 9 Fixed Fixed 4 4 Linear Linear Linear 3 3 3 Linear Linear Linear * ] + elif scales[i] == "Fixed": + assert not isinstance(prev, str), f"prev = {prev}" + scales[i] = prev + # scales = [ * 7 7 7 * 9 9 9 9 < < 4 4 Linear Linear Linear 3 3 3 Linear Linear Linear * ] + prev = scales[i] + + for i in range(len(scales)): + if scales[i] == "Linear": + assert i != 0 and i != len(scales) - 1, f"i = {i}" + prev = scales[i - 1] + assert not isinstance(prev, str) + for j in range(i + 1, len(scales)): + if scales[j] != "Linear": + break + # scales = [ * 7 7 7 * 9 9 9 9 < < 4 4 Linear Linear Linear 3 3 3 Linear Linear Linear * ] + # p i j p i j + assert j != len(scales) - 1, f"j = {j}" + next = scales[j] + assert not isinstance(next, str), f"next = {next}" + distance = j - i + 1 + d = (next - prev) / distance + for k in range(i, j): + scales[k] = prev + (k - i + 1) * d + + # scales = [ * 7 7 7 * 9 9 9 9 < < 4 4 ~ ~ ~ 3 3 3 ~ ~ ~ * ] + + scales = scales[1:-1] + assert len(scales) == p.steps, f"scales = {scales}" + + print(f"[{NAME}] scales =", scales) + + assert not any(isinstance(x, str) for x in scales), f"scales = {scales}" + + def do_cfg(self, x_out, conds_list, uncond, cond_scale, *args, **kwargs): + #print(f"\nstep = {self.step}, scale = {scales[self.step]}\n") + return original(self, x_out, conds_list, uncond, scales[self.step], *args, **kwargs) + + self.hook(DENOISER_CLASS, do_cfg) + + p.extra_generation_params.update( + { + f"{NAME} enabled": enabled, + **{f"{NAME} {i}": elem for i, elem in enumerate(elems)}, + } + ) + + print(f"[{NAME}] enabled") + + return + + def postprocess(self, p, processed, enabled, *args): + # may not be called + if enabled: + self.unhook(DENOISER_CLASS) + + def unhook(self, denoiser_cls): + current = getattr(denoiser_cls, ORIGINAL) + original = getattr(denoiser_cls, SAVED, current) + if hasattr(denoiser_cls, SAVED): + delattr(denoiser_cls, SAVED) + setattr(denoiser_cls, ORIGINAL, original) + return original, current + + def hook(self, denoiser_cls, fn): + original, _ = self.unhook(denoiser_cls) + setattr(denoiser_cls, SAVED, original) + setattr(denoiser_cls, ORIGINAL, fn) + +init_xyz(NAME, Script) diff --git a/extensions/sd-webui-dycfg/scripts/dycfg_xyz.py b/extensions/sd-webui-dycfg/scripts/dycfg_xyz.py new file mode 100644 index 0000000000000000000000000000000000000000..d1691b5d2146ee0c1b08cf7bdc4ae108159129fc --- /dev/null +++ b/extensions/sd-webui-dycfg/scripts/dycfg_xyz.py @@ -0,0 +1,129 @@ +import os +from typing import Union, List, Callable + +from modules import scripts +from modules.processing import StableDiffusionProcessing, StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img + + +def __set_value(p: StableDiffusionProcessing, script: type, index: int, value): + args = list(p.script_args) + + if isinstance(p, StableDiffusionProcessingTxt2Img): + all_scripts = scripts.scripts_txt2img.scripts + else: + all_scripts = scripts.scripts_img2img.scripts + + froms = [x.args_from for x in all_scripts if isinstance(x, script)] + for idx in froms: + assert idx is not None + args[idx + index] = value + + p.script_args = type(p.script_args)(args) + + +def to_bool(v: str): + if len(v) == 0: return False + v = v.lower() + if 'true' in v: return True + if 'false' in v: return False + + try: + w = int(v) + return bool(w) + except: + acceptable = ['True', 'False', '1', '0'] + s = ', '.join([f'`{v}`' for v in acceptable]) + raise ValueError(f'value must be one of {s}.') + + +class AxisOptions: + + def __init__(self, AxisOption: type, axis_options: list): + self.AxisOption = AxisOption + self.target = axis_options + self.options = [] + + def __enter__(self): + self.options.clear() + return self + + def __exit__(self, ex_type, ex_value, trace): + if ex_type is not None: + return + + for opt in self.options: + self.target.append(opt) + + self.options.clear() + + def create(self, name: str, type_fn: Callable, action: Callable, choices: Union[List[str],None]): + if choices is None or len(choices) == 0: + opt = self.AxisOption(name, type_fn, action) + else: + opt = self.AxisOption(name, type_fn, action, choices=lambda: choices) + return opt + + def add(self, axis_option): + self.target.append(axis_option) + + +__init = False + +def init_xyz(ext_name: str, script: type): + global __init + + if __init: + return + + for data in scripts.scripts_data: + name = os.path.basename(data.path) + if name != 'xy_grid.py' and name != 'xyz_grid.py': + continue + + if not hasattr(data.module, 'AxisOption'): + continue + + if not hasattr(data.module, 'axis_options'): + continue + + AxisOption = data.module.AxisOption + axis_options = data.module.axis_options + + if not isinstance(AxisOption, type): + continue + + if not isinstance(axis_options, list): + continue + + try: + create_options(ext_name, script, AxisOption, axis_options) + except: + pass + + __init = True + + +def create_options(ext_name: str, script: type, AxisOptionClass: type, axis_options: list): + + with AxisOptions(AxisOptionClass, axis_options) as opts: + def define(param: str, index: int, type_fn: Callable, choices: List[str] = []): + def fn(p, x, xs): + __set_value(p, script, index, x) + + name = f'[{ext_name}] {param}' + return opts.create(name, type_fn, fn, choices) + + options = [ + define('Enabled', 0, to_bool, choices=['false', 'true']), + ] + + for i in range(3): + options.extend([ + define(f'#{i} Start step', 1+i*4+0, int), + define(f'#{i} End step', 1+i*4+1, int), + define(f'#{i} CFG scale', 1+i*4+2, float), + define(f'#{i} Interpolation', 1+i*4+3, str, choices=['Default', 'Linear', 'Fixed']), + ]) + + for opt in options: + opts.add(opt) diff --git a/extensions/sd-webui-dycfg/style.css b/extensions/sd-webui-dycfg/style.css new file mode 100644 index 0000000000000000000000000000000000000000..c54f72700f8b7772bffb7b179a9265d9d2017d77 --- /dev/null +++ b/extensions/sd-webui-dycfg/style.css @@ -0,0 +1,3 @@ +.DyCFG_number { + max-width: 5em; +} \ No newline at end of file diff --git a/extensions/sd-webui-freeu/.gitignore b/extensions/sd-webui-freeu/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..8ba7f6026c5ad5db6c2d57b1234e444cc0897ad6 --- /dev/null +++ b/extensions/sd-webui-freeu/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +presets.json +*.csv diff --git a/extensions/sd-webui-freeu/LICENSE b/extensions/sd-webui-freeu/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..96425485fa1e0c7ade09d61779ca4812dab0c777 --- /dev/null +++ b/extensions/sd-webui-freeu/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 ljleb + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/sd-webui-freeu/README.md b/extensions/sd-webui-freeu/README.md new file mode 100644 index 0000000000000000000000000000000000000000..04d0c491287eab83bee5f98acb22b1b24c2268e6 --- /dev/null +++ b/extensions/sd-webui-freeu/README.md @@ -0,0 +1,91 @@ +# sd-webui-freeu +implementation of [FreeU](https://github.com/ChenyangSi/FreeU) as an [a1111 sd webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) extension + +At each of the 3 stages of the UNet decoder: +- Apply a scalar on a window over the features of the backbone +- Tame the frequencies of the skip connection + +## Settings + +- Start At Step: do not apply FreeU until this sampling step is reached +- Stop At Step: apply FreeU until this sampling step is reached +- Transition Smoothness: see $k_{smooth}$ in [this desmos graph](https://www.desmos.com/calculator/ngcqo5ictm) +- Backbone n Scale: scalar applied to the backbone window during UNet stage n +- Backbone n Offset: offset of the window, 1 is the same as 0 as the window wraps around the downsampled latent features +- Backbone n Width: width of the window applied to the backbone +- Skip n Scale: scalar applied to the low frequencies (low end) of the skip connection during UNet stage n +- Skip n High End Scale: scalar applied to the high frequencies (high end) of the skip connection +- Skip n Cutoff: ratio that separates low from high frequencies, 0 means to control the single lowest frequency with "Skip n Scale" and 1 means scale all frequencies with "Skip n Scale" + +## API + +You can pass a single dict as the alwayson script args when making API calls: + +```json +{ + "alwayson_scripts": { + "freeu": { + "args": [{ + "enable": true, + "start_ratio": 0.1, + "stop_ratio": 0.9, + "transition_smoothness": 0.1, + "stage_infos": [ + { + "backbone_factor": 1.2, + "backbone_offset": 0.5, + "backbone_width": 0.75, + "skip_factor": 0.9, + "skip_high_end_factor": 1.1, + "skip_cutoff": 0.3 + }, + { + "backbone_factor": 1.4, + "backbone_offset": 0.5, + "backbone_width": 0.75, + "skip_factor": 0.2, + "skip_high_end_factor": 1.1, + "skip_cutoff": 0.3 + }, + { + "backbone_factor": 1.1, + "backbone_offset": 0.5, + "backbone_width": 0.75, + "skip_factor": 0.9, + "skip_high_end_factor": 1.1, + "skip_cutoff": 0.3 + } + ] + }] + } + } +} +``` + +It is possible to omit any of the entries. For example: + +```json +{ + "alwayson_scripts": { + "freeu": { + "args": [{ + "start_ratio": 0.1, + "stage_infos": [ + { + "backbone_factor": 0.8, + "backbone_offset": 0.5, + "skip_high_end_factor": 0.9 + } + ] + }] + } + } +} +``` + +Here, since there is a single dict in the `stage_infos` array, freeu will only have an effect during the first stage of the unet. +If you want to modify only the second stage, prepend the `"stage_infos"` array with 1 empty dict `{}`. +If you want to modify only the third stage, prepend the `"stage_infos"` array with 2 empty dicts. + +If `"stop_ratio"` or `"start_ratio"` is an integer, then it is a step number. +Otherwise, it is expected to be a float between `0.0` and `1.0` and it represents a ratio of the total sampling steps. diff --git a/extensions/sd-webui-freeu/lib_free_u/__pycache__/global_state.cpython-310.pyc b/extensions/sd-webui-freeu/lib_free_u/__pycache__/global_state.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7bb7dcbc61d4ee0d00356291be1dda2016ce6f6 Binary files /dev/null and b/extensions/sd-webui-freeu/lib_free_u/__pycache__/global_state.cpython-310.pyc differ diff --git a/extensions/sd-webui-freeu/lib_free_u/__pycache__/unet.cpython-310.pyc b/extensions/sd-webui-freeu/lib_free_u/__pycache__/unet.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9706aeac36ff2042025e3da956b8ed1f2d90e790 Binary files /dev/null and b/extensions/sd-webui-freeu/lib_free_u/__pycache__/unet.cpython-310.pyc differ diff --git a/extensions/sd-webui-freeu/lib_free_u/__pycache__/xyz_grid.cpython-310.pyc b/extensions/sd-webui-freeu/lib_free_u/__pycache__/xyz_grid.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aae83b6a6f7e73d6001b608f2b40f970cca6bfc4 Binary files /dev/null and b/extensions/sd-webui-freeu/lib_free_u/__pycache__/xyz_grid.cpython-310.pyc differ diff --git a/extensions/sd-webui-freeu/lib_free_u/global_state.py b/extensions/sd-webui-freeu/lib_free_u/global_state.py new file mode 100644 index 0000000000000000000000000000000000000000..f01b89599f5c779aaff3d0088c0eb9f29250bb57 --- /dev/null +++ b/extensions/sd-webui-freeu/lib_free_u/global_state.py @@ -0,0 +1,201 @@ +import dataclasses +import inspect +import json +import pathlib +import re +import sys +from typing import Union, List, Any + + +@dataclasses.dataclass +class StageInfo: + backbone_factor: float = 1.0 + skip_factor: float = 1.0 + backbone_offset: float = 0.0 + backbone_width: float = 0.5 + skip_cutoff: float = 0.0 + skip_high_end_factor: float = 1.0 + # <- add new fields at the end here for png info backwards compatibility + + def to_dict(self, include_default=False): + default_stage_info = StageInfo() + res = vars(self).copy() + for k, v in res.copy().items(): + if not include_default and v == getattr(default_stage_info, k): + del res[k] + + return res + + def copy(self): + return StageInfo(**vars(self)) + + +STAGE_INFO_ARGS_LEN = len(inspect.getfullargspec(StageInfo.__init__)[0]) - 1 # off by one because of self +STAGES_COUNT = 3 +shorthand_re = re.compile(r"^([a-z]{1,2})([0-9]+)$") +all_versions = { + f"Version {version+1}": str(version+1) + for version in range(2) +} +reversed_all_versions = { + v: k + for k, v in all_versions.items() +} + +xyz_attrs: dict = {} +current_sampling_step: float = 0 + + +@dataclasses.dataclass +class State: + enable: bool = True + start_ratio: Union[float, int] = 0.0 + stop_ratio: Union[float, int] = 1.0 + transition_smoothness: float = 0.0 + version: str = "1" + stage_infos: List[Union[StageInfo, dict, Any]] = dataclasses.field(default_factory=lambda: [StageInfo() for _ in range(STAGES_COUNT)]) + + def __post_init__(self): + self.stage_infos = self.group_stage_infos() + self.version = self.format_version() + + def group_stage_infos(self): + res = [] + i = 0 + while i < len(self.stage_infos) and len(res) < STAGES_COUNT: + if isinstance(self.stage_infos[i], StageInfo): + res.append(self.stage_infos[i]) + i += 1 + elif isinstance(self.stage_infos[i], dict): + res.append(StageInfo(**self.stage_infos[i])) + i += 1 + else: + next_i = i + STAGE_INFO_ARGS_LEN + res.append(StageInfo(*self.stage_infos[i:next_i])) + i = next_i + + for _ in range(STAGES_COUNT - len(res)): + res.append(StageInfo()) + + return res + + def format_version(self): + if self.version not in reversed_all_versions: + return all_versions.get(self.version, "1") + + return str(self.version) + + def to_dict(self): + result = vars(self).copy() + result["stage_infos"] = [stage_info.to_dict() for stage_info in result["stage_infos"]] + del result["enable"] + return result + + def copy(self): + self_vars = vars(self) + old_stage_infos = self_vars["stage_infos"] + self_vars["stage_infos"] = old_stage_infos.copy() + for i, stage_info in enumerate(old_stage_infos): + self_vars["stage_infos"][i] = stage_info.copy() + + return State(**self_vars) + + def update_attr(self, key, value): + if match := shorthand_re.match(key): + char, index = match.group(1, 2) + stage_info = self.stage_infos[int(index)] + if char == "b": + stage_info.backbone_factor = value + elif char == "s": + stage_info.skip_factor = value + elif char == "o": + stage_info.backbone_offset = value + elif char == "w": + stage_info.backbone_width = value + elif char == "t": + stage_info.skip_cutoff = value + elif char == "h": + stage_info.skip_high_end_factor = value + else: + self.__dict__[key] = value + + +def apply_xyz(): + global instance + + if preset_key := xyz_attrs.get("preset"): + if preset := all_presets.get(preset_key): + instance = preset.copy() + elif preset_key != "UI Settings": + print("[sd-webui-freeu]", f"XYZ Preset '{preset_key}' does not exist", file=sys.stderr) + + for k, v in xyz_attrs.items(): + if k == "preset": + continue + + instance.update_attr(k, v) + + +STATE_ARGS_LEN = len(inspect.getfullargspec(State.__init__)[0]) - 1 # off by one because of self +PRESETS_PATH = pathlib.Path(__file__).parent.parent / "presets.json" + +instance = State() +default_presets = { + "SD1.4 Recommendations": State( + stage_infos=[ + StageInfo(1.2, 0.9), + StageInfo(1.4, 0.2), + StageInfo(1, 1), + ], + ), + "SD2.1 Recommendations": State( + stage_infos=[ + StageInfo(1.1, 0.9), + StageInfo(1.2, 0.2), + StageInfo(1, 1), + ], + ), + "SDXL Recommendations": State( + stage_infos=[ + StageInfo(1.1, 0.6), + StageInfo(1.2, 0.4), + StageInfo(1, 1), + ], + ), +} +all_presets = {} + + +def reload_presets(): + all_presets.clear() + all_presets.update(default_presets) + all_presets.update(load_presets()) + + +def load_presets(): + if not PRESETS_PATH.exists(): + return [] + + with open(PRESETS_PATH, "r") as f: + return { + k: State(**v) + for k, v in json.load(f).items() + } + + +def save_presets(presets=None): + if presets is None: + presets = get_user_presets() + + presets = {k: v.to_dict() for k, v in presets.items()} + + with open(PRESETS_PATH, "w") as f: + json.dump(presets, f) + + +def get_user_presets(): + return { + k: v + for k, v in all_presets.items() + if k not in default_presets + } diff --git a/extensions/sd-webui-freeu/lib_free_u/unet.py b/extensions/sd-webui-freeu/lib_free_u/unet.py new file mode 100644 index 0000000000000000000000000000000000000000..1d5c5eee1d6eba76494f9f22a10e9382db3608c9 --- /dev/null +++ b/extensions/sd-webui-freeu/lib_free_u/unet.py @@ -0,0 +1,200 @@ +import functools +import math +import pathlib +import sys +from typing import Tuple, Union, Optional +from lib_free_u import global_state +from modules import scripts, shared +from modules.sd_hijack_unet import th +import torch + + +def patch(): + th.cat = functools.partial(free_u_cat_hijack, original_function=th.cat) + + cn_script_paths = [ + str(pathlib.Path(scripts.basedir()).parent.parent / "extensions-builtin" / "sd-webui-controlnet"), + str(pathlib.Path(scripts.basedir()).parent / "sd-webui-controlnet"), + ] + sys.path[0:0] = cn_script_paths + cn_status = "enabled" + try: + import scripts.hook as controlnet_hook + except ImportError: + cn_status = "disabled" + else: + controlnet_hook.th.cat = functools.partial(free_u_cat_hijack, original_function=controlnet_hook.th.cat) + finally: + for p in cn_script_paths: + sys.path.remove(p) + + print("[sd-webui-freeu]", f"Controlnet support: *{cn_status}*") + + +def free_u_cat_hijack(hs, *args, original_function, **kwargs): + if not global_state.instance.enable: + return original_function(hs, *args, **kwargs) + + schedule_ratio = get_schedule_ratio() + if schedule_ratio == 0: + return original_function(hs, *args, **kwargs) + + try: + h, h_skip = hs + if list(kwargs.keys()) != ["dim"] or kwargs.get("dim", -1) != 1: + return original_function(hs, *args, **kwargs) + except ValueError: + return original_function(hs, *args, **kwargs) + + dims = h.shape[1] + try: + index = [1280, 640, 320].index(dims) + stage_info = global_state.instance.stage_infos[index] + except ValueError: + stage_info = None + + if stage_info is not None: + region_begin, region_end, region_inverted = ratio_to_region(stage_info.backbone_width, stage_info.backbone_offset, dims) + mask = torch.arange(dims, device=h.device) + mask = (region_begin <= mask) & (mask <= region_end) + if region_inverted: + mask = ~mask + mask = mask.reshape(1, -1, 1, 1).to(h.dtype) + + scale = get_backbone_scale( + h, + backbone_factor=lerp(1, stage_info.backbone_factor, schedule_ratio), + ) + h *= mask * scale + (1 - mask) + + h_skip = filter_skip( + h_skip, + threshold=stage_info.skip_cutoff, + scale=lerp(1, stage_info.skip_factor, schedule_ratio), + scale_high=lerp(1, stage_info.skip_high_end_factor, schedule_ratio), + ) + + return original_function([h, h_skip], *args, **kwargs) + + +def get_backbone_scale(h, backbone_factor): + if global_state.instance.version == "1": + return backbone_factor + + #if global_state.instance.version == "2": + features_mean = h.mean(1, keepdim=True) + batch_dims = h.shape[0] + features_max, _ = torch.max(features_mean.view(batch_dims, -1), dim=-1, keepdim=True) + features_min, _ = torch.min(features_mean.view(batch_dims, -1), dim=-1, keepdim=True) + hidden_mean = (features_mean - features_min.unsqueeze(2).unsqueeze(3)) / (features_max - features_min).unsqueeze(2).unsqueeze(3) + return 1 + (backbone_factor - 1) * hidden_mean + + +def filter_skip(x, threshold, scale, scale_high): + if scale == 1 and scale_high == 1: + return x + + fft_device = x.device + if not is_gpu_complex_supported(x): + fft_device = "cpu" + + # FFT + x_freq = torch.fft.fftn(x.to(fft_device, dtype=torch.float32), dim=(-2, -1)) + x_freq = torch.fft.fftshift(x_freq, dim=(-2, -1)) + + B, C, H, W = x_freq.shape + mask = torch.full((B, C, H, W), float(scale_high), device=fft_device) + + crow, ccol = H // 2, W // 2 + threshold_row = max(1, math.floor(crow * threshold)) + threshold_col = max(1, math.floor(ccol * threshold)) + mask[..., crow - threshold_row:crow + threshold_row, ccol - threshold_col:ccol + threshold_col] = scale + x_freq *= mask + + # IFFT + x_freq = torch.fft.ifftshift(x_freq, dim=(-2, -1)) + x_filtered = torch.fft.ifftn(x_freq, dim=(-2, -1)).real.to(device=x.device, dtype=x.dtype) + + return x_filtered + + +def ratio_to_region(width: float, offset: float, n: int) -> Tuple[int, int, bool]: + if width < 0: + offset += width + width = -width + width = min(width, 1) + + if offset < 0: + offset = 1 + offset - int(offset) + offset = math.fmod(offset, 1.0) + + if width + offset <= 1: + inverted = False + start = offset * n + end = (width + offset) * n + else: + inverted = True + start = (width + offset - 1) * n + end = offset * n + + return round(start), round(end), inverted + + +def get_schedule_ratio(): + start_step = to_denoising_step(global_state.instance.start_ratio) + stop_step = to_denoising_step(global_state.instance.stop_ratio) + + if start_step == stop_step: + smooth_schedule_ratio = 0.0 + elif global_state.current_sampling_step < start_step: + smooth_schedule_ratio = min(1.0, max(0.0, global_state.current_sampling_step / start_step)) + else: + smooth_schedule_ratio = min(1.0, max(0.0, 1 + (global_state.current_sampling_step - start_step) / (start_step - stop_step))) + + flat_schedule_ratio = 1.0 if start_step <= global_state.current_sampling_step < stop_step else 0.0 + + return lerp(flat_schedule_ratio, smooth_schedule_ratio, global_state.instance.transition_smoothness) + + +def to_denoising_step(number: Union[float, int], steps=None) -> int: + if steps is None: + steps = shared.state.sampling_steps + + if isinstance(number, float): + return int(number * steps) + + return number + + +def lerp(a, b, r): + return (1-r)*a + r*b + + +gpu_complex_support: Optional[bool] = None +def is_gpu_complex_supported(x): + global gpu_complex_support + + if x.is_cpu: + return True + + if gpu_complex_support is not None: + return gpu_complex_support + + # catch known cases in advance + mps_available = hasattr(torch.backends, "mps") and torch.backends.mps.is_available() + try: + import torch_directml + except ImportError: + dml_available = False + else: + dml_available = torch_directml.is_available() + + gpu_complex_support = not (mps_available or dml_available) + if gpu_complex_support: + # try filter_skip fft to make sure it is viable on the gpu + try: + torch.fft.fftn(x.float(), dim=(-2, -1)) + except RuntimeError: + gpu_complex_support = False + + return gpu_complex_support diff --git a/extensions/sd-webui-freeu/lib_free_u/xyz_grid.py b/extensions/sd-webui-freeu/lib_free_u/xyz_grid.py new file mode 100644 index 0000000000000000000000000000000000000000..0d3c79bd9493efa5bcf352a4f86fd4a17eb68884 --- /dev/null +++ b/extensions/sd-webui-freeu/lib_free_u/xyz_grid.py @@ -0,0 +1,82 @@ +import sys +from types import ModuleType +from typing import Optional +from modules import scripts +from lib_free_u import global_state + + +def patch(): + xyz_module = find_xyz_module() + if xyz_module is None: + print("[sd-webui-freeu]", "xyz_grid.py not found.", file=sys.stderr) + return + xyz_module.axis_options.extend([ + xyz_module.AxisOption("[FreeU] Enabled", str_to_bool, apply_global_state("enable"), choices=choices_bool), + xyz_module.AxisOption("[FreeU] Version", str, apply_global_state("version", key_map=global_state.all_versions), choices=choices_version), + xyz_module.AxisOption("[FreeU] Preset", str, apply_global_state("preset"), choices=choices_preset), + xyz_module.AxisOption("[FreeU] Start At Step", int_or_float, apply_global_state("start_ratio")), + xyz_module.AxisOption("[FreeU] Stop At Step", int_or_float, apply_global_state("stop_ratio")), + xyz_module.AxisOption("[FreeU] Transition Smoothness", int_or_float, apply_global_state("transition_smoothness")), + *[ + opt + for index in range(global_state.STAGES_COUNT) + for opt in [ + xyz_module.AxisOption(f"[FreeU] Stage {index+1} Backbone Scale", float, apply_global_state(f"b{index}")), + xyz_module.AxisOption(f"[FreeU] Stage {index+1} Backbone Offset", float, apply_global_state(f"o{index}")), + xyz_module.AxisOption(f"[FreeU] Stage {index+1} Backbone Width", float, apply_global_state(f"w{index}")), + xyz_module.AxisOption(f"[FreeU] Stage {index+1} Skip Scale", float, apply_global_state(f"s{index}")), + xyz_module.AxisOption(f"[FreeU] Stage {index+1} Skip Cutoff", float, apply_global_state(f"t{index}")), + xyz_module.AxisOption(f"[FreeU] Stage {index+1} Skip High End Scale", float, apply_global_state(f"h{index}")), + ] + ] + ]) + + +def apply_global_state(k, key_map=None): + def callback(_p, v, _vs): + if key_map is not None: + v = key_map[v] + global_state.xyz_attrs[k] = v + + return callback + + +def str_to_bool(string): + string = str(string) + if string in ["None", ""]: + return None + elif string.lower() in ["true", "1"]: + return True + elif string.lower() in ["false", "0"]: + return False + else: + raise ValueError(f"Could not convert string to boolean: {string}") + + +def int_or_float(string): + try: + return int(string) + except ValueError: + return float(string) + + +def choices_bool(): + return ["False", "True"] + + +def choices_version(): + return list(global_state.all_versions.keys()) + + +def choices_preset(): + presets = list(global_state.all_presets.keys()) + presets.insert(0, "UI Settings") + return presets + + +def find_xyz_module() -> Optional[ModuleType]: + for data in scripts.scripts_data: + if data.script_class.__module__ in {"xyz_grid.py", "xy_grid.py"} and hasattr(data, "module"): + return data.module + + return None diff --git a/extensions/sd-webui-freeu/scripts/__pycache__/freeu.cpython-310.pyc b/extensions/sd-webui-freeu/scripts/__pycache__/freeu.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..686d2868c05dd1f16922e094221cec038bfdba13 Binary files /dev/null and b/extensions/sd-webui-freeu/scripts/__pycache__/freeu.cpython-310.pyc differ diff --git a/extensions/sd-webui-freeu/scripts/freeu.py b/extensions/sd-webui-freeu/scripts/freeu.py new file mode 100644 index 0000000000000000000000000000000000000000..dfce8f2243c98b93802bd0f448b1c5611925625c --- /dev/null +++ b/extensions/sd-webui-freeu/scripts/freeu.py @@ -0,0 +1,441 @@ +import json +import gradio as gr +from modules import scripts, script_callbacks, processing, shared +from lib_free_u import global_state, unet, xyz_grid + + +txt2img_steps_component = None +img2img_steps_component = None +txt2img_steps_callbacks = [] +img2img_steps_callbacks = [] + + +class FreeUScript(scripts.Script): + def title(self): + return "FreeU" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + global_state.reload_presets() + default_stage_infos = next(iter(global_state.all_presets.values())).stage_infos + + with gr.Accordion(open=False, label=self.title()): + with gr.Row(): + with gr.Row(): + enabled = gr.Checkbox( + label="Enable", + value=False, + ) + + version = gr.Dropdown( + show_label=False, + elem_id=self.elem_id("version"), + choices=list(global_state.all_versions.keys()), + value=next(iter(reversed(global_state.all_versions.keys()))), + ) + + preset_name = gr.Dropdown( + show_label=False, + choices=list(global_state.all_presets.keys()), + value=next(iter(global_state.all_presets.keys())), + type="value", + elem_id=self.elem_id("preset_name"), + allow_custom_value=True, + tooltip="Apply button loads settings\nWrite custom name to enable save\nDelete automatically will save to file", + size="sm", + ) + + is_custom_preset = preset_name.value not in global_state.default_presets + preset_exists = preset_name.value in global_state.all_presets + + apply_preset = gr.Button( + value="✅", + size="lg", + elem_classes="tool", + interactive=preset_exists, + ) + save_preset = gr.Button( + value="💾", + size="lg", + elem_classes="tool", + interactive=is_custom_preset, + ) + refresh_presets = gr.Button( + value="🔄", + size="lg", + elem_classes="tool" + ) + delete_preset = gr.Button( + value="🗑️", + size="lg", + elem_classes="tool", + interactive=is_custom_preset and preset_exists, + ) + + with gr.Row(): + start_ratio = gr.Slider( + label="Start At Step", + elem_id=self.elem_id("start_at_step"), + minimum=0, + maximum=1, + value=0, + ) + + stop_ratio = gr.Slider( + label="Stop At Step", + elem_id=self.elem_id("stop_at_step"), + minimum=0, + maximum=1, + value=1, + ) + + transition_smoothness = gr.Slider( + label="Transition Smoothness", + elem_id=self.elem_id("transition_smoothness"), + minimum=0, + maximum=1, + value=0, + ) + + flat_stage_infos = [] + + for index in range(global_state.STAGES_COUNT): + stage_n = index + 1 + default_stage_info = default_stage_infos[index] + + with gr.Accordion(open=index < 2, label=f"Stage {stage_n}"): + with gr.Row(): + backbone_scale = gr.Slider( + label=f"Backbone {stage_n} Scale", + elem_id=self.elem_id(f"backbone_scale_{stage_n}"), + minimum=-1, + maximum=3, + value=default_stage_info.backbone_factor, + ) + + backbone_offset = gr.Slider( + label=f"Backbone {stage_n} Offset", + elem_id=self.elem_id(f"backbone_offset_{stage_n}"), + minimum=0, + maximum=1, + value=default_stage_info.backbone_offset, + ) + + backbone_width = gr.Slider( + label=f"Backbone {stage_n} Width", + elem_id=self.elem_id(f"backbone_width_{stage_n}"), + minimum=0, + maximum=1, + value=default_stage_info.backbone_width, + ) + + with gr.Row(): + skip_scale = gr.Slider( + label=f"Skip {stage_n} Scale", + elem_id=self.elem_id(f"skip_scale_{stage_n}"), + minimum=-1, + maximum=3, + value=default_stage_info.skip_factor, + ) + + skip_high_end_scale = gr.Slider( + label=f"Skip {stage_n} High End Scale", + elem_id=self.elem_id(f"skip_high_end_scale_{stage_n}"), + minimum=-1, + maximum=3, + value=default_stage_info.skip_high_end_factor, + ) + + skip_cutoff = gr.Slider( + label=f"Skip {stage_n} Cutoff", + elem_id=self.elem_id(f"skip_cutoff_{stage_n}"), + minimum=0.0, + maximum=1.0, + value=default_stage_info.skip_cutoff, + ) + + flat_stage_infos.extend([ + backbone_scale, + skip_scale, + backbone_offset, + backbone_width, + skip_cutoff, + skip_high_end_scale, + ]) + + def on_preset_name_change(preset_name): + is_custom_preset = preset_name not in global_state.default_presets + preset_exists = preset_name in global_state.all_presets + return ( + gr.Button.update(interactive=preset_exists), + gr.Button.update(interactive=is_custom_preset), + gr.Button.update(interactive=is_custom_preset and preset_exists), + ) + + preset_name.change( + fn=on_preset_name_change, + inputs=[preset_name], + outputs=[apply_preset, save_preset, delete_preset], + ) + + def on_apply_click(user_settings_name): + preset = global_state.all_presets[user_settings_name] + return ( + gr.Slider.update(value=preset.start_ratio), + gr.Slider.update(value=preset.stop_ratio), + gr.Slider.update(value=preset.transition_smoothness), + *[ + gr.update(value=v) + for stage_info in preset.stage_infos + for v in stage_info.to_dict(include_default=True).values() + ], + ) + + apply_preset.click( + fn=on_apply_click, + inputs=[preset_name], + outputs=[start_ratio, stop_ratio, transition_smoothness, *flat_stage_infos], + ) + + def on_save_click(preset_name, start_ratio, stop_ratio, transition_smoothness, *flat_stage_infos): + global_state.all_presets[preset_name] = global_state.State( + stage_infos=flat_stage_infos, + start_ratio=start_ratio, + stop_ratio=stop_ratio, + transition_smoothness=transition_smoothness, + ) + global_state.save_presets() + + return ( + gr.Dropdown.update(choices=list(global_state.all_presets.keys())), + gr.Button.update(interactive=True), + gr.Button.update(interactive=True), + ) + + save_preset.click( + fn=on_save_click, + inputs=[preset_name, start_ratio, stop_ratio, transition_smoothness, *flat_stage_infos], + outputs=[preset_name, apply_preset, delete_preset], + ) + + def on_refresh_click(preset_name): + global_state.reload_presets() + is_custom_preset = preset_name not in global_state.default_presets + preset_exists = preset_name in global_state.all_presets + + return ( + gr.Dropdown.update(value=preset_name, choices=list(global_state.all_presets.keys())), + gr.Button.update(interactive=preset_exists), + gr.Button.update(interactive=is_custom_preset), + gr.Button.update(interactive=is_custom_preset and preset_exists), + ) + + refresh_presets.click( + fn=on_refresh_click, + inputs=[preset_name], + outputs=[preset_name, apply_preset, save_preset, delete_preset], + ) + + def on_delete_click(preset_name): + preset_name_index = list(global_state.all_presets.keys()).index(preset_name) + del global_state.all_presets[preset_name] + global_state.save_presets() + + preset_name_index = min(len(global_state.all_presets) - 1, preset_name_index) + preset_names = list(global_state.all_presets.keys()) + preset_name = preset_names[preset_name_index] + + is_custom_preset = preset_name not in global_state.default_presets + preset_exists = preset_name in global_state.all_presets + return ( + gr.Dropdown.update(value=preset_name, choices=preset_names), + gr.Button.update(interactive=preset_exists), + gr.Button.update(interactive=is_custom_preset), + gr.Button.update(interactive=is_custom_preset and preset_exists), + ) + + delete_preset.click( + fn=on_delete_click, + inputs=[preset_name], + outputs=[preset_name, apply_preset, save_preset, delete_preset], + ) + + schedule_infotext = gr.HTML(visible=False, interactive=False) + stages_infotext = gr.HTML(visible=False, interactive=False) + version_infotext = gr.HTML(visible=False, interactive=False) + + def register_schedule_infotext_change(steps_component): + schedule_infotext.change( + fn=self.on_schedule_infotext_update, + inputs=[schedule_infotext, steps_component], + outputs=[schedule_infotext, start_ratio, stop_ratio, transition_smoothness], + ) + + steps_component, steps_callbacks = ( + (img2img_steps_component, img2img_steps_callbacks) + if is_img2img else + (txt2img_steps_component, txt2img_steps_callbacks) + ) + + if steps_component is None: + steps_callbacks.append(register_schedule_infotext_change) + else: + register_schedule_infotext_change(steps_component) + + stages_infotext.change( + fn=self.on_stages_infotext_update, + inputs=[stages_infotext], + outputs=[stages_infotext, enabled, *flat_stage_infos], + ) + + version_infotext.change( + fn=self.on_version_infotext_update, + inputs=[version_infotext], + outputs=[version_infotext, version] + ) + + self.infotext_fields = [ + (schedule_infotext, "FreeU Schedule"), + (stages_infotext, "FreeU Stages"), + (version_infotext, "FreeU Version"), + ] + self.paste_field_names = [f for _, f in self.infotext_fields] + + return enabled, start_ratio, stop_ratio, transition_smoothness, version, *flat_stage_infos + + def on_schedule_infotext_update(self, infotext, steps): + if not infotext: + return (gr.skip(),) * 4 + + start_ratio, stop_ratio, transition_smoothness, *_ = infotext.split(", ") + + return ( + gr.update(value=""), + gr.update(value=unet.to_denoising_step(xyz_grid.int_or_float(start_ratio), steps) / steps), + gr.update(value=unet.to_denoising_step(xyz_grid.int_or_float(stop_ratio), steps) / steps), + gr.update(value=float(transition_smoothness)), + ) + + def on_stages_infotext_update(self, infotext): + if not infotext: + return (gr.skip(),) * (2 + global_state.STAGES_COUNT * global_state.STAGE_INFO_ARGS_LEN) + + stage_infos = json.loads(infotext) + stage_infos = [ + global_state.StageInfo(**stage_info) + for stage_info in stage_infos + ] + stage_infos.extend([ + global_state.StageInfo() + for _ in range(global_state.STAGES_COUNT - len(stage_infos)) + ]) + + return ( + gr.update(value=""), + gr.update(value=shared.opts.data.get("freeu_png_info_auto_enable", True)), + *( + gr.update(value=v) + for stage_info in stage_infos + for v in stage_info.to_dict(include_default=True).values() + ) + ) + + def on_version_infotext_update(self, infotext): + if not infotext: + return (gr.skip(),) * 2 + + return ( + gr.update(value=""), + gr.update(value=global_state.reversed_all_versions.get(infotext, infotext)), + ) + + def process( + self, + p: processing.StableDiffusionProcessing, + *args + ): + if isinstance(args[0], dict): + global_state.instance = global_state.State(**args[0]) + elif isinstance(args[0], bool): + stage_infos_begin = global_state.STATE_ARGS_LEN - 1 + global_state.instance = global_state.State( + args[0], + *[float(n) for n in args[1:stage_infos_begin-1]], + args[stage_infos_begin-1], + args[stage_infos_begin:], + ) + else: + raise TypeError(f"Unrecognized args sequence starting with type {type(args[0])}") + + global_state.apply_xyz() + global_state.xyz_attrs.clear() + if not global_state.instance.enable: + return + + last_d = False + p.extra_generation_params["FreeU Stages"] = json.dumps(list(reversed([ + stage_info.to_dict() + for stage_info in reversed(global_state.instance.stage_infos) + # strip all empty dicts + if last_d or stage_info.to_dict() and (last_d := True) + ]))) + p.extra_generation_params["FreeU Schedule"] = ", ".join([ + str(global_state.instance.start_ratio), + str(global_state.instance.stop_ratio), + str(global_state.instance.transition_smoothness), + ]) + p.extra_generation_params["FreeU Version"] = global_state.instance.version + + def process_batch(self, p, *args, **kwargs): + global_state.current_sampling_step = 0 + + +def increment_sampling_step(*_args, **_kwargs): + global_state.current_sampling_step += 1 + + +try: + script_callbacks.on_cfg_after_cfg(increment_sampling_step) +except AttributeError: + # webui < 1.6.0 + # normally we should increment the current sampling step after cfg + # but as long as we don't need to run code during cfg it should be fine to increment early + script_callbacks.on_cfg_denoised(increment_sampling_step) + + +def on_after_component(component, **kwargs): + global txt2img_steps_component, img2img_steps_component + + if kwargs.get("elem_id", None) == "img2img_steps": + img2img_steps_component = component + for callback in img2img_steps_callbacks: + callback(component) + + if kwargs.get("elem_id", None) == "txt2img_steps": + txt2img_steps_component = component + for callback in txt2img_steps_callbacks: + callback(component) + + +script_callbacks.on_after_component(on_after_component) + + +def on_ui_settings(): + section = ("freeu", "FreeU") + shared.opts.add_option( + "freeu_png_info_auto_enable", + shared.OptionInfo( + default=True, + label="Auto enable when loading the PNG Info of a generation that used FreeU", + section=section, + ) + ) + + +script_callbacks.on_ui_settings(on_ui_settings) + + +unet.patch() +xyz_grid.patch() diff --git a/extensions/sd-webui-hires-fix-tweaks/.gitattributes b/extensions/sd-webui-hires-fix-tweaks/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..dfe0770424b2a19faf507a501ebfc23be8f54e7b --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/extensions/sd-webui-hires-fix-tweaks/.gitignore b/extensions/sd-webui-hires-fix-tweaks/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d9005f2cc7fc4e65f14ed5518276007c08cf2fd0 --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/.gitignore @@ -0,0 +1,152 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/extensions/sd-webui-hires-fix-tweaks/LICENSE b/extensions/sd-webui-hires-fix-tweaks/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e20b431bcb69267acc39eaf8fdb94c30f40e3223 --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/LICENSE @@ -0,0 +1,661 @@ +GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/extensions/sd-webui-hires-fix-tweaks/README.md b/extensions/sd-webui-hires-fix-tweaks/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0db559282552b72aa15c274c498897dbcc2b24e7 --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/README.md @@ -0,0 +1,105 @@ +# sd webui hires fix tweaks + +Add additional options and features to hires fix for [Stable Diffusion web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) +![screenshot](screenshot.png) + +## Features + +1. Hires pass CFG scale + - Set a different CFG scale for hires pass
this is particularly useful if you're working with LCM, can also a achieve interesting effects + +2. Hires Batch and Seed + - Generate multiple hires pass form first pass image + - Specify different seed for hires pass + +3. Hires prompt mode and Remove First Pass Extra Networks + - Changes how the hires prompt is created based on the first pass prompt + 1. `Default`: Webui default behavior:
if blank same as first pass else use hires prompt + 2. `Append`: Append hires prompt after first pass prompt + 3. `Prepend`: Prepend hires prompt before first pass prompt + 4. `Prompt S/R`: Prompt Search and Replace:
replace or insert first pass prompt with hires prompt +

+ - `Remove First Pass Extra Networks`: When check will remove extra networks from first pass prompt before creating hires prompt +4. Hires output directory + - Specify a different output directory for hires pass + - `Settings > Paths for saving > Output directory for hires. fix images` + +## Remove First Pass Extra Networks +- `Remove First Pass Extra Networks` operates by removing all extra networks in prompt (every thin matching ``) in the first pass prompt, this means it only works with `LoRA` and `Hypernetworks` and dose not work with `Textual Inversion Embeddings` or if the extra networks are added by later by other means such as `Setting` or `Styles`. +- As it only touches the first pass prompt it will not have an affect if `Hires prompt mode` is `Default` and the hires prompt is not blank, as the user input hires prompt will take precedence. +- - The default behavior of the webui is If `hires prompt is not specfied (when blank)` then `use the same first pass prompt for hires pass` else `use the user input hires prompt`. if the first pass prompt is not use the `Remove First Pass Extra Networks` will not have an affect. +Usage case example: +1. You wish the hires prompt to be the same as the first pass prompt but without the extra networks +2. Combining with other `Hires prompt mode` such as `Append` to change the extra networks used during hires pass +> Note: the `Default` Hires prompt mode has priority over `Remove First Pass Extra Networks`, in other words if `Default` is selected `Remove First Pass Extra Networks` will be ignored if hires prompt is not blank + +## Prompt Search and Replace syntax +When in Prompt Search and Replace mode the first pass prompt will be used as the base template to perform a search and replace on to create the hires prompt. + +the first pass can be any regular prompt, or you can insert `@insert_marker@` anywhere in the prompt to indicate where the hires prompt should be inserted. +the `@insert_marker@` will be removed from the prompt before image generation. + +example first pass prompt +``` +this is an example prompt @insert_marker@, and replace prompt some more replace with blank prompt, +prompt to +replace can be +multil-ine +like so +``` + +the hires prompt is used as instructions for the search and replace +each instruction is entry starts with a `key_word` enclosed by `@` at the start of the line, everything after this until the next `@key_word@` is the replacement or inserted prompt + +Example hires prompt (instructions) + +``` +@insert_marker@ insert something here +@replace prompt@ replace something here +@replace with blank@ +@can be +multil-ine@replacement can +be multi-line +``` + +- `@insert_marker@` exists in the prompt it will perform insert mode `insert something here` will be inserted in its place of `@insert_marker@` the `@insert_marker@` itself will be removed from the prompt + +- `@replace prompt@` dose not in the prompt it will perform replace mode, the prompt will be unchanged but the hires prompt will have `replace prompt` replaced with `replace something here` + +- `@replace with blank@` similar to `@replace prompt@` but the replacement is `"empty"`, this effectively this removes the `replace with blank` from the hires prompt + +- both search phrase and replacement can be multi-line + +if you need a literal `@` in the instructions then you can escape it by doubling it `@@` +you can also change the character used to enclose the `key_word` by setting the `@` to some other character + +This is the resulting using the above prompt and hires prompt +resulting prompt +``` +this is an example prompt , and replace prompt some more replace with blank prompt, +prompt to +replace can be +multil-ine +like so +``` + +Resulting hires prompt + +``` +this is an example prompt insert something here +, and replace something here + some more prompt, +prompt to +replace replacement can +be multi-line +like so +``` + +### Notes +- `Remove First Pass Extra Networks` and `Hires prompt mode` are performed very early in the image generation pipline, even before `Styles` is applied to prompts, which means that it will not affect the prompt added by `Styles`. it should only affect the style you can visibly see in the prompt input box. +- - Features added can be hidden in settings if you do use them +- If you would like to suggest a feature feel free to open an issue or pull request +- I position this extension as "Workflow improvement" tool, in general, this extensions functionality should be possible to do without this extension, even if might require multiple steps +- - `Hires pass CFG scale` and `Hires Batch and Seed` can be archived by manually performing img2img on the txt2img output. +- - `Hires prompt mode` and `Remove First Pass Extra Networks` can be archived by manually editing the prompt before generating the hires pass. +- - `Hires output directory` can be archived by manually changing the output directory in the webui settings diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/settings.cpython-310.pyc b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/settings.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54811d7c977df329e49f7bfac053150c59fd02a7 Binary files /dev/null and b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/settings.cpython-310.pyc differ diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/ui.cpython-310.pyc b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/ui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9de77e436bdf94080a46e43d48a082a66cd8a6b4 Binary files /dev/null and b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/ui.cpython-310.pyc differ diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/utils.cpython-310.pyc b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee0028f380ad8fb9bfb561e45f87c14a68bf4800 Binary files /dev/null and b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/utils.cpython-310.pyc differ diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/xyz.cpython-310.pyc b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/xyz.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37ede1cfcb3036100d82da2faa0b2d044e7bac97 Binary files /dev/null and b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/__pycache__/xyz.cpython-310.pyc differ diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_batch_seed.cpython-310.pyc b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_batch_seed.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8e30fbd9a667e1ffa2c8cfc511f11fe1801ba62 Binary files /dev/null and b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_batch_seed.cpython-310.pyc differ diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_cfg_scale.cpython-310.pyc b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_cfg_scale.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84b4b05420aeb0f80847698ca5e772307d3bf275 Binary files /dev/null and b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_cfg_scale.cpython-310.pyc differ diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_output_dir.cpython-310.pyc b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_output_dir.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47248c82f07df9bd0acfe985846333d173148f94 Binary files /dev/null and b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_output_dir.cpython-310.pyc differ diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_prompt_mode.cpython-310.pyc b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_prompt_mode.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..babe82171f689cacc755719739e9d15e52096df7 Binary files /dev/null and b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/__pycache__/hr_prompt_mode.cpython-310.pyc differ diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_batch_seed.py b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_batch_seed.py new file mode 100644 index 0000000000000000000000000000000000000000..bc1743559e16b37c8a8f186c803c2f24925d4270 --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_batch_seed.py @@ -0,0 +1,375 @@ +from modules import errors, patches, processing, shared, script_callbacks, images, sd_models +from hires_fix_tweaks.utils import dumps_quote_swap_json, loads_quote_swap_json +from PIL import ImageChops +import inspect +import random + + +def same_img_pil(img1, img2): + return img1.size == img2.size and ImageChops.difference(img1, img2).getbbox() is None + + +def create_infotext_hijack(create_infotext, script_class): + create_infotext_signature = inspect.signature(create_infotext) + + def wrapped_function(*args, **kwargs): + try: + bind_args = create_infotext_signature.bind(*args, **kwargs) + bind_args.apply_defaults() + bind_args = bind_args.arguments + p = bind_args['p'] + index = bind_args['index'] + use_main_prompt = bind_args['use_main_prompt'] + + try: + hires_batch_seed = next((obj for obj in p.scripts.alwayson_scripts if isinstance(obj, script_class))).hires_batch_seed + + if use_main_prompt or hires_batch_seed.force_write_hr_info_flag: + # params.txt and grid + seeds = p.all_seeds + subseeds = p.all_subseeds + hr_seeds = hires_batch_seed.all_hr_seeds + hr_subseeds = hires_batch_seed.all_hr_subseeds + index = 0 + elif index is None: + # intermediate images + assert False + else: + # hr batch results + seeds = p.seeds + subseeds = p.subseeds + hr_seeds = hires_batch_seed.hr_seeds + hr_subseeds = hires_batch_seed.hr_subseeds + + hires_seed_diff = [ + seeds[index] != hr_seeds[index], + p.subseed_strength != hires_batch_seed.hr_subseed_strength, + hires_batch_seed.hr_subseed_strength > 0 and subseeds[index] != hr_subseeds[index], + p.seed_resize_from_w != hires_batch_seed.hr_seed_resize_from_w, + p.seed_resize_from_h != hires_batch_seed.hr_seed_resize_from_h, + ] + if any(hires_seed_diff): + hr_seed_info = {} + if hires_seed_diff[0]: + hr_seed_info['Seed'] = hr_seeds[index] + + if hires_batch_seed.hr_subseed_strength > 0: + if p.subseed_strength <= 0 or subseeds[index] != hr_subseeds[index]: + hr_seed_info['Subseed'] = hr_subseeds[index] + hr_seed_info['Strength'] = hires_batch_seed.hr_subseed_strength + + if hires_batch_seed.hr_seed_resize_from_w > 0 and hires_batch_seed.hr_seed_resize_from_h > 0: + hr_seed_info['Resize'] = [hires_batch_seed.hr_seed_resize_from_w, hires_batch_seed.hr_seed_resize_from_h] + + # store hr_seed_info as json string with double and single quotes swapped + p.extra_generation_params['Hires seed'] = dumps_quote_swap_json(hr_seed_info) + else: + assert False + + except Exception: + # remove hr_seed_info + p.extra_generation_params.pop('Hires seed', None) + + except Exception: + errors.report(f"create infotext hijack failed {__name__}") + + finally: + results = create_infotext(*args, **kwargs) + return results + + return wrapped_function + + +def hijack_create_infotext(script_class): + try: + patches.patch(__name__, processing, 'create_infotext', create_infotext_hijack(processing.create_infotext, script_class)) + + def undo_hijack(): + patches.undo(__name__, processing, 'create_infotext') + + script_callbacks.on_script_unloaded(undo_hijack) + except RuntimeError: + pass + + +def parse_infotext(infotext, params): + try: + params['Hires seed'] = loads_quote_swap_json(params['Hires seed']) + except Exception: + pass + + +class HiresBatchSeed: + def __init__(self, script): + self.script = script + + self.enable = None + + self.first_pass_seeds = None + self.first_pass_subseeds = None + self.first_pass_seed_resize_from_w = None + self.first_pass_seed_resize_from_h = None + + self.hr_batch_count = None + self.enable_hr_seed = None + self.hr_seed = None + self.hr_seed_enable_extras = None + self.hr_subseed = None + self.hr_subseed_strength = None + self.hr_seed_resize_from_w = None + self.hr_seed_resize_from_h = None + + self.all_hr_seeds = None + self.all_hr_subseeds = None + self.hr_seeds = None + self.hr_subseeds = None + self.force_write_hr_info_flag = None + self.resize_image_cache = None + + self.update_progress_bar = None + self.patch_get_hr_prompt = None + + self.original_get_hr_prompt = None + self.original_get_hr_negative_prompt = None + + def setup(self, p, *args): + # cleanup + self.force_write_hr_info_flag = None + self.hr_seeds = None + self.hr_subseed_strength = None + self.hr_subseeds = None + self.hr_seed_resize_from_w = None + self.hr_seed_resize_from_h = None + self.original_get_hr_prompt = None + self.original_get_hr_negative_prompt = None + + def process(self, p, *args): + self.hr_batch_count = args[4] # multi hr seed + self.enable_hr_seed = args[5] + + self.update_progress_bar = self.patch_get_hr_prompt = self.hr_batch_count > 1 + + self.enable = p.enable_hr and (self.enable_hr_seed or self.update_progress_bar) + # if hr_disabled or hr batch count <= 1 and hr seed is disabled then module is disabled + if not self.enable: + # module is disabled + return + + if self.enable_hr_seed: + self.hr_seed = args[6] + self.hr_seed_enable_extras = args[7] + if self.hr_seed_enable_extras: + self.hr_subseed = args[8] + self.hr_subseed_strength = args[9] + self.hr_seed_resize_from_w = args[10] + self.hr_seed_resize_from_h = args[11] + if self.hr_seed_resize_from_w <= 0 or self.hr_seed_resize_from_h <= 0: + self.hr_seed_resize_from_w = -1 + self.hr_seed_resize_from_h = -1 + else: + self.hr_subseed = 0 + self.hr_subseed_strength = 0 + self.hr_seed_resize_from_w = -1 + self.hr_seed_resize_from_h = -1 + + self.force_write_hr_info_flag = True + else: + # enable_hr_seed is false use first pass seed + self.hr_seed = 0 + self.hr_subseed = 0 + self.hr_subseed_strength = p.subseed_strength + self.hr_seed_resize_from_w = p.seed_resize_from_w + self.hr_seed_resize_from_h = p.seed_resize_from_h + + self.init_hr_seeds(p) + + p.sample_hr_pass = self.sample_hr_pass_hijack(p, p.sample_hr_pass) + p.sample = self.sample_hijack(p, p.sample) + + def before_process_batch(self, p, *args, **kwargs): + if not self.enable: + return + + if self.patch_get_hr_prompt: + # fix for 1.9 + if self.original_get_hr_prompt: + p.extra_generation_params['Hires prompt'] = self.original_get_hr_prompt + self.original_get_hr_prompt = None + if self.original_get_hr_negative_prompt: + p.extra_generation_params['Hires negative prompt'] = self.original_get_hr_negative_prompt + self.original_get_hr_negative_prompt = None + + if not self.update_progress_bar: + return + self.update_progress_bar = False + # known issue: progress may break when using scripts like xyz grid + additional_hr_batch_count = (self.hr_batch_count - 1) * p.n_iter + shared.state.job_count += additional_hr_batch_count + if shared.opts.multiple_tqdm and not shared.cmd_opts.disable_console_progressbars and shared.total_tqdm._tqdm and shared.total_tqdm._tqdm.total: + shared.total_tqdm.updateTotal(shared.total_tqdm._tqdm.total + additional_hr_batch_count * (p.hr_second_pass_steps or p.steps)) + + def process_batch(self, p, *args, **kwargs): + if not self.enable: + return + self.hr_seeds = self.all_hr_seeds + self.hr_subseeds = self.all_hr_subseeds + + def postprocess_batch_list(self, p, pp, *args, **kwargs): + if not self.enable: + return + p.prompts = p.prompts * self.hr_batch_count + p.negative_prompts = p.negative_prompts * self.hr_batch_count + p.seeds = p.seeds * self.hr_batch_count + p.subseeds = p.subseeds * self.hr_batch_count + + def postprocess(self, p, processed, *args): + if not self.enable: + return + processed.all_seeds = [j for i in range(0, len(processed.all_seeds), processed.batch_size) for j in processed.all_seeds[i:i + processed.batch_size] * self.hr_batch_count] + processed.all_subseeds = [j for i in range(0, len(processed.all_subseeds), processed.batch_size) for j in processed.all_subseeds[i:i + processed.batch_size] * self.hr_batch_count] + + def init_hr_seeds(self, p): + if isinstance(self.hr_seed, str): + try: + self.hr_seed = int(self.hr_seed) + except Exception: + self.hr_seed = 0 + + if self.hr_seed == 0: + self.all_hr_seeds = p.all_seeds + else: + seed = int(random.randrange(4294967294)) if self.hr_seed == -1 else self.hr_seed + self.all_hr_seeds = [int(seed) + (x if self.hr_subseed_strength == 0 else 0) for x in range(len(p.all_seeds))] + + if isinstance(self.hr_subseed, str): + try: + self.hr_subseed = int(self.hr_subseed) + except Exception: + self.hr_subseed = 0 + + if self.hr_subseed == 0: + self.all_hr_subseeds = p.all_subseeds + else: + subseed = int(random.randrange(4294967294)) if self.hr_seed == -1 else self.hr_subseed + self.all_hr_subseeds = [int(subseed) + x for x in range(len(p.all_subseeds))] + + def sample_hijack(self, p, sample): + def wrapped_function(*args, **kwargs): + if not self.enable: + return sample(*args, **kwargs) + + self.force_write_hr_info_flag = False + result = sample(*args, **kwargs) + return result + + return wrapped_function + + def sample_hr_pass_hijack(self, p, sample_hr_pass): + def wrapped_function(*args, **kwargs): + if not self.enable: + return sample_hr_pass(*args, **kwargs) + + # save original shared.opts.save_images_before_highres_fix setting to be restored later + save_images_before_highres_fix = shared.opts.save_images_before_highres_fix + + original_resize_image = images.resize_image + + self.first_pass_seeds = p.seeds + self.first_pass_subseeds = p.subseeds + self.first_pass_subseed_strength = p.subseed_strength + self.first_pass_seed_resize_from_w = p.seed_resize_from_w + self.first_pass_seed_resize_from_h = p.seed_resize_from_h + + samples = processing.DecodedSamples() + try: + # hijack resize_image and init resize_image_cache + images.resize_image = self.resize_image_hijack(images.resize_image) + self.resize_image_cache = [] + + p.subseed_strength = self.hr_subseed_strength + p.seed_resize_from_w = self.hr_seed_resize_from_w + p.seed_resize_from_h = self.hr_seed_resize_from_h + + self.hr_seeds = [] + self.hr_subseeds = [] + hr_seeds_batch = self.all_hr_seeds[p.iteration * p.batch_size:(p.iteration + 1) * p.batch_size] + hr_subseeds_batch = self.all_hr_subseeds[p.iteration * p.batch_size:(p.iteration + 1) * p.batch_size] + + for index in range(self.hr_batch_count): + p.seeds = [seed + index for seed in hr_seeds_batch] if self.hr_subseed_strength == 0 else hr_seeds_batch + self.hr_seeds.extend(p.seeds) + p.subseeds = [subseed + index for subseed in hr_subseeds_batch] + self.hr_subseeds.extend(p.subseeds) + + result = sample_hr_pass(*args, **kwargs) + samples.extend(result) + + # disable saving images before highres fix for all but the first batch + shared.opts.save_images_before_highres_fix = False + + # check and restore hr_checkpoint incase model was switch by something like refine + if index < self.hr_batch_count - 1 and sd_models.model_data.sd_model.sd_model_checkpoint != (p.hr_checkpoint_info or sd_models.select_checkpoint()).filename: + sd_models.reload_model_weights(info=p.hr_checkpoint_info) + p.setup_conds() + + if self.patch_get_hr_prompt: + # fix for 1.9 + if callable(get_hr_prompt := p.extra_generation_params.get('Hires prompt')): + self.original_get_hr_prompt = get_hr_prompt + p.extra_generation_params['Hires prompt'] = self.get_hr_prompt_hijack(get_hr_prompt) + if callable(get_hr_negative_prompt := p.extra_generation_params.get('Hires negative prompt')): + self.original_get_hr_negative_prompt = get_hr_negative_prompt + p.extra_generation_params['Hires negative prompt'] = self.get_hr_prompt_hijack(get_hr_negative_prompt) + + finally: + p.seeds = self.first_pass_seeds + p.subseeds = self.first_pass_subseeds + p.subseed_strength = self.first_pass_subseed_strength + p.seed_resize_from_w = self.first_pass_seed_resize_from_w + p.seed_resize_from_h = self.first_pass_seed_resize_from_h + + # restore original shared.opts.save_images_before_highres_fix setting + shared.opts.save_images_before_highres_fix = save_images_before_highres_fix + + # restore original images.resize_image and clear resize_image_cache + images.resize_image = original_resize_image + self.resize_image_cache = None + + return samples + + return wrapped_function + + def resize_image_hijack(self, resize_image): + resize_image_signature = inspect.signature(resize_image) + + def wrapped_function(*args, **kwargs): + if not self.enable: + return resize_image(*args, **kwargs) + bind_args = resize_image_signature.bind(*args, **kwargs).arguments + im = bind_args.pop('im') + + bind_args_items = bind_args.items() + for cache_key, cache_im, cached_result in self.resize_image_cache: + if bind_args_items == cache_key and same_img_pil(im, cache_im): + return cached_result + + result = resize_image(*args, **kwargs) + self.resize_image_cache.append((bind_args_items, im, result)) + return result + return wrapped_function + + def get_hr_prompt_hijack(self, get_hr_prompt): + # fix for 1.9 + get_hr_prompt_signature = inspect.signature(get_hr_prompt) + + def wrapped_function(*args, **kwargs): + if not self.enable: + return get_hr_prompt(*args, **kwargs) + try: + bind_args = get_hr_prompt_signature.bind(*args, **kwargs).arguments + bind_args['index'] = bind_args['index'] // self.hr_batch_count + return get_hr_prompt(**bind_args) + except Exception: + return get_hr_prompt(*args, **kwargs) + + return wrapped_function diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_cfg_scale.py b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_cfg_scale.py new file mode 100644 index 0000000000000000000000000000000000000000..c7f4cbb9806d9ea2209ea2d916c3f909e9645d9c --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_cfg_scale.py @@ -0,0 +1,25 @@ +class HiresCFGScale: + def __init__(self, script): + self.script = script + self.apply_hr_cfg_scale = None + self.first_pass_cfg_scale = None + + def setup(self, p, *args): + p.hr_cfg_scale = args[0] + + def process_batch(self, p, *args, **kwargs): + # p.extra_generation_params + self.first_pass_cfg_scale = p.cfg_scale + self.apply_hr_cfg_scale = p.hr_cfg_scale != 0 and p.hr_cfg_scale != self.first_pass_cfg_scale + if self.apply_hr_cfg_scale: + p.extra_generation_params['Hires CFG scale'] = p.hr_cfg_scale + else: + p.extra_generation_params.pop('Hires CFG scale', None) + + def before_hr(self, p): + if self.apply_hr_cfg_scale: + p.cfg_scale = p.hr_cfg_scale + + def postprocess_batch(self, p): + if self.apply_hr_cfg_scale: + p.cfg_scale = self.first_pass_cfg_scale diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_output_dir.py b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_output_dir.py new file mode 100644 index 0000000000000000000000000000000000000000..d3c71dbb6204c4a3d9fe7c50e15cc766d22bf485 --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_output_dir.py @@ -0,0 +1,48 @@ +from modules import shared + + +class HiresOutputDir: + def __init__(self): + self.enable = None + self.original_outpath_samples = None + self.job_original_outpath_samples = None + + def setup(self, p, *args): + self.original_outpath_samples = None + self.job_original_outpath_samples = None + + self.enable = ( + p.enable_hr + and len(shared.opts.hires_fix_tweaks_outdir_hires_fix) > 0 + and not len(shared.opts.outdir_samples) > 0 + ) + if not self.enable: + return + + # save original outdir + self.original_outpath_samples = p.outpath_samples + + def before_process_batch(self, p, *args, **kwargs): + if not self.enable: + return + + if self.job_original_outpath_samples is not None: + # ensure job outdir for current batch + p.outpath_samples = self.job_original_outpath_samples + + def postprocess_batch(self, p, *args, **kwargs): + if not self.enable: + return + + # save job outdir for next batch + self.job_original_outpath_samples = p.outpath_samples + + # set outdir to hires_fix_tweaks_outdir_hires_fix + p.outpath_samples = shared.opts.hires_fix_tweaks_outdir_hires_fix + + def postprocess(self, p, processed, *args): + if not self.enable: + return + + # restore original outdir (don't think this is necessary but to be safe) + p.outpath_samples = self.original_outpath_samples diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_prompt_mode.py b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_prompt_mode.py new file mode 100644 index 0000000000000000000000000000000000000000..1bd6905253b5ac018e247d1d2a220f667983ba7d --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/hr_modules/hr_prompt_mode.py @@ -0,0 +1,297 @@ +from hires_fix_tweaks.utils import dumps_quote_swap_json, loads_quote_swap_json +from modules import extra_networks +from modules import shared +import gradio as gr +import re + + +def setup_regex(): + global marker_char, search_replace_instructions_pattern + if len(shared.opts.hires_fix_tweaks_marker_char) != 1 or re.match(r'[\s\w]', shared.opts.hires_fix_tweaks_marker_char): + shared.opts.hires_fix_tweaks_marker_char = '@' + invalid_marker_character_message = r'''ERROR: invalid marker character + marker character must be a single uncommon character + defaulting to "@"''' + print(invalid_marker_character_message) + gr.Warning(invalid_marker_character_message) + marker_char = shared.opts.hires_fix_tweaks_marker_char + marker_char_escape = re.escape(shared.opts.hires_fix_tweaks_marker_char) + # search for all line starts with "@marker@", @@ for escaped @ + search_replace_instructions_pattern = re.compile( + # r'^@((?:[^@]|@@)+)@' + f'^{marker_char_escape}((?:[^{marker_char_escape}]|{marker_char_escape * 2})+){marker_char_escape}', + flags=re.MULTILINE, + ) + + +def remove_extra_networks(prompt): + """return prompt with extra networks removed""" + return extra_networks.parse_prompts([prompt])[0][0] + + +def hires_prompt_mode_default(prompt, hr_prompt, remove_fp_extra_networks=False): + """ + if hr_prompt == '' and remove_fp_extra_networks: + remove extra networks from prompt and hr_prompt + else: + no change + """ + return prompt, hr_prompt or (remove_extra_networks(prompt) if remove_fp_extra_networks else prompt) + + +def hires_prompt_mode_append(prompt, hr_prompt, remove_fp_extra_networks=False): + if remove_fp_extra_networks or hr_prompt.strip(): + separator = shared.opts.hires_fix_tweaks_append_separator.format(newline='\n') + hr_prompt = f'{remove_extra_networks(prompt) if remove_fp_extra_networks else prompt}{separator}{hr_prompt}' + return prompt, hr_prompt + + +def hires_prompt_mode_prepend(prompt, hr_prompt, remove_fp_extra_networks=False): + if remove_fp_extra_networks or hr_prompt.strip(): + separator = shared.opts.hires_fix_tweaks_prepend_separator.format(newline='\n') + hr_prompt = f'{hr_prompt}{separator}{remove_extra_networks(prompt) if remove_fp_extra_networks else prompt}' + return prompt, hr_prompt + + +# search leading and trailing newlines +one_leading_and_trailing_newline_pattern = re.compile(r'^\r?\n?([\W\w]*)\r?\n?$') +search_replace_instructions_pattern: re.Pattern +marker_char: str + + +def hires_prompt_mode_search_replace(prompt, hr_prompt, remove_fp_extra_networks=False): + """ + parse hr_prompt as instructions for search and replace in prompt + + instructions syntax: @search@ replace + each pare starts with a search value which is denoted by a newline starting with "@key@" + anything after the search value is the replacement until the next search value + both search and replace values are can be multi-line + if search or replace value requires a literal "@" in the prompt, escape it with "@@" + + the instructions are parsed form hr_prompt then hr_prompt is replaced with the contents of prompt + then based on the instructions hr_prompt is modified + if "@search@" value is found in prompt, then it performs an "insert" + in hr_prompt search for "@search@" and replace with "replace" value + in prompt remove "@search@" + otherwise if performs a "replace" + in hr_prompt search for "search" (not "@search@") and replace with "replace" value + prompt is not modified + """ + # parse hr_prompt as instructions for search and replace + # even indexes are search value, odd indexes are replacing value + search_replace_instructions_list = search_replace_instructions_pattern.split(hr_prompt)[1:] + + hr_prompt = remove_extra_networks(prompt) if remove_fp_extra_networks else prompt + for i in range(0, len(search_replace_instructions_list), 2): + # restore escaped @ + key = search_replace_instructions_list[i].replace(marker_char * 2, marker_char) + insert_key = f'{marker_char}{key}{marker_char}' + + # restore escaped @ and removes 1 leading and trailing newline + replace = search_replace_instructions_list[i + 1].replace(marker_char * 2, marker_char) + replace = one_leading_and_trailing_newline_pattern.search(replace).group(1) + + if insert_key in prompt: + # insert mode: remove @key@ from prompt and replace @key@ in hr_prompt with replacement + prompt = prompt.replace(insert_key, '') + hr_prompt = hr_prompt.replace(insert_key, replace) + else: + # replace mode: replace insert_marker in hr_prompt with replacement + hr_prompt = hr_prompt.replace(key, replace) + + return prompt, hr_prompt + + +hires_prompt_mode_functions = { + 'Default': hires_prompt_mode_default, + 'Append': hires_prompt_mode_append, + 'Prepend': hires_prompt_mode_prepend, + 'Prompt S/R': hires_prompt_mode_search_replace, +} + + +def get_prompt(prompt_obj, index): + return prompt_obj[index] if isinstance(prompt_obj, list) else prompt_obj + + +class FakeP: + def __init__(self, prompt, negative_prompt, hr_prompt, hr_negative_prompt): + self.prompt = '' if prompt is None else prompt.strip() + self.negative_prompt = '' if negative_prompt is None else negative_prompt.strip() + self.hr_prompt = '' if hr_prompt is None else hr_prompt.strip() + self.hr_negative_prompt = '' if hr_negative_prompt is None else hr_negative_prompt.strip() + + def compare(self, other, p, np): + positive = p and all(getattr(self, attr).strip() == getattr(other, attr).strip() for attr in ['prompt', 'hr_prompt']) + negative = np and all(getattr(self, attr).strip() == getattr(other, attr).strip() for attr in ['negative_prompt', 'hr_negative_prompt']) + return positive, negative + + +def get_mode_info(mode, hr_prompt, negative, remove_fp_extra_networks=False): + """ + { + 'h': [mode, hr_prompt, remove_fp_extra_networks], + 'n': [mode, hr_prompt], + 'a': append_separator, + 'p': prepend_separator, + 'c': marker_char, + } + """ + if shared.opts.hires_fix_tweaks_save_template: + prompt_mode = [mode, hr_prompt.strip()] + if remove_fp_extra_networks: + prompt_mode.append(remove_fp_extra_networks) + info_obj = {'n' if negative else 'h': prompt_mode} + if mode == 'Append' and shared.opts.hires_fix_tweaks_append_separator != shared.opts.get_default('hires_fix_tweaks_append_separator'): + info_obj['a'] = shared.opts.hires_fix_tweaks_append_separator + elif mode == 'Prepend' and shared.opts.hires_fix_tweaks_prepend_separator != shared.opts.get_default('hires_fix_tweaks_prepend_separator'): + info_obj['p'] = shared.opts.hires_fix_tweaks_prepend_separator + elif mode == 'Prompt S/R' and shared.opts.hires_fix_tweaks_marker_char != shared.opts.get_default('hires_fix_tweaks_marker_char'): + info_obj['c'] = shared.opts.hires_fix_tweaks_marker_char + return info_obj + + +def merge_mode_info(info_obj_1, info_obj_2): + if info_obj_1 and info_obj_2: + match (isinstance(info_obj_1, dict), isinstance(info_obj_2, dict)): + case True, True: + info_obj_1.update(info_obj_2) + return info_obj_1 + case False, False: + assert len(info_obj_1) == len(info_obj_2) + info_obj = [] + for o1, o2 in zip(info_obj_1, info_obj_2): + o1.update(o2) + info_obj.append(o1) + return info_obj + case False, True: + [o.update(info_obj_2) for o in info_obj_1] + return info_obj_1 + case True, False: + [o.update(info_obj_2) for o in info_obj_2] + return info_obj_2 + elif info_obj_1: + return info_obj_1 + elif info_obj_2: + return info_obj_2 + + +def parse_mode_info(mode_info): + info_obj = loads_quote_swap_json(mode_info) + h = info_obj.get('h', [None, None, None]) + mode_p, hr_prompt, remove_fp_extra_networks = h if len(h) == 3 else (h + [False]) + mode_np, hr_np_prompt = info_obj.get('n', [None, None]) + app_sep = info_obj.get('a', shared.opts.get_default('hires_fix_tweaks_append_separator')) + pre_sep = info_obj.get('p', shared.opts.get_default('hires_fix_tweaks_prepend_separator')) + marker = info_obj.get('c', shared.opts.get_default('hires_fix_tweaks_marker_char')) + return mode_p, hr_prompt, mode_np, hr_np_prompt, app_sep, pre_sep, marker, remove_fp_extra_networks + + +def parse_and_apply_mode_info(mode_info, params): + mode_p, hr_prompt, mode_np, hr_np_prompt, app_sep, pre_sep, marker, remove_fp_extra_networks = parse_mode_info(mode_info) + + if 'HR Append' not in params: + params['HR append'] = app_sep + shared.opts.set('hires_fix_tweaks_append_separator', app_sep) + + if 'HR Prepend' not in params: + params['HR prepend'] = pre_sep + shared.opts.set('hires_fix_tweaks_prepend_separator', pre_sep) + + if 'HR marker' not in params: + params['HR marker'] = marker + shared.opts.set('hires_fix_tweaks_marker_char', marker) + + return mode_p, hr_prompt, mode_np, hr_np_prompt, remove_fp_extra_networks + + +def process_prompt_mode(hires_prompt_mode, p, negative=False, remove_fp_extra_networks=False): + info_obj = None + if remove_fp_extra_networks or hires_prompt_mode != 'Default': + p_prompt, p_hr_prompt = (p.negative_prompt, p.hr_negative_prompt) if negative else (p.prompt, p.hr_prompt) + + hires_prompt_mode_function = hires_prompt_mode_functions.get(hires_prompt_mode, hires_prompt_mode_default) + + if any(isinstance(var, list) for var in [p_prompt, p_hr_prompt]): + prompt_list, hr_prompt_list, info_obj = [], [], [] + for i in range(len(p_prompt if isinstance(p_prompt, list) else p_hr_prompt)): + prompt, hr_prompt = hires_prompt_mode_function(get_prompt(p_prompt, i), get_prompt(p_hr_prompt, i), remove_fp_extra_networks) + prompt_list.append(prompt) + hr_prompt_list.append(hr_prompt) + info_obj.append(get_mode_info(hires_prompt_mode, get_prompt(p_hr_prompt, i), negative, remove_fp_extra_networks)) + + if negative: + p.negative_prompt, p.hr_negative_prompt = prompt_list, hr_prompt_list + else: + p.prompt, p.hr_prompt = prompt_list, hr_prompt_list + + else: + info_obj = get_mode_info(hires_prompt_mode, p_hr_prompt, negative, remove_fp_extra_networks) + if negative: + p.negative_prompt, p.hr_negative_prompt = hires_prompt_mode_function(p_prompt, p_hr_prompt, remove_fp_extra_networks) + else: + p.prompt, p.hr_prompt = hires_prompt_mode_function(p_prompt, p_hr_prompt, remove_fp_extra_networks) + + return info_obj + + +def apply_override(p): + for key in ['hires_fix_tweaks_append_separator', 'hires_fix_tweaks_prepend_separator', 'hires_fix_tweaks_marker_char']: + if key in p.override_settings: + shared.opts.set(key, p.override_settings.pop(key)) + + +def setup(p, *args): + remove_fp_extra_networks, hires_prompt_mode, hires_negative_prompt_mode = args[1:4] + with RestoreSettings(): + apply_override(p) + info_obj_p = process_prompt_mode(hires_prompt_mode, p, remove_fp_extra_networks=remove_fp_extra_networks) + info_obj_np = process_prompt_mode(hires_negative_prompt_mode, p, negative=True) + if info_obj := merge_mode_info(info_obj_p, info_obj_np): + p.extra_generation_params['Hires prompt mode'] = dumps_quote_swap_json(info_obj) + + +class RestoreSettings: + def __enter__(self): + keys = ['hires_fix_tweaks_append_separator', 'hires_fix_tweaks_prepend_separator', 'hires_fix_tweaks_marker_char'] + self.settings = {key: getattr(shared.opts, key) for key in keys} + + def __exit__(self, exc_type, exc_val, exc_tb): + for key, value in self.settings.items(): + shared.opts.set(key, value) + + +def parse_infotext(infotext, params): + use_p, use_n = False, False + try: + if shared.opts.hires_fix_tweaks_restore_template: + with RestoreSettings(): + if 'Hires prompt mode' in params: + mode_p, hr_prompt, mode_np, hr_np_prompt, remove_fp_extra_networks = parse_and_apply_mode_info(params['Hires prompt mode'], params) + + if mode_p or mode_np: + p_info = FakeP(params['Prompt'], params['Negative prompt'], params['Hires prompt'], params['Hires negative prompt']) + p = FakeP(params['Prompt'], params['Negative prompt'], hr_prompt, hr_np_prompt) + if mode_p: + process_prompt_mode(mode_p, p, remove_fp_extra_networks=remove_fp_extra_networks) + if mode_np: + process_prompt_mode(mode_np, p, negative=True) + use_p, use_n = p.compare(p_info, mode_p, mode_np) + if use_p: + params['Hires prompt mode'] = mode_p + params['Hires prompt'] = hr_prompt + params['Remove FP Networks'] = remove_fp_extra_networks + if use_n: + params['Hires negative prompt mode'] = mode_np + params['Hires negative prompt'] = hr_np_prompt + + except Exception as e: + print(e) + pass + + if not use_p: + params['Hires prompt mode'] = 'Default' + params['Remove FP Networks'] = False + if not use_n: + params['Hires negative prompt mode'] = 'Default' diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/settings.py b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..aec2502367c36ad6cbb8056bc8095c5ca8ea3054 --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/settings.py @@ -0,0 +1,94 @@ +from hires_fix_tweaks.hr_modules import hr_prompt_mode +from modules import shared +import gradio as gr + + +shared.options_templates.update( + shared.options_section( + ('hires_fix_tweaks', 'Hires. fix tweaks'), + { + 'hires_fix_tweaks_save_template': + shared.OptionInfo( + True, + 'Save the hires prompt mode template to infotext', + ) + .needs_reload_ui(), + 'hires_fix_tweaks_restore_template': + shared.OptionInfo( + True, + 'Restore hires prompt mode template from infotext', + ) + .needs_reload_ui(), + 'hires_fix_tweaks_append_separator': + shared.OptionInfo( + '{newline}', + 'Append mode insert separator', + infotext='HR append', + ) + .info('default: "{newline}"'), + 'hires_fix_tweaks_prepend_separator': + shared.OptionInfo( + '{newline}', + 'Prepend mode insert separator', + infotext='HR prepend', + ) + .info('default: "{newline}"'), + 'hires_fix_tweaks_show_hr_cfg': + shared.OptionInfo( + True, + 'Show hires CFG Scale slider', + ) + .needs_reload_ui(), + 'hires_fix_tweaks_show_hr_batch_seed': + shared.OptionInfo( + True, + 'Show hires batch count and seed', + ) + .needs_reload_ui(), + 'hires_fix_tweaks_show_hr_remove_fp_extra_networks': + shared.OptionInfo( + True, + 'Show "Remove First Pass Extra Networks" checkbox', + ) + .needs_reload_ui(), + 'hires_fix_tweaks_show_hr_prompt_mode': + shared.OptionInfo( + True, + 'Show hires prompt mode', + ) + .info('only usable if "Hires fix: show hires prompt and negative prompt" is also enabled') + .needs_reload_ui(), + 'hires_fix_tweaks_marker_char': + shared.OptionInfo( + '@', + 'Hires fix search/replace syntax ' + 'marker character', + onchange=hr_prompt_mode.setup_regex, + infotext='HR marker', + ) + .info('default: "@", can be changed other characters if the default is causing issues, must be a single uncommon character'), + 'hires_fix_tweaks_hires_prompt_mode_ui_type': + shared.OptionInfo( + 'Radio', 'Hires Prompt mode menu style', gr.Radio, + {'choices': ['Radio', 'Dropdown']}, + ) + .needs_reload_ui(), + } + ) +) + +shared.options_templates.update( + shared.options_section( + ('saving-paths', "Paths for saving"), + { + "hires_fix_tweaks_outdir_hires_fix": + shared.OptionInfo( + '', 'Output directory for hires. fix images', + component_args=shared.hide_dirs + ) + .info('leave blank to use same directory as txt2img, and will be ignored if "Output directory for images" is not empty. (hires-fix-tweaks extension)'), + } + ) +) + +hr_prompt_mode.setup_regex() diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/ui.py b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/ui.py new file mode 100644 index 0000000000000000000000000000000000000000..f3f2f6675d9cdec6c8753e05607c4fc55e9974a9 --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/ui.py @@ -0,0 +1,184 @@ +from hires_fix_tweaks.hr_modules import hr_prompt_mode +from hires_fix_tweaks.hr_modules import hr_batch_seed +from modules import generation_parameters_copypaste # noqa: generation_parameters_copypaste is the ailes to infotext_utils +from modules import shared, ui_components, ui +from contextlib import nullcontext +import gradio as gr +import json + +try: + from modules.ui_components import InputAccordion +except ImportError: + InputAccordion = None + + +def connect_reuse_seed(seed, reuse_seed: gr.Button, generation_info: gr.Textbox, is_subseed): + def copy_seed(gen_info_string: str, index): + infotext_skip_pasting = shared.opts.infotext_skip_pasting + try: + gen_info = json.loads(gen_info_string) + infotext = gen_info['infotexts'][index] + shared.opts.infotext_skip_pasting = [] + gen_parameters = generation_parameters_copypaste.parse_generation_parameters(infotext) + hr_batch_seed.parse_infotext(None, gen_parameters) + res = int(gen_parameters['Hires seed']['Subseed' if is_subseed else 'Seed']) + except Exception: + res = 0 + finally: + shared.opts.infotext_skip_pasting = infotext_skip_pasting + return [res, gr.update()] + reuse_seed.click(fn=copy_seed, _js="(x, y) => [x, selected_gallery_index()]", show_progress=False, inputs=[generation_info, seed], outputs=[seed, seed]) + + +class UI: + def __init__(self, script): + self.script = script + self.script.on_after_component_elem_id.append(('txt2img_hires_fix_row2', self.create_ui_batch_cfg)) + self.script.on_after_component_elem_id.append(('txt2img_hires_fix_row4', self.create_ui_hr_prompt_mode)) + # ui create status + self.create_ui_cfg_done = None + self.create_ui_hr_prompt_mode_done = None + self.create_hr_seed_ui_done = None + + # gradio elements + # hr cfg scale + self.hr_cfg_e = None + + # hr prompt mode + self.hr_prompt_mode_e = None + self.hr_negative_prompt_mode_e = None + self.remove_fp_extra_networks_e = None + + # hr batch and seed + self.hr_batch_count_e = None + self.enable_hr_seed_e = None + self.hr_seed_e = None + self.hr_seed_checkbox_e = None + self.hr_subseed_e = None + self.hr_subseed_strength_e = None + self.hr_seed_resize_from_h_e = None + self.hr_seed_resize_from_w_e = None + + def ui_args(self): + return [ + # hr cfg scale + self.hr_cfg_e, + + # hr prompt mode + self.remove_fp_extra_networks_e, + self.hr_prompt_mode_e, + self.hr_negative_prompt_mode_e, + + # hr batch and seed + self.hr_batch_count_e, + self.enable_hr_seed_e, + self.hr_seed_e, + self.hr_seed_checkbox_e, + self.hr_subseed_e, + self.hr_subseed_strength_e, + self.hr_seed_resize_from_w_e, + self.hr_seed_resize_from_h_e, + ] + + def fallback_create_ui(self): + global InputAccordion + if None in [self.create_ui_cfg_done, self.create_hr_seed_ui_done]: + # pre 1.7.0 compatibility + InputAccordion = None + with gr.Accordion(label=self.script.title(), open=False): + self.create_ui_batch_cfg() + self.create_ui_hr_prompt_mode() + + def create_ui_hr_prompt_mode(self, *args, **kwargs): + if self.create_ui_hr_prompt_mode_done: + return + gr_ui_element = getattr(gr, shared.opts.hires_fix_tweaks_hires_prompt_mode_ui_type, gr.Radio) + with gr.Row() if (shared.opts.hires_fix_tweaks_show_hr_prompt_mode or shared.opts.hires_fix_tweaks_show_hr_remove_fp_extra_networks) else nullcontext(): + self.remove_fp_extra_networks_e = gr.Checkbox(label='Remove First Pass Extra Networks', value=False, elem_id=self.script.elem_id('remove_fp_extra_networks'), elem_classes=['hr-tweaks-center-checkbox'], tooltip='Remove extra networks from first-pass prompt before constructing hires-prompt', visible=shared.opts.hires_fix_tweaks_show_hr_remove_fp_extra_networks) + self.hr_prompt_mode_e = gr_ui_element(choices=list(hr_prompt_mode.hires_prompt_mode_functions), label='Hires prompt mode', value='Default', elem_id=self.script.elem_id('hr_prompt_extend_mode'), elem_classes=['hr-prompt-extend-mode'] if shared.opts.hires_fix_tweaks_show_hr_remove_fp_extra_networks else [], visible=shared.opts.hires_fix_tweaks_show_hr_prompt_mode) + self.hr_negative_prompt_mode_e = gr_ui_element(choices=list(hr_prompt_mode.hires_prompt_mode_functions), label='Hires negative prompt mode', value='Default', elem_id=self.script.elem_id('hr_negative_prompt_extend_mode'), elem_classes=['hr-prompt-extend-mode'] if shared.opts.hires_fix_tweaks_show_hr_remove_fp_extra_networks else [], visible=shared.opts.hires_fix_tweaks_show_hr_prompt_mode) + self.script.infotext_fields.extend([ + (self.remove_fp_extra_networks_e, 'Remove FP Networks'), + (self.hr_prompt_mode_e, 'Hires prompt mode'), + (self.hr_negative_prompt_mode_e, 'Hires negative prompt mode'), + ]) + if shared.opts.hires_fix_tweaks_show_hr_prompt_mode and not shared.opts.hires_fix_show_prompts: + with gr.Row(): + gr.Markdown('''`Hires prompt mode` is only usable if `Settings` > `UI alternatives` > `Hires fix: show hires prompt and negative prompt` is enabled +if you do not need this feature you can disable it in `Settings` > `Hires. fix tweaks` > `Show hires Hires prompt mode`''') + self.create_hr_seed_ui() + self.create_ui_hr_prompt_mode_done = True + + def create_ui_batch_cfg(self, *args, **kwargs): + if self.create_ui_cfg_done: + return + with gr.Row(elem_id=self.script.elem_id("batch_cfg_row")) if shared.opts.hires_fix_tweaks_show_hr_cfg or shared.opts.hires_fix_tweaks_show_hr_batch_seed else nullcontext(): + with gr.Column(scale=8) if shared.opts.hires_fix_tweaks_show_hr_cfg else nullcontext(): + self.hr_cfg_e = gr.Slider(value=0, minimum=0, maximum=30.0, step=0.5, label='Hires CFG Scale', elem_id=self.script.elem_id('hr_cfg_scale'), tooltip='0: same as first pass', visible=shared.opts.hires_fix_tweaks_show_hr_cfg) + with gr.Column(min_width=200) if shared.opts.hires_fix_tweaks_show_hr_batch_seed else nullcontext(): + self.hr_batch_count_e = gr.Slider(label='Hires batch count', value=1, minimum=1, maximum=16, step=1, elem_id=self.script.elem_id('batch_count'), visible=shared.opts.hires_fix_tweaks_show_hr_batch_seed) + self.script.infotext_fields.append((self.hr_cfg_e, lambda d: d.get('Hires CFG scale', 0))) + self.create_ui_cfg_done = True + + def create_hr_seed_ui(self, *args, **kwargs): + if self.create_hr_seed_ui_done: + return + with ( + ui_components.InputAccordion(False, label="Hires Seed", elem_id=self.script.elem_id('custom_seed'), visible=shared.opts.hires_fix_tweaks_show_hr_batch_seed) if InputAccordion + else gr.Accordion('Hires Seed', open=False, elem_id=self.script.elem_id('custom_seed')) + as self.enable_hr_seed_e + ): + with gr.Row(elem_id=self.script.elem_id("seed_row")): + if not InputAccordion: + # pre 1.7.0 compatibility + self.enable_hr_seed_e = gr.Checkbox(label='Enable', elem_id=self.script.elem_id("enable_hr_seed_subseed_show"), value=False) + # the elem_id suffix is used _subseed_show to apply the [id$=_subseed_show] css rule + if shared.cmd_opts.use_textbox_seed: + self.hr_seed_e = gr.Textbox(label='Hires Seed', value='0', elem_id=self.script.elem_id("seed")) + else: + self.hr_seed_e = gr.Number(label='Hires Seed', value=0, elem_id=self.script.elem_id("seed"), precision=0) + + same_seed = ui_components.ToolButton('🟰', elem_id=self.script.elem_id("same_seed"), tooltip="Set seed to 0, use same seed as the first pass") + random_seed = ui_components.ToolButton(ui.random_symbol, elem_id=self.script.elem_id("random_seed"), tooltip="Set seed to -1, which will cause a new random number to be used every time") + reuse_seed = ui_components.ToolButton(ui.reuse_symbol, elem_id=self.script.elem_id("reuse_seed"), tooltip="Reuse seed from last generation, mostly useful if it was randomized") + + self.hr_seed_checkbox_e = gr.Checkbox(label='Extra', elem_id=self.script.elem_id("subseed_show"), value=False) + + with gr.Group(visible=False, elem_id=self.script.elem_id("seed_extras")) as seed_extras: + with gr.Row(elem_id=self.script.elem_id("subseed_row")): + if shared.cmd_opts.use_textbox_seed: + self.hr_subseed_e = gr.Textbox(label='Hires variation seed', value='0', elem_id=self.script.elem_id("subseed")) + else: + self.hr_subseed_e = gr.Number(label='Hires variation seed', value=0, elem_id=self.script.elem_id("subseed"), precision=0) + same_seed_subseed = ui_components.ToolButton('🟰', elem_id=self.script.elem_id("same_seed_subseed"), tooltip="Set seed to 0, use same seed as the first pass") + random_subseed = ui_components.ToolButton(ui.random_symbol, elem_id=self.script.elem_id("random_subseed"), tooltip="Set seed to -1, which will cause a new random number to be used every time") + reuse_subseed = ui_components.ToolButton(ui.reuse_symbol, elem_id=self.script.elem_id("reuse_subseed"), tooltip="Reuse seed from last generation, mostly useful if it was randomized") + self.hr_subseed_strength_e = gr.Slider(label='Hires variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=self.script.elem_id("hr_subseed_strength")) + + with gr.Row(elem_id=self.script.elem_id("seed_resize_from_row")): + self.hr_seed_resize_from_w_e = gr.Slider(minimum=0, maximum=2048, step=8, label="Hires resize seed from width", value=0, elem_id=self.script.elem_id("seed_resize_from_w")) + self.hr_seed_resize_from_h_e = gr.Slider(minimum=0, maximum=2048, step=8, label="Hires resize seed from height", value=0, elem_id=self.script.elem_id("seed_resize_from_h")) + + same_seed.click(fn=None, _js="function(){setInputToValueHrTweaks('" + self.script.elem_id("seed") + "', '0')}", show_progress=False) + same_seed_subseed.click(fn=None, _js="function(){setInputToValueHrTweaks('" + self.script.elem_id("subseed") + "', '0')}", show_progress=False) + random_seed.click(fn=None, _js="function(){setRandomSeed('" + self.script.elem_id("seed") + "')}", show_progress=False) + random_subseed.click(fn=None, _js="function(){setRandomSeed('" + self.script.elem_id("subseed") + "')}", show_progress=False) + + self.hr_seed_checkbox_e.change(lambda x: gr.update(visible=x), show_progress=False, inputs=[self.hr_seed_checkbox_e], outputs=[seed_extras]) + + self.script.infotext_fields.extend( + [ + (self.enable_hr_seed_e, lambda d: 'Hires seed' in d), + (self.hr_seed_e, lambda d: d.get('Hires seed', {}).get('Seed', 0)), + (self.hr_seed_checkbox_e, lambda d: any(map(d.get('Hires seed', {}).__contains__, ['Strength', 'Resize']))), + (self.hr_subseed_e, lambda d: d.get('Hires seed', {}).get('Subseed', 0)), + (self.hr_subseed_strength_e, lambda d: d.get('Hires seed', {}).get('Strength', 0)), + (self.hr_seed_resize_from_w_e, lambda d: d.get('Hires seed', {}).get('Resize', [0, None])[0]), + (self.hr_seed_resize_from_h_e, lambda d: d.get('Hires seed', {}).get('Resize', [None, 0])[1]), + ] + ) + + self.script.on_after_component(lambda x: connect_reuse_seed(self.hr_seed_e, reuse_seed, x.component, False), elem_id=f'generation_info_{self.script.tabname}') + self.script.on_after_component(lambda x: connect_reuse_seed(self.hr_subseed_e, reuse_subseed, x.component, True), elem_id=f'generation_info_{self.script.tabname}') + + self.create_hr_seed_ui_done = True diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/utils.py b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..c764ac3d17470cada52addf9e31b832044338ec6 --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/utils.py @@ -0,0 +1,12 @@ +import json + +quote_swap = str.maketrans('\'"', '"\'') + + +def dumps_quote_swap_json(input_object): + return json.dumps(input_object).translate(quote_swap) + + +def loads_quote_swap_json(input_string): + return json.loads(input_string.translate(quote_swap)) + diff --git a/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/xyz.py b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/xyz.py new file mode 100644 index 0000000000000000000000000000000000000000..c93bd79dadadbadf163b2651e075bb4d32307a07 --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/hires_fix_tweaks/xyz.py @@ -0,0 +1,15 @@ +from modules import scripts + + +def xyz_grid_axis(): + for data in scripts.scripts_data: + if data.script_class.__module__ in ('xyz_grid.py', 'scripts.xyz_grid') and hasattr(data, 'module'): + xyz_grid = data.module + xyz_grid.axis_options.extend( + [ + xyz_grid.AxisOptionTxt2Img('[Hires fix tweaks] Hires CFG Scale', float, xyz_grid.apply_field('hr_cfg_scale')), + ] + ) + break + else: + raise ImportError("[Hires fix tweaks] Can't find xyz_grid: Hires CFG Scale won't work in XYZ Grid") diff --git a/extensions/sd-webui-hires-fix-tweaks/javascript/scripts.js b/extensions/sd-webui-hires-fix-tweaks/javascript/scripts.js new file mode 100644 index 0000000000000000000000000000000000000000..dfdf867e70497a82b9a04cbd4ff1094f60642f1b --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/javascript/scripts.js @@ -0,0 +1,5 @@ +function setInputToValueHrTweaks(elem_id, value) { + const input = gradioApp().querySelector("#" + elem_id + " input"); + input.value = value; + updateInput(input); +} \ No newline at end of file diff --git a/extensions/sd-webui-hires-fix-tweaks/screenshot.png b/extensions/sd-webui-hires-fix-tweaks/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..80c7c82645d51e34d87a4b44bcbb318619ca73f2 Binary files /dev/null and b/extensions/sd-webui-hires-fix-tweaks/screenshot.png differ diff --git a/extensions/sd-webui-hires-fix-tweaks/scripts/__pycache__/sd-webui-hires-fix-tweaks.cpython-310.pyc b/extensions/sd-webui-hires-fix-tweaks/scripts/__pycache__/sd-webui-hires-fix-tweaks.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..341ca7c50aef58e494e0e0153e26f691ea4419eb Binary files /dev/null and b/extensions/sd-webui-hires-fix-tweaks/scripts/__pycache__/sd-webui-hires-fix-tweaks.cpython-310.pyc differ diff --git a/extensions/sd-webui-hires-fix-tweaks/scripts/sd-webui-hires-fix-tweaks.py b/extensions/sd-webui-hires-fix-tweaks/scripts/sd-webui-hires-fix-tweaks.py new file mode 100644 index 0000000000000000000000000000000000000000..3abf4dfff44e85aa826ab8a8c2738f9003959c54 --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/scripts/sd-webui-hires-fix-tweaks.py @@ -0,0 +1,62 @@ +from hires_fix_tweaks.hr_modules import hr_cfg_scale, hr_prompt_mode, hr_batch_seed, hr_output_dir +from hires_fix_tweaks import ui, xyz, settings # noqa: F401 +from modules import scripts, script_callbacks + + +class HiresFixTweaks(scripts.Script): + def __init__(self): + super().__init__() + self.infotext_fields = [] + self.on_after_component_elem_id = [] + self.ui_class = ui.UI(self) + self.hr_output_dir = hr_output_dir.HiresOutputDir() + self.hires_cfg_scale = hr_cfg_scale.HiresCFGScale(self) + self.hires_batch_seed = hr_batch_seed.HiresBatchSeed(self) + + def title(self): + return 'Hires. fix tweaks' + + def show(self, is_img2img): + if not is_img2img: + return scripts.AlwaysVisible + + def ui(self, is_img2img): + self.ui_class.fallback_create_ui() + return self.ui_class.ui_args() + + def setup(self, p, *args): + self.hr_output_dir.setup(p, *args) + hr_prompt_mode.setup(p, *args) + self.hires_cfg_scale.setup(p, *args) + self.hires_batch_seed.setup(p, *args) + + def process(self, p, *args): + self.hires_batch_seed.process(p, *args) + + def before_process_batch(self, p, *args, **kwargs): + self.hr_output_dir.before_process_batch(p, *args, **kwargs) + self.hires_batch_seed.before_process_batch(p, *args, **kwargs) + + def process_batch(self, p, *args, **kwargs): + self.hires_cfg_scale.process_batch(p, *args, **kwargs) + self.hires_batch_seed.process_batch(p, *args, **kwargs) + + def before_hr(self, p, *args): + self.hires_cfg_scale.before_hr(p) + + def postprocess_batch(self, p, *args, **kwargs): + self.hr_output_dir.postprocess_batch(p, *args, **kwargs) + self.hires_cfg_scale.postprocess_batch(p) + + def postprocess_batch_list(self, p, pp, *args, **kwargs): + self.hires_batch_seed.postprocess_batch_list(p, pp, *args, **kwargs) + + def postprocess(self, p, processed, *args): + self.hr_output_dir.postprocess(p, processed, *args) + self.hires_batch_seed.postprocess(p, processed, *args) + + +hr_batch_seed.hijack_create_infotext(HiresFixTweaks) +script_callbacks.on_infotext_pasted(hr_batch_seed.parse_infotext) +script_callbacks.on_infotext_pasted(hr_prompt_mode.parse_infotext) +script_callbacks.on_before_ui(xyz.xyz_grid_axis) diff --git a/extensions/sd-webui-hires-fix-tweaks/style.css b/extensions/sd-webui-hires-fix-tweaks/style.css new file mode 100644 index 0000000000000000000000000000000000000000..45f0bbdc2f6d7fb1ea1d6471f618c2a8ee2d7fd4 --- /dev/null +++ b/extensions/sd-webui-hires-fix-tweaks/style.css @@ -0,0 +1,15 @@ +.hr-tweaks-center-checkbox { + margin: 0.25em 0 0 0 !important; + min-width: auto !important; + flex-grow: 0 !important; +} + +.hr-tweaks-center-checkbox label{ + font-size: 12px !important; + line-height: 1 !important; + text-align: center; +} + +.hr-prompt-extend-mode div label { + padding: 0.1em 0.3em !important; +} diff --git a/extensions/sd-webui-image-comparison/LICENSE b/extensions/sd-webui-image-comparison/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..91b74bef797843ad1245014dc2865ff6cd4ac688 --- /dev/null +++ b/extensions/sd-webui-image-comparison/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Haoming + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/sd-webui-image-comparison/README.md b/extensions/sd-webui-image-comparison/README.md new file mode 100644 index 0000000000000000000000000000000000000000..52e2d99992d76ebf3451129f58c40d5529b4e73a --- /dev/null +++ b/extensions/sd-webui-image-comparison/README.md @@ -0,0 +1,28 @@ +# SD Webui Image Comparison +This is an Extension for the [Automatic1111 Webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui), which adds a new tab for image comparison. + +

+
+Draggable Image Slider +

+ +Wanna check how your **img2img** went? Simply compare the results in the brand new **Comparison** tab! + +Ways to Load the Images + +1. Upload the input and output images manually +2. Click a button in the `Comparison` tab to load the images from `img2img` / `Extras` tab +3. Click a button in the `img2img` / `Extras` tab to send the images to `Comparison` tab + - Enable this in the `User interface` section of **Settings** + +Ways to Compare the Details + +- Press `+` to zoom in; press `-` to zoom out +- Use `arrow keys` to move around +- Press `0` to reset the scale and position + +Also comes with an **Infotext** panel below to quickly compare the generation parameters. +*(Only for manually uploaded images)* +

+
+

diff --git a/extensions/sd-webui-image-comparison/info.jpg b/extensions/sd-webui-image-comparison/info.jpg new file mode 100644 index 0000000000000000000000000000000000000000..15bbcfe9cf362918c382e02b029898a604be370c Binary files /dev/null and b/extensions/sd-webui-image-comparison/info.jpg differ diff --git a/extensions/sd-webui-image-comparison/javascript/comp_loader.js b/extensions/sd-webui-image-comparison/javascript/comp_loader.js new file mode 100644 index 0000000000000000000000000000000000000000..b84adb8cdce1f9de89a202ae924b749304dc20c8 --- /dev/null +++ b/extensions/sd-webui-image-comparison/javascript/comp_loader.js @@ -0,0 +1,42 @@ +class ImgCompLoader { + + static swapImage() { + let temp = ImageComparator.img_A.src; + ImageComparator.img_A.src = ImageComparator.img_B.src; + ImageComparator.img_B.src = temp; + } + + static loadImage(tab) { + var source_a = null; + var source_b = null; + + switch (tab) { + default: + alert('WTF?'); + break; + case 'i2i': + source_a = gradioApp().getElementById('img2img_image').querySelector('img'); + source_b = gradioApp().getElementById('img2img_gallery').querySelector('img'); + break; + case 'inpaint': + source_a = gradioApp().getElementById('img2img_inpaint_tab').querySelector('img'); + source_b = gradioApp().getElementById('img2img_gallery').querySelector('img'); + break; + case 'extras': + source_a = gradioApp().getElementById('extras_image').querySelector('img'); + source_b = gradioApp().getElementById('extras_gallery').querySelector('img'); + break; + case 'upload': + source_a = gradioApp().getElementById('img_comp_input_A').querySelector('img'); + source_b = gradioApp().getElementById('img_comp_input_B').querySelector('img'); + break; + } + + if (source_a == null || source_b == null) + return; + + ImageComparator.img_A.src = source_a.src; + ImageComparator.img_B.src = source_b.src; + ImageComparator.reset(); + } +} diff --git a/extensions/sd-webui-image-comparison/javascript/img_comp.js b/extensions/sd-webui-image-comparison/javascript/img_comp.js new file mode 100644 index 0000000000000000000000000000000000000000..912796510ce0811ebd90f55473bd5993b3762b0d --- /dev/null +++ b/extensions/sd-webui-image-comparison/javascript/img_comp.js @@ -0,0 +1,313 @@ +class ImageComparator { + + static IMG_COMP_WIDTH; + static img_A; + static img_B; + static alpha_slider; + static bar; + static direction_checkbox; + static cached_image = undefined; + + static translateX = 0.0; + static translateY = 0.0; + static scale = 1.0; + + static isHorizontal() { + return this.direction_checkbox.checked; + } + + static switch_to_comparison() { + const tabs = gradioApp().querySelector('#tabs').querySelector('.tab-nav').querySelectorAll('button'); + for (let i = 0; i < tabs.length; i++) { + if (tabs[i].textContent.trim() === "Comparison") { + tabs[i].click(); + break; + } + } + } + + static reset() { + if (this.isHorizontal()) { + this.img_B.parentNode.style.left = `calc(50% + ${this.IMG_COMP_WIDTH / 2}px)`; + this.img_B.style.left = `${this.IMG_COMP_WIDTH}px`; + this.bar.style.left = `calc(50% + ${this.IMG_COMP_WIDTH / 2}px)`; + + this.img_B.parentNode.style.top = '0px'; + this.img_B.style.top = '0px'; + this.bar.style.top = '0px'; + + this.bar.style.height = `${this.IMG_COMP_WIDTH}px`; + this.bar.style.width = '2px'; + + this.img_A.classList.add('hor'); + this.img_A.classList.remove('ver'); + } else { + this.img_B.parentNode.style.left = `calc(50% - ${this.IMG_COMP_WIDTH / 2}px)`; + this.img_B.style.left = '0px'; + this.bar.style.left = `calc(50% - ${this.IMG_COMP_WIDTH / 2}px)`; + + this.img_B.parentNode.style.top = `calc(50% + ${this.IMG_COMP_WIDTH / 2}px)`; + this.img_B.style.top = `${this.IMG_COMP_WIDTH}px`; + this.bar.style.top = `${this.IMG_COMP_WIDTH}px`; + + this.bar.style.width = `${this.IMG_COMP_WIDTH}px`; + this.bar.style.height = '2px'; + + this.img_A.classList.remove('hor'); + this.img_A.classList.add('ver'); + } + + this.img_B.style.opacity = 1.0; + this.alpha_slider.querySelector('input').value = 1.0; + updateInput(this.alpha_slider.querySelector('input')); + } + + static addButtons() { + // 0: Off ; 1: Text ; 2: Icon + const config = gradioApp().getElementById('setting_comp_send_btn').querySelectorAll('label'); + var option = 0; + + for (let i = 1; i < 3; i++) { + if (config[i].classList.contains('selected')) { + option = i; + break; + } + } + + if (option === 0) + return; + + ['img2img', 'extras'].forEach((mode) => { + const row = gradioApp().getElementById(`image_buttons_${mode}`).querySelector('.form'); + const btn = row.lastElementChild.cloneNode(); + + btn.id = `${mode}_send_to_comp`; + btn.title = "Send images to comparison tab."; + if (option === 1) + btn.textContent = "Send to Comparison"; + else + btn.textContent = "🆚"; + + if (mode === "extras") { + btn.addEventListener('click', () => { + ImgCompLoader.loadImage("extras"); + this.switch_to_comparison(); + }); + } + else { + const tabs = gradioApp().getElementById('img2img_settings').querySelector('.tabs').querySelector('.tab-nav'); + + btn.addEventListener('click', () => { + [...tabs.querySelectorAll('button')].forEach((tab) => { + if (tab.classList.contains('selected')) { + const t = tab.textContent.trim(); + + if (t === "img2img") { + ImgCompLoader.loadImage("i2i"); + this.switch_to_comparison(); + } + else if (t === "Inpaint") { + ImgCompLoader.loadImage("inpaint"); + this.switch_to_comparison(); + } + else { + alert("Only img2img and Inpaint are supported in Comparison!"); + return; + } + } + }); + }); + } + + row.appendChild(btn); + }); + } + + static addTxt2ImgButton() { + // 0: Off ; 1: Text ; 2: Icon + const config = gradioApp().getElementById('setting_comp_send_btn_t2i').querySelectorAll('label'); + var option = 0; + + for (let i = 1; i < 3; i++) { + if (config[i].classList.contains('selected')) { + option = i; + break; + } + } + + if (option === 0) + return; + + ["txt2img_generate", "txt2img_upscale"].forEach((btn) => { + const generate = gradioApp().getElementById(btn); + if (generate != null) + generate.addEventListener("click", () => { + this.cached_image = gradioApp().getElementById('txt2img_gallery').querySelector('img')?.src; + }); + }); + + const row = gradioApp().getElementById("image_buttons_txt2img").querySelector('.form'); + const btn = row.lastElementChild.cloneNode(); + + btn.id = "txt2img_send_to_comp"; + btn.title = "Send images to comparison tab."; + if (option === 1) + btn.textContent = "Send to Comparison"; + else + btn.textContent = "🆚"; + + btn.addEventListener('click', () => { + if (this.cached_image == null) { + alert("No cached result exists!"); + return; + } + + ImageComparator.img_A.src = this.cached_image; + ImageComparator.img_B.src = gradioApp().getElementById('txt2img_gallery').querySelector('img').src; + ImageComparator.reset(); + + this.switch_to_comparison(); + }); + + row.appendChild(btn); + } + + static init() { + const block_A = gradioApp().getElementById('img_comp_A'); + this.img_A = block_A.querySelector('img'); + const block_B = gradioApp().getElementById('img_comp_B'); + this.img_B = block_B.querySelector('img'); + + const tab = gradioApp().getElementById('tab_sd-webui-image-comparison'); + this.IMG_COMP_WIDTH = parseFloat(getComputedStyle(tab).getPropertyValue('--img-comp-width').split('px')[0]); + + const row = gradioApp().getElementById('img_comp_row'); + row.setAttribute("tabindex", 0); + row.style.display = 'block'; + + block_A.insertBefore(this.img_A, block_A.firstChild); + while (block_A.children.length > 1) + block_A.lastChild.remove(); + + block_B.insertBefore(this.img_B, block_B.firstChild); + while (block_B.children.length > 1) + block_B.lastChild.remove(); + + block_A.classList.add('comp-block'); + block_B.classList.add('comp-block'); + + this.img_A.ondragstart = (event) => { event.preventDefault; return false; }; + this.img_B.ondragstart = (event) => { event.preventDefault; return false; }; + + block_B.style.pointerEvents = 'none'; + block_B.style.left = `calc(50% + ${this.IMG_COMP_WIDTH / 2}px)`; + this.img_B.style.left = `${this.IMG_COMP_WIDTH}px`; + + this.alpha_slider = gradioApp().getElementById('img_comp_alpha'); + ['mousemove', 'touchmove'].forEach((ev) => { + this.alpha_slider.addEventListener(ev, () => { + this.img_B.style.opacity = this.alpha_slider.querySelector('input').value; + }); + }); + + this.direction_checkbox = gradioApp().getElementById('img_comp_horizontal').querySelector('input[type=checkbox]'); + + this.bar = row.querySelector('.bar'); + + this.bar = document.createElement('div'); + this.bar.classList.add('bar'); + row.appendChild(this.bar); + + ['click', 'mousemove', 'touchmove'].forEach((ev) => { + row.addEventListener(ev, (e) => { + e.preventDefault(); + + if (ev.startsWith('touch')) + e = e.changedTouches[0]; + else if (e.buttons != 1) + return; + + const rect = e.target.getBoundingClientRect(); + var ratio = 0.5; + + if (this.isHorizontal()) { + if (e.target.id === 'img_comp_row') + ratio = (e.clientX > (rect.left + rect.right) / 2) ? 1.0 : 0.0; + else + ratio = ((e.clientX - rect.left) / (rect.right - rect.left)); + + const SLIDE_VALUE = this.IMG_COMP_WIDTH * (1.0 - ratio); + + this.bar.style.left = `calc(50% + ${this.IMG_COMP_WIDTH / 2}px - ${SLIDE_VALUE}px)`; + block_B.style.left = `calc(50% + ${this.IMG_COMP_WIDTH / 2}px - ${SLIDE_VALUE}px)`; + this.img_B.style.left = `calc(${-this.IMG_COMP_WIDTH}px + ${SLIDE_VALUE}px)`; + } else { + if (e.target.id === 'img_comp_row') + ratio = (e.clientX > (rect.left + rect.right) / 2) ? 1.0 : 0.0; + else + ratio = ((e.clientY - rect.top) / (rect.bottom - rect.top)); + + const SLIDE_VALUE = this.IMG_COMP_WIDTH * (1.0 - ratio); + + this.bar.style.top = `calc(${this.IMG_COMP_WIDTH}px - ${SLIDE_VALUE}px)`; + block_B.style.top = `calc(${this.IMG_COMP_WIDTH}px - ${SLIDE_VALUE}px)`; + this.img_B.style.top = `calc(${-this.IMG_COMP_WIDTH}px + ${SLIDE_VALUE}px)`; + } + }); + }); + + row.addEventListener("keydown", (e) => { + var flag = false; + + if (e.key == "=" || e.key == "+") { + this.scale = Math.min(this.scale + 0.25, 8.0); + flag = true; + } + if (e.key == "-") { + this.scale = Math.max(this.scale - 0.25, 0.25) + flag = true; + } + if (e.key == "ArrowUp") { + this.translateY += (50 / this.scale); + flag = true; + } + if (e.key == "ArrowDown") { + this.translateY -= (50 / this.scale); + flag = true; + } + if (e.key == "ArrowRight") { + this.translateX -= (50 / this.scale); + flag = true; + } + if (e.key == "ArrowLeft") { + this.translateX += (50 / this.scale); + flag = true; + } + if (e.key == "0") { + this.translateX = 0.0; + this.translateY = 0.0; + this.scale = 1.0; + flag = true; + } + + if (flag) { + e.preventDefault(); + row.style.transform = `scale(${this.scale}) translate(${this.translateX}px, ${this.translateY}px)`; + return false; + } + }); + + ImageComparator.reset(); + this.addButtons(); + this.addTxt2ImgButton(); + + const container = document.createElement("div"); + container.id = "img_comp_row_container"; + row.parentNode.insertBefore(container, row); + container.appendChild(row); + } +} + +onUiLoaded(async () => { + ImageComparator.init(); +}); diff --git a/extensions/sd-webui-image-comparison/scripts/__pycache__/img_comp.cpython-310.pyc b/extensions/sd-webui-image-comparison/scripts/__pycache__/img_comp.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75fd922e4f0e1b221de916219d689ff5e2671ee1 Binary files /dev/null and b/extensions/sd-webui-image-comparison/scripts/__pycache__/img_comp.cpython-310.pyc differ diff --git a/extensions/sd-webui-image-comparison/scripts/img_comp.py b/extensions/sd-webui-image-comparison/scripts/img_comp.py new file mode 100644 index 0000000000000000000000000000000000000000..292831367e157067e4e34f554f0583529cea6805 --- /dev/null +++ b/extensions/sd-webui-image-comparison/scripts/img_comp.py @@ -0,0 +1,204 @@ +from modules.images import read_info_from_image +from modules import script_callbacks, shared +from PIL import Image +import gradio as gr +import re + + +def parsePrompts(info: str) -> str: + positive = negative = "" + params = {} + + if "Negative prompt:" in info: + positive, chunk = info.split("Negative prompt:") + + if "Steps" in chunk: + negative, args = chunk.split("Steps") + else: + negative = chunk + args = "" + + else: + if "Steps" in info: + positive, args = info.split("Steps") + else: + positive = info + args = "" + + comma = ",(?=(?:[^\"]*[\"][^\"]*[\"])*[^\"]*$)" + chunks = re.split(comma, ("Steps" + args)) + + for c in chunks: + params[c.split(":", 1)[0].strip()] = c.split(":", 1)[1].strip() + + return ( + positive.strip().replace("<", "<").replace(">", ">"), + negative.strip().replace("<", "<").replace(">", ">"), + params + ) + + +def toDiff(s: str) -> str: + if len(s) == 0: + return "" + else: + return f'{s}' + + +def img2info(imgA, imgB) -> str: + if imgA is None or imgB is None: + return [gr.HTML.update(value=""), gr.HTML.update(value="")] + + infoA, _ = read_info_from_image(imgA) + infoB, _ = read_info_from_image(imgB) + + if infoA is None or infoB is None: + return [gr.HTML.update(value=""), gr.HTML.update(value="")] + + infoA = parsePrompts(infoA) + infoB = parsePrompts(infoB) + + contentA = [] + contentB = [] + + if infoA[0] == infoB[0]: + contentA.append(infoA[0]) + contentB.append(infoB[0]) + else: + contentA.append(toDiff(infoA[0])) + contentB.append(toDiff(infoB[0])) + + if infoA[1] == infoB[1]: + contentA.append(infoA[1]) + contentB.append(infoB[1]) + else: + contentA.append(toDiff(infoA[1])) + contentB.append(toDiff(infoB[1])) + + contentA[0] = f"Positive Prompt: {contentA[0]}" + contentB[0] = f"Positive Prompt: {contentB[0]}" + + contentA[1] = f"Negative Prompt: {contentA[1]}" + contentB[1] = f"Negative Prompt: {contentB[1]}" + + paramsA = [] + paramsB = [] + + for K, V in infoA[2].items(): + if K in infoB[2].keys(): + if V == infoB[2][K]: + paramsA.append(f"{K}: {V}") + paramsB.append(f"{K}: {V}") + else: + paramsA.append(toDiff(f"{K}: {V}")) + paramsB.append(toDiff(f"{K}: {infoB[2][K]}")) + del infoB[2][K] + else: + paramsA.append(toDiff(f"{K}: {V}")) + + for K, V in infoB[2].items(): + paramsB.append(toDiff(f"{K}: {V}")) + + contentA.append(f'Params: {", ".join(paramsA)}') + contentB.append(f'Params: {", ".join(paramsB)}') + + return [ + gr.HTML.update( + value=f""" +
Infotext
+

{'
'.join(contentA)}

+ """ + ), + gr.HTML.update( + value=f""" +
Infotext
+

{'
'.join(contentB)}

+ """ + ), + ] + + +def img_ui(): + dummy = Image.new('RGB', (1, 1), 'dimgrey') + + with gr.Blocks() as IMG_COMP: + with gr.Row(elem_id='img_comp_row'): + gr.Image( + value=dummy, + image_mode='RGB', + type='pil', + show_download_button=False, + interactive=False, + container=False, + height=768, + elem_id='img_comp_A' + ) + + gr.Image( + value=dummy, + image_mode='RGB', + type='pil', + show_download_button=False, + interactive=False, + container=False, + height=768, + elem_id='img_comp_B' + ) + + with gr.Row(): + swap_btn = gr.Button('Swap', elem_id='img_comp_swap', scale=1) + gr.Slider(label="Opacity", minimum=0.0, maximum=1.0, step=0.05, value=1.0, elem_id='img_comp_alpha', scale=3) + dir_cb = gr.Checkbox(label='Horizontal Slider', value=True, elem_id='img_comp_horizontal', scale=1) + + with gr.Row(elem_id='img_comp_tools'): + upload_A = gr.Image( + image_mode='RGB', + label='Image A', + type='pil', + show_download_button=False, + interactive=True, + height=256, + elem_id='img_comp_input_A' + ) + + with gr.Column(): + comp_btn = gr.Button('Compare Upload', elem_id='img_comp_btn') + i2i_btn = gr.Button('Compare img2img', elem_id='img_comp_i2i') + inp_btn = gr.Button('Compare Inpaint', elem_id='img_comp_inpaint') + ex_btn = gr.Button('Compare Extras', elem_id='img_comp_extras') + + upload_B = gr.Image( + image_mode='RGB', + label='Image B', + type='pil', + show_download_button=False, + interactive=True, + height=256, + elem_id='img_comp_input_B' + ) + + comp_btn.click(None, None, None, _js='() => { ImgCompLoader.loadImage("upload"); }') + i2i_btn.click(None, None, None, _js='() => { ImgCompLoader.loadImage("i2i"); }') + inp_btn.click(None, None, None, _js='() => { ImgCompLoader.loadImage("inpaint"); }') + ex_btn.click(None, None, None, _js='() => { ImgCompLoader.loadImage("extras"); }') + + with gr.Row(elem_id='img_comp_info'): + infotextA = gr.HTML() + infotextB = gr.HTML() + + upload_A.change(img2info, [upload_A, upload_B], [infotextA, infotextB]) + upload_B.change(img2info, [upload_A, upload_B], [infotextA, infotextB]) + + swap_btn.click(None, None, None, _js='() => { ImgCompLoader.swapImage(); }') + dir_cb.change(None, None, None, _js='() => { ImageComparator.reset(); }') + + return [(IMG_COMP, 'Comparison', 'sd-webui-image-comparison')] + + +def add_ui_settings(): + shared.opts.add_option("comp_send_btn", shared.OptionInfo("Off", 'Add a "Send to Comparison" button under img2img generation result', gr.Radio, lambda: {"choices": ["Off", "Text", "Icon"]}, section=('ui', "User interface")).needs_reload_ui()) + shared.opts.add_option("comp_send_btn_t2i", shared.OptionInfo("Off", 'Add a "Send to Comparison" button under txt2img generation result to compare against previous generation', gr.Radio, lambda: {"choices": ["Off", "Text", "Icon"]}, section=('ui', "User interface")).needs_reload_ui()) + + +script_callbacks.on_ui_tabs(img_ui) +script_callbacks.on_ui_settings(add_ui_settings) diff --git a/extensions/sd-webui-image-comparison/style.css b/extensions/sd-webui-image-comparison/style.css new file mode 100644 index 0000000000000000000000000000000000000000..4a85716200399bfcc1949b00f5e5ce89c3b99c88 --- /dev/null +++ b/extensions/sd-webui-image-comparison/style.css @@ -0,0 +1,78 @@ +#tab_sd-webui-image-comparison { + overflow: hidden; + --img-comp-width: 768px; +} + +#img_comp_row:focus { + outline: none; +} + +#img_comp_row { + height: var(--img-comp-width); +} + +#img_comp_row img { + position: relative; +} + +#img_comp_row img.hor:hover { + cursor: ew-resize; +} + +#img_comp_row img.ver:hover { + cursor: ns-resize; +} + +#img_comp_row .comp-block { + width: var(--img-comp-width); + height: var(--img-comp-width); + position: absolute; + z-index: 100; + left: calc(50% - (var(--img-comp-width) / 2)); + border-radius: 0px; +} + +#img_comp_row .bar { + display: block; + position: absolute; + background-color: white; + z-index: 500; + min-width: 0px; + min-height: 0px; + opacity: 0.9; + pointer-events: none; +} + +#img_comp_btn { + margin-top: auto; +} + +#img_comp_extras { + margin-bottom: auto; +} + +#img_comp_horizontal { + margin: 1em; +} + +#img_comp_tools { + background-color: var(--panel-background-fill); + padding: 1em; + border-radius: 1em; +} + +#img_comp_info p { + background: var(--panel-background-fill); + padding: 1em; + border-radius: 1em; +} + +#img_comp_info p .comp-diff { + color: red; +} + +#img_comp_row_container { + height: var(--img-comp-width); + width: 100%; + overflow: hidden; +} diff --git a/extensions/sd-webui-image-comparison/tab.gif b/extensions/sd-webui-image-comparison/tab.gif new file mode 100644 index 0000000000000000000000000000000000000000..2ebaa269f09c87d5fddd7a80cd475561d65b832d --- /dev/null +++ b/extensions/sd-webui-image-comparison/tab.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d460617a902a12a887290f676840031e65e9be5f568e067221237f1c11d415e5 +size 2558602 diff --git a/extensions/sd-webui-incantations/.gitignore b/extensions/sd-webui-incantations/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c18dd8d83ceed1806b50b0aaa46beb7e335fff13 --- /dev/null +++ b/extensions/sd-webui-incantations/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/extensions/sd-webui-incantations/LICENSE b/extensions/sd-webui-incantations/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7 --- /dev/null +++ b/extensions/sd-webui-incantations/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/extensions/sd-webui-incantations/README.md b/extensions/sd-webui-incantations/README.md new file mode 100644 index 0000000000000000000000000000000000000000..67fd054829e2584fbc69301f5afaf69ca0acc5f1 --- /dev/null +++ b/extensions/sd-webui-incantations/README.md @@ -0,0 +1,353 @@ +# sd-webui-incantations + +# Table of Contents +- [What is this?](#what-is-this) +- [Installation](#installation) +- [Compatibility Notice](#compatibility-notice) +- [News](#compatibility-notice) +- [Extension Features](#extension-features) + - [Smoothed Energy Guidance](#smoothed-energy-guidance) + - [Semantic CFG](#semantic-cfg-s-cfg) + - [Perturbed Attention Guidance](#perturbed-attention-guidance) + - [CFG Scheduler](#cfg-interval--cfg-scheduler) + - [Multi-Concept T2I-Zero](#multi-concept-t2i-zero--attention-regulation) + - [Seek for Incantations](#seek-for-incantations) +- [Tutorial](#tutorial) +- [Other cool extensions](#also-check-out) +- [Credits](#credits) + +## What is this? +### This extension for [AUTOMATIC1111/stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) implements algorithms from state-of-the-art research to achieve **higher-quality** images with *more accurate* prompt adherence. + +All methods are **training-free** and rely only on modifying the text embeddings or attention maps. + + +## Installation +To install the `sd-webui-incantations` extension, follow these steps: + +0. **Ensure you have the latest Automatic1111 stable-diffusion-webui version ≥ 1.93 installed** + +1. **Open the "Extensions" tab and navigate to the "Install from URL" section**: + +2. **Paste the repository URL into the "URL for extension's git repository" field**: + ``` + https://github.com/v0xie/sd-webui-incantations.git + ``` + +3. **Press the Install button**: Wait a few seconds for the extension to finish installing. + +4. **Restart the Web UI**: + Completely restart your Stable Diffusion Web UI to load the new extension. + +## Compatibility Notice +* Incompatible with **stable-diffusion-webui-forge**: Use this extension with Forge: https://github.com/pamparamm/sd-perturbed-attention +* Reported incompatible with Adetailer: https://github.com/v0xie/sd-webui-incantations/issues/21 +* Reported incompatible with ControlNet Reference Only: https://github.com/v0xie/sd-webui-incantations/pull/47 +* Incompatible with some older webui versions: https://github.com/v0xie/sd-webui-incantations/issues/14 +* May conflict with other extensions which modify the CFGDenoiser + +## News +- 2024-07-02 🔥 - Smoothed Energy Guidance! https://github.com/v0xie/sd-webui-incantations/pull/60 +- 2024-06-07 🔥 - Saliency Adaptive Noise Fusion for PAG, and bug-fixes! https://github.com/v0xie/sd-webui-incantations/pull/52 +- 2024-05-15 🔥 - S-CFG, optimizations for PAG and T2I-Zero, and more! https://github.com/v0xie/sd-webui-incantations/pull/37 +- 2024-04-29 🔥 - The implementation of T2I-Zero is fixed and works much more stably now. + +# Extension Features + +--- +## Smoothed Energy Guidance +Increases quality of outputs by blurring the self-attention in the middle block layers, with minimal added inference time. +Recommended to fix the CFG scale to 3.0, and control the effect using the Blur Sigma value. Increase CFG if the effect is insufficient. + +#### Controls +* **SEG Blur Sigma** - Controls the sigma value (2^sigma) in the Gaussian Blur. Values greater than 10.5 is "infinite blur". +* **Start Step**: Start SEG on this step. +* **End Step**: End SEG after this step. + +#### Results +SD XL + +* Unconditional +![image](./images/xyz_grid-0463-3.jpg) + +- Prompt: "a family of teddy bears having a barbecue in their backyard" +![image](./images/xyz_grid-0469-4.jpg) + +#### Also check out the paper authors' official project repository: +- https://github.com/SusungHong/SEG-SDXL + +#### [Return to top](#sd-webui-incantations) + +--- +## Semantic CFG (S-CFG) +https://arxiv.org/abs/2404.05384 +Dynamically rescale CFG guidance per semantic region to a uniform level to improve image / text alignment. +**Very computationally expensive**: A batch size of 4 with 1024x1024 will max out a 24GB 4090. + +#### Controls +* **SCFG Scale**: Multiplies the correction by a constant factor. Default: 1.0. +* **SCFG R**: A hyperparameter controlling the factor of cross-attention map refinement. Higher values use more memory and computation time. Default: 4. +* **Rate Min**: The minimum rate that the CFG can be scaled by. Default: 0.8. +* **Rate Max**: The maximum rate that the CFG can be scaled by. Default: 3.0. +* **Clamp Rate**: Overrides Rate Max. Clamps the Max Rate to Clamp Rate / CFG. Default: 0.0. +* **Start Step**: Start S-CFG on this step. +* **End Step**: End S-CFG after this step. + +#### Results +Prompt: "A cute puppy on the moon", Min Rate: 0.5, Max Rate: 10.0 +- SD 1.5 +![image](./images/xyz_grid-0006-1-SCFG.jpg) + +#### Also check out the paper authors' official project repository: +- https://github.com/SmilesDZgk/S-CFG + +#### [Return to top](#sd-webui-incantations) + +--- +## Perturbed Attention Guidance +https://arxiv.org/abs/2403.17377 +An alternative/complementary method to CFG (Classifier-Free Guidance) that increases sampling quality. + +# Update: 20-05-2024 +Implemented a new feature called "Saliency-Adaptive Noise Fusion" derived from ["High-fidelity Person-centric Subject-to-Image Synthesis"](https://arxiv.org/abs/2311.10329). + +This feature combines the guidance from PAG and CFG in an adaptive way that improves image quality especially at higher guidance scales. + +Check out the paper authors' project repository here: https://github.com/CodeGoat24/Face-diffuser + +#### Controls +* **Use Saliency-Adaptive Noise Fusion**: Use improved method of combining CFG + PAG. +* **PAG Scale**: Controls the intensity of effect of PAG on the generated image. +* **PAG Start Step**: Step to start using PAG. +* **PAG End Step**: Step to stop using PAG. + +#### Results +Prompt: "a puppy and a kitten on the moon" +- SD 1.5 +![image](./images/xyz_grid-3040-1-a%20puppy%20and%20a%20kitten%20on%20the%20moon.png) + +- SD XL +![image](./images/xyz_grid-3041-1-a%20puppy%20and%20a%20kitten%20on%20the%20moon.jpg) + +#### Also check out the paper authors' official project page: +- https://ku-cvlab.github.io/Perturbed-Attention-Guidance/ + +#### [Return to top](#sd-webui-incantations) + +--- + +## CFG Interval / CFG Scheduler +https://arxiv.org/abs/2404.07724 and https://arxiv.org/abs/2404.13040 + +Constrains the usage of CFG to within a specified noise interval. Allows usage of high CFG levels (>15) without drastic alteration of composition. + +Adds controllable CFG schedules. For Clamp-Linear, use (c=2.0) for SD1.5 and (c=4.0) for SDXL. For PCS, use (s=1.0) for SD1.5 and (s=0.1) for SDXL. + +To use CFG Scheduler, PAG Active must be set True! PAG scale can be set to 0. + +#### Controls +* **Enable CFG Scheduler**: Enables the CFG Scheduler. +* **CFG Schedule Type**: Sets the schedule type to apply CFG. + - Constant: The default CFG method (constant value over all timesteps) + - Interval: Constant with CFG only being applied within the specified noise interval! + - Clamp-Linear: Clamps the CFG to the maximum of (c, Linear) + - Clamp-Cosine: Clamps the CFG to the maximum of (c, Cosine) + - PCS: Powered Cosine, lower values are typically better +* **CFG Noise Interval Start**: Minimum noise level to use CFG with. SDXL recommended value: 0.28. +* **CFG Noise Interval End**: Maximum noise level to use CFG with. SDXL recommended value: >5.42. + + +#### Results +##### CFG Interval +Prompt: "A pointillist painting of a raccoon looking at the sea." +- SD XL +![image](./images/xyz_grid-3192-1-A%20pointillist%20painting%20of%20a%20raccoon%20looking%20at%20the%20sea.jpg) + +##### CFG Schedule +Prompt: "An epic lithograph of a handsome salaryman carefully pouring coffee from a cup into an overflowing carafe, 4K, directed by Wong Kar Wai" +- SD XL +![image](./images/xyz_grid-3380-1-An%20epic%20lithograph%20of%20a%20handsome%20salaryman%20carefully%20pouring%20coffee%20from%20a%20cup%20into%20an%20overflowing%20carafe,%204K,%20directed%20by%20Wong.jpg) + +#### [Return to top](#sd-webui-incantations) +--- +## Multi-Concept T2I-Zero / Attention Regulation + +#### Update: 29-04-2024 +The algorithms previously implemented for T2I-Zero were incorrect. They should be working much more stably now. See the previous result in the 'images' folder for an informal comparison between old and new. + +Implements Corrections by Similarities and Cross-Token Non-Maximum Suppression from https://arxiv.org/abs/2310.07419 + +Also implements some methods from "Enhancing Semantic Fidelity in Text-to-Image Synthesis: Attention Regulation in Diffusion Models" https://arxiv.org/abs/2403.06381 + +#### Corrections by Similarities +Reduces the contribution of tokens on far away or conceptually unrelated tokens. + +#### Cross-Token Non-Maximum Suppression +Attempts to reduces the mixing of features of unrelated concepts. + +#### Controls: +* **Step End**: After this step, the effect of both CbS and CTNMS ends. +* **Correction by Similarities Window Size**: The number of adjacent tokens on both sides that can influence each token +* **CbS Score Threshold**: Tokens with similarity below this threshold have their effect reduced +* **CbS Correction Strength**: How much the Correction by Similarities effects the image. +* **Alpha for Cross-Token Non-Maximum Suppression**: Controls how much effect the attention maps of CTNMS affects the image. +* **EMA Smoothing Factor**: Smooths the results based on the average of the results of the previous steps. 0 is disabled. + +#### Known Issues: +Can error out with image dimensions which are not a multiple of 64 + +#### Results: +Prompt: "A photo of a lion and a grizzly bear and a tiger in the woods" +SD XL +![image](./images/xyz_grid-3348-1590472902-A%20photo%20of%20a%20lion%20and%20a%20grizzly%20bear%20and%20a%20tiger%20in%20the%20woods.jpg) + +#### Also check out the paper authors' official project pages: +- https://multi-concept-t2i-zero.github.io/ +- https://github.com/YaNgZhAnG-V5/attention_regulation + +#### [Return to top](#sd-webui-incantations) +--- +### Seek for Incantations +An incomplete implementation of a "prompt-upsampling" method from https://arxiv.org/abs/2401.06345 +Generates an image following the prompt, then uses CLIP text/image similarity to add on to the prompt and generate a new image. + +#### Controls: +* **Append Generated Caption**: If true, will append an additional interrogated caption to the prompt. For Deepbooru Interrogate, recommend disabling. +* **Deepbooru Interrogate**: Uses Deepbooru to interrogate instead of CLIP. +* **Delimiter**: The word to separate the original prompt and the generated prompt. Recommend trying BREAK, AND, NOT, etc. +* **Word Replacement**: The word/token to replace dissimilar words with. +* **Gamma**: Replaces words below this level of similarity with the Word Replacement. + +For example, if your prompt is "a blue dog", delimiter is "BREAK", and word replacement is "-", and the level of similarity of the word "blue" in the generated image is below gamma, then the new prompt will be "a blue dog BREAK a - dog" + +A WIP implementation of the "prompt optimization" methods are available in branch ["s4a-dev2"](https://github.com/v0xie/sd-webui-incantations/tree/s4a-dev2) + + +#### Results: +SD XL +* Original Prompt: cinematic 4K photo of a dog riding a bus and eating cake and wearing headphones +* Modified Prompt: cinematic 4K photo of a dog riding a bus and eating cake and wearing headphones BREAK - - - - - dog - - bus - - - - - - +![image](./images/xyz_grid-2652-1419902843-cinematic%204K%20photo%20of%20a%20dog%20riding%20a%20bus%20and%20eating%20cake%20and%20wearing%20headphones.png) + +#### [Return to top](#sd-webui-incantations) +--- + +### Issues / Pull Requests are welcome! +--- + +### Tutorial + +[**Improve Stable Diffusion Prompt Following & Image Quality Significantly With Incantations Extension**](https://youtu.be/lMQ7DIPmrfI) + +[![image](https://cdn-uploads.huggingface.co/production/uploads/6345bd89fe134dfd7a0dba40/TzuZWTiHAc3wTxh3PwGL5.png)](https://youtu.be/lMQ7DIPmrfI) + +#### [Return to top](#sd-webui-incantations) + +## Also check out: + +* **Characteristic Guidance**: Awesome enhancements for sampling at high CFG levels [https://github.com/scraed/CharacteristicGuidanceWebUI](https://github.com/scraed/CharacteristicGuidanceWebUI) + +* **A1111-SD-WebUI-DTG**: Awesome prompt upsampling method for booru trained anime models [https://github.com/KohakuBlueleaf/z-a1111-sd-webui-dtg](https://github.com/KohakuBlueleaf/z-a1111-sd-webui-dtg) + +* **CADS**: Diversify your generated images [https://github.com/v0xie/sd-webui-cads](https://github.com/v0xie/sd-webui-cads) + +* **Semantic Guidance**: [https://github.com/v0xie/sd-webui-semantic-guidance](https://github.com/v0xie/sd-webui-semantic-guidance) + +* **Agent Attention**: Faster image generation and improved image quality with Agent Attention [https://github.com/v0xie/sd-webui-agentattention](https://github.com/v0xie/sd-webui-agentattention) + +#### [Return to top](#sd-webui-incantations) +--- + +### Credits +- The authors of the papers for their methods: + + @misc{yu2024seek, + title={Seek for Incantations: Towards Accurate Text-to-Image Diffusion Synthesis through Prompt Engineering}, + author={Chang Yu and Junran Peng and Xiangyu Zhu and Zhaoxiang Zhang and Qi Tian and Zhen Lei}, + year={2024}, + eprint={2401.06345}, + archivePrefix={arXiv}, + primaryClass={cs.CV} + } + + @misc{tunanyan2023multiconcept, + title={Multi-Concept T2I-Zero: Tweaking Only The Text Embeddings and Nothing Else}, + author={Hazarapet Tunanyan and Dejia Xu and Shant Navasardyan and Zhangyang Wang and Humphrey Shi}, + year={2023}, + eprint={2310.07419}, + archivePrefix={arXiv}, + primaryClass={cs.CV} + } + + @misc{ahn2024selfrectifying, + title={Self-Rectifying Diffusion Sampling with Perturbed-Attention Guidance}, + author={Donghoon Ahn and Hyoungwon Cho and Jaewon Min and Wooseok Jang and Jungwoo Kim and SeonHwa Kim and Hyun Hee Park and Kyong Hwan Jin and Seungryong Kim}, + year={2024}, + eprint={2403.17377}, + archivePrefix={arXiv}, + primaryClass={cs.CV} + } + + @misc{zhang2024enhancing, + title={Enhancing Semantic Fidelity in Text-to-Image Synthesis: Attention Regulation in Diffusion Models}, + author={Yang Zhang and Teoh Tze Tzun and Lim Wei Hern and Tiviatis Sim and Kenji Kawaguchi}, + year={2024}, + eprint={2403.06381}, + archivePrefix={arXiv}, + primaryClass={cs.CV} + } + + @misc{kynkäänniemi2024applying, + title={Applying Guidance in a Limited Interval Improves Sample and Distribution Quality in Diffusion Models}, + author={Tuomas Kynkäänniemi and Miika Aittala and Tero Karras and Samuli Laine and Timo Aila and Jaakko Lehtinen}, + year={2024}, + eprint={2404.07724}, + archivePrefix={arXiv}, + primaryClass={cs.CV} + } + + @misc{wang2024analysis, + title={Analysis of Classifier-Free Guidance Weight Schedulers}, + author={Xi Wang and Nicolas Dufour and Nefeli Andreou and Marie-Paule Cani and Victoria Fernandez Abrevaya and David Picard and Vicky Kalogeiton}, + year={2024}, + eprint={2404.13040}, + archivePrefix={arXiv}, + primaryClass={cs.CV} + } + + @misc{shen2024rethinking, + title={Rethinking the Spatial Inconsistency in Classifier-Free Diffusion Guidance}, + author={Dazhong Shen and Guanglu Song and Zeyue Xue and Fu-Yun Wang and Yu Liu}, + year={2024}, + eprint={2404.05384}, + archivePrefix={arXiv}, + primaryClass={cs.CV} + } + + @misc{wang2024highfidelity, + title={High-fidelity Person-centric Subject-to-Image Synthesis}, + author={Yibin Wang and Weizhong Zhang and Jianwei Zheng and Cheng Jin}, + year={2024}, + eprint={2311.10329}, + archivePrefix={arXiv}, + primaryClass={cs.CV} + } + + @misc{hong2024smoothedenergyguidanceguiding, + title={Smoothed Energy Guidance: Guiding Diffusion Models with Reduced Energy Curvature of Attention}, + author={Susung Hong}, + year={2024}, + eprint={2408.00760}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2408.00760} + } +} + + +- [Hard Prompts Made Easy](https://github.com/YuxinWenRick/hard-prompts-made-easy) +- [@udon-universe's extension templates](https://github.com/udon-universe/stable-diffusion-webui-extension-templates) + +#### [Return to top](#sd-webui-incantations) +--- + diff --git a/extensions/sd-webui-incantations/experiments/pl.ipynb b/extensions/sd-webui-incantations/experiments/pl.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..4cac6eec351e1ca85ca7625dfc3248450858547e --- /dev/null +++ b/extensions/sd-webui-incantations/experiments/pl.ipynb @@ -0,0 +1,199 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import clip\n", + "import os, sys\n", + "import numpy as np\n", + "import torch\n", + "import torch.hub\n", + "from torchvision import transforms\n", + "from torchvision.transforms.functional import InterpolationMode\n", + "from PIL import Image\n", + "sys.path.append('../../..')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def categories():\n", + "\ttxt_path = os.path.join('../../../interrogate', 'flavors.txt')\n", + "\twith open(txt_path, 'r', encoding='utf-8') as f:\n", + "\t\treturn [line.strip() for line in f.readlines()]\n", + "c = categories()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "device = 'cuda'\n", + "clip_model_name = 'ViT-L/14'\n", + "clip_models_path = '/f/stablediffusion/stable-diffusion-webui/models/clip-interrogator'\n", + "blip_image_eval_size = 384\n", + "dtype = torch.float32\n", + "\n", + "# from modules/interrogate.py\n", + "def load_clip_model():\n", + "\timport clip\n", + "\tmodel, preprocess = clip.load(clip_model_name)\n", + "\tmodel.eval()\n", + "\tmodel = model.to(device)\n", + "\treturn model, preprocess\n", + "\n", + "def preprocess_img(preprocess, pil_image):\n", + "\t\tclip_image = preprocess(pil_image).unsqueeze(0).type(dtype).to(device)\n", + "\t\treturn clip_image\n", + "\n", + "def encode_image(clip_model, clip_image):\n", + "\timage_features = clip_model.encode_image(clip_image).type(dtype)\n", + "\treturn image_features\n", + "\n", + "def similarity(text_array, text_features, image_features, top_count=1):\n", + " similarity = torch.zeros((1, len(text_array))).to(device)\n", + " for i in range(image_features.shape[0]):\n", + " similarity += (100.0 * image_features[i].unsqueeze(0) @ text_features.T).softmax(dim=-1)\n", + " similarity /= image_features.shape[0]\n", + "\n", + " top_probs, top_labels = similarity.cpu().topk(top_count, dim=-1)\n", + " return [(text_array[top_labels[0][i].numpy()], (top_probs[0][i].numpy()*100)) for i in range(top_count)] \n", + "\n", + "def torch_gc():\n", + "\twith torch.cuda.device('cuda:0'):\n", + "\t\ttorch.cuda.empty_cache()\n", + "\t\ttorch.cuda.ipc_collect()\n", + "\n", + "def rank(clip_model, image_features, text_array, top_count=1):\n", + "\ttop_count = min(top_count, len(text_array))\n", + "\ttext_tokens = clip.tokenize(list(text_array), truncate=True).to(device)\n", + "\ttext_features = clip_model.encode_text(text_tokens).type(dtype)\n", + "\ttext_features /= text_features.norm(dim=-1, keepdim=True)\n", + "\n", + "\tsimilarity = torch.zeros((1, len(text_array))).to(device)\n", + "\tfor i in range(image_features.shape[0]):\n", + "\t\tsimilarity += (100.0 * image_features[i].unsqueeze(0) @ text_features.T).softmax(dim=-1)\n", + "\tsimilarity /= image_features.shape[0]\n", + "\n", + "\ttop_probs, top_labels = similarity.cpu().topk(top_count, dim=-1)\n", + "\treturn [(text_array[top_labels[0][i].numpy()], (top_probs[0][i].numpy()*100)) for i in range(top_count)]\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model, preprocess = load_clip_model()\n", + "clip_model = model\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "img_10 = Image.open('images/10.png')\n", + "img_10_features = encode_image(model, preprocess_img(preprocess, img_10))\n", + "\n", + "img_30 = Image.open('images/30.png')\n", + "img_30_features = encode_image(model, preprocess_img(preprocess, img_30))\n", + "\n", + "# %%markdown\n", + "# ![title](images/10.png)\n", + "# ![title](images/30.png)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "clip_model = model\n", + "device = 'cuda'\n", + "dtype = torch.float32\n", + "\n", + "text = 'a photo of a cat wearing a pink hat on a blue rug'\n", + "text_array = text.split(' ')\n", + "text_array = c\n", + "text_tokens = clip.tokenize(text, truncate=True).to(device)\n", + "text_features = clip_model.encode_text(text_tokens).type(dtype)\n", + "\n", + "with torch.no_grad():\n", + "\tprint(similarity(text_array, text_features, img_10_features, len(text_array)))\n", + "\n", + "text_features_single = clip_model.encode_text(text_tokens).type(dtype)\n", + "\n", + "text_concat = ', '.join([text, text, text, text])\n", + "text_tokens = clip.tokenize(text_concat, truncate=True).to(device)\n", + "\n", + "\n", + "text_token_list = [clip.tokenize(x, truncate=True).to(device) for x in text_array]\n", + "empty_token = clip.tokenize('', truncate=True).to(device)\n", + "text_feature_list = [clip_model.encode_text(t).type(dtype) for t in text_token_list]\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "t = torch.zeros_like(text_features).to(torch.float32)\n", + "torch.not_equal(text_features, t)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[x.shape for x in text_feature_list]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/extensions/sd-webui-incantations/images/xyz_grid-0006-1-SCFG.jpg b/extensions/sd-webui-incantations/images/xyz_grid-0006-1-SCFG.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c59b58a9b5de198f009038728856ef89304b97d0 Binary files /dev/null and b/extensions/sd-webui-incantations/images/xyz_grid-0006-1-SCFG.jpg differ diff --git a/extensions/sd-webui-incantations/images/xyz_grid-0463-3.jpg b/extensions/sd-webui-incantations/images/xyz_grid-0463-3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ba891c0ba2ef4f7174f0b78a95c7632ad2fe9448 --- /dev/null +++ b/extensions/sd-webui-incantations/images/xyz_grid-0463-3.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c084290e706547a1bc1e640da5559a5be0fc02f3edefe3467f3352403fd41bf +size 1332650 diff --git a/extensions/sd-webui-incantations/images/xyz_grid-0469-4.jpg b/extensions/sd-webui-incantations/images/xyz_grid-0469-4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..28f970dfc6563c6600be168226b3a18b4d01cbc6 --- /dev/null +++ b/extensions/sd-webui-incantations/images/xyz_grid-0469-4.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c4893f7d7b37740d370f30eacc71ec3ea3102fd3ab6baa79570f73e4422b94c +size 1234698 diff --git a/extensions/sd-webui-incantations/images/xyz_grid-2652-1419902843-cinematic 4K photo of a dog riding a bus and eating cake and wearing headphones.png b/extensions/sd-webui-incantations/images/xyz_grid-2652-1419902843-cinematic 4K photo of a dog riding a bus and eating cake and wearing headphones.png new file mode 100644 index 0000000000000000000000000000000000000000..e0ffc0dc352088ffec01b32cae3e12eefd67a6aa --- /dev/null +++ b/extensions/sd-webui-incantations/images/xyz_grid-2652-1419902843-cinematic 4K photo of a dog riding a bus and eating cake and wearing headphones.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b79b24a4b61fce7999311d2636a536f7b79a4052f89ab24c29175817e34cb70a +size 2484060 diff --git a/extensions/sd-webui-incantations/images/xyz_grid-2660-1590472902-A photo of a lion and a grizzly bear and a tiger in the woods.jpg b/extensions/sd-webui-incantations/images/xyz_grid-2660-1590472902-A photo of a lion and a grizzly bear and a tiger in the woods.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cc96fa793732ce79099cf6dc8fd311cf9d14f10a --- /dev/null +++ b/extensions/sd-webui-incantations/images/xyz_grid-2660-1590472902-A photo of a lion and a grizzly bear and a tiger in the woods.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d226d6ec7f66092b7289302aadbbedfd9cac9fc198ddb0ee18df257102b6e810 +size 1314985 diff --git a/extensions/sd-webui-incantations/images/xyz_grid-3040-1-a puppy and a kitten on the moon.png b/extensions/sd-webui-incantations/images/xyz_grid-3040-1-a puppy and a kitten on the moon.png new file mode 100644 index 0000000000000000000000000000000000000000..c74bcda7710d27658a125c43b260619d9cf97e42 --- /dev/null +++ b/extensions/sd-webui-incantations/images/xyz_grid-3040-1-a puppy and a kitten on the moon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:779c3254676ea069facd7591e3adc5e38c930347740a4c0bae1d561473012431 +size 2079942 diff --git a/extensions/sd-webui-incantations/images/xyz_grid-3041-1-a puppy and a kitten on the moon.jpg b/extensions/sd-webui-incantations/images/xyz_grid-3041-1-a puppy and a kitten on the moon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d8c731c0536998e8584fb2dffb632e160b8bd0c8 Binary files /dev/null and b/extensions/sd-webui-incantations/images/xyz_grid-3041-1-a puppy and a kitten on the moon.jpg differ diff --git a/extensions/sd-webui-incantations/images/xyz_grid-3192-1-A pointillist painting of a raccoon looking at the sea.jpg b/extensions/sd-webui-incantations/images/xyz_grid-3192-1-A pointillist painting of a raccoon looking at the sea.jpg new file mode 100644 index 0000000000000000000000000000000000000000..54337b4ada7882e1b3cf17716997f3867c57be39 Binary files /dev/null and b/extensions/sd-webui-incantations/images/xyz_grid-3192-1-A pointillist painting of a raccoon looking at the sea.jpg differ diff --git a/extensions/sd-webui-incantations/images/xyz_grid-3348-1590472902-A photo of a lion and a grizzly bear and a tiger in the woods.jpg b/extensions/sd-webui-incantations/images/xyz_grid-3348-1590472902-A photo of a lion and a grizzly bear and a tiger in the woods.jpg new file mode 100644 index 0000000000000000000000000000000000000000..214e7c066f29e8fbbf5c62149a08f2b7d76d7caf --- /dev/null +++ b/extensions/sd-webui-incantations/images/xyz_grid-3348-1590472902-A photo of a lion and a grizzly bear and a tiger in the woods.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c19d684c2381a5a62c16499af1f995db0c2699e1651715eabd09f73bfc7a62b5 +size 1338934 diff --git a/extensions/sd-webui-incantations/images/xyz_grid-3380-1-An epic lithograph of a handsome salaryman carefully pouring coffee from a cup into an overflowing carafe, 4K, directed by Wong.jpg b/extensions/sd-webui-incantations/images/xyz_grid-3380-1-An epic lithograph of a handsome salaryman carefully pouring coffee from a cup into an overflowing carafe, 4K, directed by Wong.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8cf8aaf820d8b0643fce9d7d0807edb7ce4a5005 Binary files /dev/null and b/extensions/sd-webui-incantations/images/xyz_grid-3380-1-An epic lithograph of a handsome salaryman carefully pouring coffee from a cup into an overflowing carafe, 4K, directed by Wong.jpg differ diff --git a/extensions/sd-webui-incantations/requirements.txt b/extensions/sd-webui-incantations/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..8e89390b9aa6d1654397cbe4a64d0ea0b9eed068 --- /dev/null +++ b/extensions/sd-webui-incantations/requirements.txt @@ -0,0 +1 @@ +# sentence_transformers >= 2.2.2 \ No newline at end of file diff --git a/extensions/sd-webui-incantations/scripts/__pycache__/cfg_combiner.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/__pycache__/cfg_combiner.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc231ae2bd533401c270ac2074bc7c009af4a16d Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/__pycache__/cfg_combiner.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/__pycache__/incant.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/__pycache__/incant.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9684a6b6664d9869416c33a80b888378b07fea13 Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/__pycache__/incant.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/__pycache__/incantation_base.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/__pycache__/incantation_base.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05cb9933cbacd0ed9517026948fe97fef15e551b Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/__pycache__/incantation_base.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/__pycache__/pag.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/__pycache__/pag.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e3f9634a19965752b0cd69e15db3a104dd333edb Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/__pycache__/pag.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/__pycache__/save_attn_maps.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/__pycache__/save_attn_maps.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e651ffc4aa0bfa7cb909e9980e5625825af7c38 Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/__pycache__/save_attn_maps.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/__pycache__/scfg.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/__pycache__/scfg.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f521ab6008b4ff398a65ca00b90bc9d17288974 Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/__pycache__/scfg.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/__pycache__/smoothed_energy_guidance.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/__pycache__/smoothed_energy_guidance.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61c39623f8aa7db1488a30915d9735c38ed3abe5 Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/__pycache__/smoothed_energy_guidance.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/__pycache__/t2i_zero.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/__pycache__/t2i_zero.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..217db0d48841100ce416d88509bb48b53f0ac9d6 Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/__pycache__/t2i_zero.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/__pycache__/ui_wrapper.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/__pycache__/ui_wrapper.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88435aa20b690c36aca4d8e84e6ef521fd2ee2b6 Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/__pycache__/ui_wrapper.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/cfg_combiner.py b/extensions/sd-webui-incantations/scripts/cfg_combiner.py new file mode 100644 index 0000000000000000000000000000000000000000..bbf3d4f6f48c4ea1b1f0eae3242baf55aee83d6a --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/cfg_combiner.py @@ -0,0 +1,276 @@ +import gradio as gr +import logging +import torch +import torchvision.transforms as F +from modules import shared, scripts, devices, patches, script_callbacks +from modules.script_callbacks import CFGDenoiserParams +from modules.processing import StableDiffusionProcessing +from scripts.incantation_base import UIWrapper +from scripts.scfg import scfg_combine_denoised + +logger = logging.getLogger(__name__) + +class CFGCombinerScript(UIWrapper): + """ Some scripts modify the CFGs in ways that are not compatible with each other. + This script will patch the CFG denoiser function to apply CFG in an ordered way. + This script adds a dict named 'incant_cfg_params' to the processing object. + This dict contains the following: + 'denoiser': the denoiser object + 'pag_params': list of PAG parameters + 'scfg_params': the S-CFG parameters + ... + """ + def __init__(self): + pass + + # Extension title in menu UI + def title(self): + return "CFG Combiner" + + # Decide to show menu in txt2img or img2img + def show(self, is_img2img): + return scripts.AlwaysVisible + + # Setup menu ui detail + def setup_ui(self, is_img2img): + self.infotext_fields = [] + self.paste_field_names = [] + return [] + + def before_process(self, p: StableDiffusionProcessing, *args, **kwargs): + logger.debug("CFGCombinerScript before_process") + cfg_dict = { + "denoiser": None, + "pag_params": None, + "scfg_params": None + } + setattr(p, 'incant_cfg_params', cfg_dict) + + def process(self, p: StableDiffusionProcessing, *args, **kwargs): + pass + + def before_process_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + pass + + def process_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + """ Process the batch and hook the CFG denoiser if PAG or S-CFG is active """ + logger.debug("CFGCombinerScript process_batch") + pag_active = p.extra_generation_params.get('PAG Active', False) + cfg_active = p.extra_generation_params.get('CFG Interval Enable', False) + scfg_active = p.extra_generation_params.get('SCFG Active', False) + + if not any([ + pag_active, + cfg_active, + scfg_active + ]): + return + + #logger.debug("CFGCombinerScript process_batch: pag_active or scfg_active") + + cfg_denoise_lambda = lambda params: self.on_cfg_denoiser_callback(params, p.incant_cfg_params) + unhook_lambda = lambda: self.unhook_callbacks() + + script_callbacks.on_cfg_denoiser(cfg_denoise_lambda) + script_callbacks.on_script_unloaded(unhook_lambda) + logger.debug('Hooked callbacks') + + def postprocess_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + logger.debug("CFGCombinerScript postprocess_batch") + script_callbacks.remove_current_script_callbacks() + + def unhook_callbacks(self, cfg_dict = None): + if not cfg_dict: + return + self.unpatch_cfg_denoiser(cfg_dict) + + def on_cfg_denoiser_callback(self, params: CFGDenoiserParams, cfg_dict: dict): + """ Callback for when the CFG denoiser is called + Patches the combine_denoised function with a custom one. + """ + if cfg_dict['denoiser'] is None: + cfg_dict['denoiser'] = params.denoiser + else: + self.unpatch_cfg_denoiser(cfg_dict) + self.patch_cfg_denoiser(params.denoiser, cfg_dict) + + def patch_cfg_denoiser(self, denoiser, cfg_dict: dict): + """ Patch the CFG Denoiser combine_denoised function """ + if not cfg_dict: + logger.error("Unable to patch CFG Denoiser, no dict passed as cfg_dict") + return + if not denoiser: + logger.error("Unable to patch CFG Denoiser, denoiser is None") + return + + if getattr(denoiser, 'combine_denoised_patched', False) is False: + try: + setattr(denoiser, 'combine_denoised_original', denoiser.combine_denoised) + # create patch that references the original function + pass_conds_func = lambda *args, **kwargs: combine_denoised_pass_conds_list( + *args, + **kwargs, + original_func = denoiser.combine_denoised_original, + pag_params = cfg_dict['pag_params'], + scfg_params = cfg_dict['scfg_params'] + ) + patched_combine_denoised = patches.patch(__name__, denoiser, "combine_denoised", pass_conds_func) + setattr(denoiser, 'combine_denoised_patched', True) + setattr(denoiser, 'combine_denoised_original', patches.original(__name__, denoiser, "combine_denoised")) + except KeyError: + logger.exception("KeyError patching combine_denoised") + pass + except RuntimeError: + logger.exception("RuntimeError patching combine_denoised") + pass + + def unpatch_cfg_denoiser(self, cfg_dict = None): + """ Unpatch the CFG Denoiser combine_denoised function """ + if cfg_dict is None: + return + denoiser = cfg_dict.get('denoiser', None) + if denoiser is None: + return + + setattr(denoiser, 'combine_denoised_patched', False) + try: + patches.undo(__name__, denoiser, "combine_denoised") + except KeyError: + logger.exception("KeyError unhooking combine_denoised") + pass + except RuntimeError: + logger.exception("RuntimeError unhooking combine_denoised") + pass + + cfg_dict['denoiser'] = None + + +def combine_denoised_pass_conds_list(*args, **kwargs): + """ Hijacked function for combine_denoised in CFGDenoiser + Currently relies on the original function not having any kwargs + If any of the params are not None, it will apply the corresponding guidance + The order of guidance is: + 1. CFG and S-CFG are combined multiplicatively + 2. PAG guidance is added to the result + 3. ... + ... + """ + original_func = kwargs.get('original_func', None) + pag_params = kwargs.get('pag_params', None) + scfg_params = kwargs.get('scfg_params', None) + + if pag_params is None and scfg_params is None: + logger.warning("No reason to hijack combine_denoised") + return original_func(*args) + + def new_combine_denoised(x_out, conds_list, uncond, cond_scale): + denoised_uncond = x_out[-uncond.shape[0]:] + denoised = torch.clone(denoised_uncond) + + ### Variables + # 0. Standard CFG Value + cfg_scale = cond_scale + + # 1. CFG Interval + # Overrides cfg_scale if pag_params is not None + if pag_params is not None: + if pag_params.cfg_interval_enable: + cfg_scale = pag_params.cfg_interval_scheduled_value + + # 2. PAG + pag_x_out = None + pag_scale = None + run_pag = False + if pag_params is not None: + pag_active = pag_params.pag_active + pag_x_out = pag_params.pag_x_out + pag_scale = pag_params.pag_scale + + if not pag_active: + pass + # Not within step interval? + elif not pag_params.pag_start_step <= pag_params.step <= pag_params.pag_end_step: + pass + # Scale is zero? + elif pag_scale <= 0: + pass + else: + run_pag = pag_active + + # 3. Saliency Map + use_saliency_map = False + if pag_params is not None: + use_saliency_map = pag_params.pag_sanf + + + ### Combine Denoised + for i, conds in enumerate(conds_list): + for cond_index, weight in conds: + + model_delta = x_out[cond_index] - denoised_uncond[i] + + # S-CFG + rate = 1.0 + if scfg_params is not None: + rate = scfg_combine_denoised( + model_delta = model_delta, + cfg_scale = cfg_scale, + scfg_params = scfg_params, + ) + # If rate is not an int, convert to tensor + if rate is None: + logger.error("scfg_combine_denoised returned None, using default rate of 1.0") + rate = 1.0 + elif not isinstance(rate, int) and not isinstance(rate, float): + rate = rate.to(device=shared.device, dtype=model_delta.dtype) + else: + # rate is tensor, probably + pass + + # 1. Experimental formulation for S-CFG combined with CFG + cfg_x = (model_delta) * rate * (weight * cfg_scale) + if not use_saliency_map or not run_pag: + denoised[i] += cfg_x + del rate + + # 2. PAG + # PAG is added like CFG + if pag_params is not None: + if not run_pag: + pass + # do pag + else: + try: + pag_delta = x_out[cond_index] - pag_x_out[i] + pag_x = pag_delta * (weight * pag_scale) + + if not use_saliency_map: + denoised[i] += pag_x + + # 3. Saliency Adaptive Noise Fusion arXiv.2311.10329v5 + # Smooth the saliency maps + if use_saliency_map: + blur = F.GaussianBlur(kernel_size=3, sigma=1).to(device=shared.device) + omega_rt = blur(torch.abs(cfg_x)) + omega_rs = blur(torch.abs(pag_x)) + soft_rt = torch.softmax(omega_rt, dim=0) + soft_rs = torch.softmax(omega_rs, dim=0) + + m = torch.stack([soft_rt, soft_rs], dim=0) # 2 c h w + _, argmax_indices = torch.max(m, dim=0) + + # select from cfg_x or pag_x + m1 = torch.where(argmax_indices == 0, 1, 0) + + # hadamard product + sal_cfg = cfg_x * m1 + pag_x * (1 - m1) + + denoised[i] += sal_cfg + except Exception as e: + logger.exception("Exception in combine_denoised_pass_conds_list - %s", e) + + #torch.cuda.empty_cache() + devices.torch_gc() + + return denoised + return new_combine_denoised(*args) \ No newline at end of file diff --git a/extensions/sd-webui-incantations/scripts/incant.py b/extensions/sd-webui-incantations/scripts/incant.py new file mode 100644 index 0000000000000000000000000000000000000000..1fd7310a775364d8901c069ea7082f927ed28863 --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/incant.py @@ -0,0 +1,807 @@ +import logging +from os import environ +import modules.scripts as scripts +import gradio as gr +from PIL import Image +import numpy as np +import re + +from modules import script_callbacks, prompt_parser +from modules.script_callbacks import CFGDenoiserParams, CFGDenoisedParams, AfterCFGCallbackParams +from modules.prompt_parser import reconstruct_multicond_batch, stack_conds, reconstruct_cond_batch +from modules.processing import StableDiffusionProcessing, decode_latent_batch, txt2img_image_conditioning +#from modules.shared import sd_model, opts +from modules.sd_samplers_cfg_denoiser import pad_cond +from modules import shared, devices, errors, deepbooru +from modules.interrogate import InterrogateModels +from scripts.ui_wrapper import UIWrapper, arg +# from scripts.t2i_zero import SegaExtensionScript + +import torch +from torchvision.transforms import ToPILImage + +logger = logging.getLogger(__name__) +logger.setLevel(environ.get("SD_WEBUI_LOG_LEVEL", logging.INFO)) + + +""" +!!! +!!! Only semi-functional !!! +!!! + +!!! Might conflict with other extensions that modify the prompt !!! +Known conflicts: Dynamic Prompts + +Appends a "learned" prompt to the end of your prompt that is optimized to maximize the similarity between the text and image embeddings at the end of the diffusion process. + +This is done by masking out words in the prompt that are below a threshold given by CLIP e.g. semantic guidance from the paper. + +This is useful as is because it allows you to generate images that are (maybe) more similar to the prompt. + +The other methods in the paper are not implemented yet. + +I'm not sure how to implement the other methods in the paper because the details of how exactly the "prompt is learned" aren't clear to me. Any insights would be appreciated. + +""" + + +""" + +An unofficial and incomplete implementation of Seek for Incantations: Towards Accurate Text-to-Image Diffusion Synthesis +through Prompt Engineering for Automatic1111 WebUI + +@misc{yu2024seek, + title={Seek for Incantations: Towards Accurate Text-to-Image Diffusion Synthesis through Prompt Engineering}, + author={Chang Yu and Junran Peng and Xiangyu Zhu and Zhaoxiang Zhang and Qi Tian and Zhen Lei}, + year={2024}, + eprint={2401.06345}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} + +Author: v0xie +GitHub URL: https://github.com/v0xie/sd-webui-incantations + + +""" + +class Interrogator: + def __init__(self): + pass + def load(self): + pass + def generate_caption(self): + pass + def unload(self): + pass + +class InterrogatorCLIP(Interrogator): + def __init__(self): + self.interrogator = shared.interrogator + + def load(self): + self.interrogator = shared.interrogator + self.interrogator.load() + + def generate_caption(self, pil_image): + self.load() + return self.interrogator.generate_caption(pil_image.to(shared.device)) + + def unload(self): + self.interrogator.unload() + + +class InterrogatorDeepbooru(Interrogator): + def __init__(self): + self.interrogator = deepbooru.model + + def load(self): + self.interrogator = deepbooru.model + self.interrogator.load() + + def generate_caption(self, pil_image): + self.load() + if not shared.opts.interrogate_return_ranks: + print('\nincantations - warning: interrogate_return_ranks should be enabled for Deepbooru Interrogate to work') + threshold = shared.opts.interrogate_deepbooru_score_threshold + if threshold < 0.4: + print('\nincantations - warning: deepbooru score threshold should be lowered for Deepbooru Interrogate to work') + tags = self.interrogator.tag(pil_image) + #prompts = prompt_parser.parse_prompt_attention(tags) + return tags + + def unload(self): + pass + +class IncantStateParams: + def __init__(self): + self.delim = '' + self.word = '-' + self.coarse = 10 + self.fine = 30 + self.gamma = 0.25 + self.quality = False + self.deepbooru = False + self.prompt = '' + self.prompts = [] + self.prompt_tokens = [] + self.caption_coarse = [] + self.caption_fine = [] + self.img_coarse = [] + self.img_fine = [] + self.emb_img_coarse = [] + self.emb_img_fine = [] + self.emb_txt_coarse = [] + self.emb_txt_fine = [] + self.grad_txt = [] + self.text_tokens = [] + self.grad_img = [] + self.matches_coarse = [] + self.matches_fine = [] + self.masked_prompt = [] + self.get_conds_with_caching = None + self.steps = None + self.iteration = None + self.batch_size = 1 + self.p = None + self.init_noise = None + self.first_stage_cache = None + self.second_stage = False + self.denoiser = None + self.job = None + + # is this loss + self.loss = [] # grad_img * grad txt + self.loss_qual = [] # quality guidance + self.loss_sem = [] # semantic guidance + self.loss_txt_txt = [] # txt - txt + self.loss_txt_img = [] # txt - img + self.loss_spar = [] # sparsity + + # hyperparameters + self.qual_scale = 1.0 + self.sem_scale = 1.0 + self.txt_txt_scale = 1.0 + self.txt_img_scale = 1.0 + self.spar_scale = 1.0 + +class IncantExtensionScript(UIWrapper): + def __init__(self): + self.stage_1 = [[]] + self.cached_c = [[None, None],[None, None]] + self.infotext_fields = {} + self.paste_field_names = [] + + # Extension title in menu UI + def title(self): + return "Seek Incantations" + + # Setup menu ui detail + def setup_ui(self, is_img2img): + return self.setup_seek_incantations() + + def get_infotext_fields(self): + return self.infotext_fields + + def get_paste_field_names(self): + return self.paste_field_names + + def setup_seek_incantations(self): + with gr.Accordion('Seek for Incantations', open=False): + inc_active = gr.Checkbox(value=False, default=False, label="Active", elem_id='incant_active') + inc_quality = gr.Checkbox(value=False, default=False, label="Append Generated Caption", elem_id='incant_append_prompt', info="Append interrogated caption to prompt. (Deepbooru is reversed, if disabled, will not append the masked original prompt)") + inc_deepbooru = gr.Checkbox(value=False, default=False, label="Deepbooru Interrogate", elem_id='incant_deepbooru') + inc_coarse_step = gr.Slider(visible=False, value = 10, minimum = 1, maximum = 120, step = 1, label="Coarse Step", elem_id = 'incant_coarse_step') + with gr.Row(): + inc_delim = gr.Textbox(value='BREAK', label="Delimiter", elem_id='incant_delim', info="Prompt DELIM Optimized Prompt. Try BREAK, AND, NOT, etc.") + inc_word = gr.Textbox(value='-', label="Word Replacement", elem_id='incant_word', info="Replace masked words with this") + with gr.Row(): + inc_gamma = gr.Slider(value = 0.2, minimum = -1.0, maximum = 1.0, step = 0.0001, label="Gamma", elem_id = 'incant_gamma', info="If gamma > 0, mask words with similarity less than gamma percent. If gamma < 0, mask more similar words. For Deepbooru, try higher values > 0.7") + inc_active.do_not_save_to_config = True + inc_quality.do_not_save_to_config = True + inc_deepbooru.do_not_save_to_config = True + inc_delim.do_not_save_to_config = True + inc_word.do_not_save_to_config = True + inc_gamma.do_not_save_to_config = True + inc_coarse_step.do_not_save_to_config = True + self.infotext_fields = [ + (inc_active, lambda d: gr.Checkbox.update(value='INCANT Active' in d)), + (inc_quality, 'INCANT Append Prompt'), + (inc_deepbooru, 'INCANT Deepbooru'), + (inc_delim, 'INCANT Delim'), + (inc_word, 'INCANT Word'), + (inc_gamma, 'INCANT Gamma'), +# (inc_coarse_step, 'INCANT Coarse'), + ] + self.paste_field_names = [ + 'incant_active', + 'incant_append_prompt', + 'incant_deepbooru', + 'incant_delim', + 'incant_word', + 'incant_gamma', +# 'incant_coarse', + ] + return [inc_active, inc_quality, inc_deepbooru, inc_delim, inc_word, inc_gamma, inc_coarse_step] + + def interrogator(self, deepbooru=False): + if deepbooru: + return InterrogatorDeepbooru() + else: + return shared.interrogator + + def before_process(self, p: StableDiffusionProcessing, *args, **kwargs): + self.incant_before_process(p, *args, **kwargs) + + def incant_before_process(self, p: StableDiffusionProcessing, inc_active, inc_quality, inc_deepbooru, inc_delim, inc_word, inc_gamma, inc_coarse_step, *args, **kwargs): + inc_active = getattr(p, "incant_active", inc_active) + if inc_active is False: + return + p.n_iter = p.n_iter * 2 + + def process(self, p: StableDiffusionProcessing, *args, **kwargs): + self.incant_process(p, *args, **kwargs) + + def incant_process(self, p: StableDiffusionProcessing, inc_active, inc_quality, inc_deepbooru, inc_delim, inc_word, inc_gamma, inc_coarse_step, *args, **kwargs): + inc_active = getattr(p, "incant_active", inc_active) + if inc_active is False: + return + inc_quality = getattr(p, "incant_append_prompt", inc_quality) + inc_delim = getattr(p, "incant_delim", inc_delim) + inc_word = getattr(p, "incant_word", inc_word) + + # modifying the all_prompts* may conflict with extensions that do so + # hr fix untested + if p.iteration == 0: + param_list = [ + "all_hr_negative_prompts", + "all_hr_prompts", + "all_negative_prompts", + "all_prompts", + "all_seeds", + "all_subseeds", + ] + + for param_name in param_list: + run_fn_on_attr(p, param_name, duplicate_alternate_elements, p.batch_size) + + # assign + delim_str = f' {inc_delim} ' if len(inc_delim) > 0 else ' ' + for n in range(1, p.n_iter, 2): + start_idx = n * p.batch_size + end_idx = (n + 1) * p.batch_size + p.all_prompts[start_idx:end_idx] = [prompt + delim_str + '<>' for prompt in p.all_prompts[start_idx:end_idx]] + + def before_process_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + self.incant_before_process_batch(p, *args, **kwargs) + + def incant_before_process_batch(self, p: StableDiffusionProcessing, inc_active, inc_quality, inc_deepbooru, inc_delim, inc_word, inc_gamma, inc_coarse_step, *args, **kwargs): + inc_active = getattr(p, "incant_active", inc_active) + if inc_active is False: + return + inc_quality = getattr(p, "incant_append_prompt", inc_quality) + inc_deepbooru = getattr(p, "incant_deepbooru", inc_deepbooru) + inc_delim = getattr(p, "incant_delim", inc_delim) + inc_word = getattr(p, "incant_word", inc_word) + fine_step = getattr(p, "incant_fine", None) + inc_gamma = getattr(p, "incant_gamma", inc_gamma) + inc_coarse_step = getattr(p, "incant_coarse", inc_coarse_step) + if fine_step == None: + #print(f"Fine step {fine_step} is greater than total steps {p.steps}, setting to {p.steps-2}") + fine_step = p.steps - 2 + + interrogator = self.interrogator(inc_deepbooru) + interrogator.load() + n = p.n_iter + if p.iteration % 2 == 1: + n = p.iteration + # batch of images + batch_start_idx = n * p.batch_size + batch_end_idx = (n + 1) * p.batch_size + # mask + mask_start_idx = (n - 1) * p.batch_size + delim_str = f' {inc_delim} ' if len(inc_delim) > 0 else ' ' + #add_mask_prompt = self.stage_1.masked_prompt[mask_start_idx] + for idx in range(batch_start_idx, batch_end_idx): + add_mask_prompt = '' + mask_idx = mask_start_idx + (idx - batch_start_idx) + masked_prompts = self.stage_1.masked_prompt[mask_idx] + # if we don't want to append other masked captions, only use the first one + if not inc_quality: + masked_prompts = [masked_prompts[0]] + for masked_prompt_idx, prompt in enumerate(masked_prompts): + if masked_prompt_idx > 0: + add_mask_prompt += delim_str + prompt + else: + add_mask_prompt += prompt + p.all_prompts[idx] = p.all_prompts[idx].replace('<>', add_mask_prompt) + kwargs['prompts'][mask_idx] = kwargs['prompts'][mask_idx].replace('<>', add_mask_prompt) + + # p.steps += fine_step + # TODO: nicely put this into a dict + p.extra_generation_params.update({ + "INCANT Active": inc_active, + "INCANT Append Prompt": inc_quality, + "INCANT Delim": inc_delim, + "INCANT Word": inc_word, + "INCANT Deepbooru": inc_deepbooru, + "INCANT Fine": fine_step, + "INCANT Gamma": inc_gamma, + }) + self.create_hook(p, inc_active, inc_quality, inc_deepbooru, inc_delim, inc_word, inc_gamma, inc_coarse_step, *args, **kwargs) + + def parse_concept_prompt(self, prompt:str) -> list[str]: + """ + Separate prompt by comma into a list of concepts + TODO: parse prompt into a list of concepts using A1111 functions + >>> g = lambda prompt: self.parse_concept_prompt(prompt) + >>> g("") + [] + >>> g("apples") + ['apples'] + >>> g("apple, banana, carrot") + ['apple', 'banana', 'carrot'] + """ + if len(prompt) == 0: + return [] + return [x.strip() for x in prompt.split(",")] + + def create_hook(self, p, active, quality, deepbooru, delim, word, gamma, coarse_step, *args, **kwargs): + + import clip + # Create a list of parameters for each concept + incant_params = IncantStateParams() + incant_params.p = p + incant_params.prompt = p.prompt + incant_params.prompts = [pr for pr in p.prompts] + #incant_params.prompt_tokens = clip.tokenize(list(p.prompt), truncate=True).to(devices.device_interrogate) + incant_params.quality = quality + incant_params.delim = delim + incant_params.word = word + incant_params.deepbooru = deepbooru + fine = p.steps + incant_params.fine = p.steps + if incant_params.fine >= p.steps: + print(f"Fine step {fine} is greater than total steps {p.steps}, setting to {p.steps}") + fine = max(p.steps - 2, 1) + incant_params.gamma = gamma + incant_params.coarse = coarse_step + + incant_params.qual_scale = 0 + incant_params.sem_scale = 0 + incant_params.iteration = p.iteration + incant_params.get_conds_with_caching = p.get_conds_with_caching + incant_params.steps = p.steps + incant_params.batch_size = p.batch_size + incant_params.job = shared.state.job + #incant_params.first_stage_cache = self.stage_1[0] + incant_params.second_stage = (p.iteration % 2) == 1 + tqdm = shared.total_tqdm + if not hasattr(p, 'incant_params'): + setattr(p, 'incant_params', incant_params) + + if p.iteration % 2 == 0: + self.stage_1 = incant_params + else: + # assign old cache to next iteration + incant_params.first_stage_cache = self.stage_1 + + # Use lambda to call the callback function with the parameters to avoid global variables + y = lambda params: self.on_cfg_denoiser_callback(params, incant_params) + y2 = lambda params: self.cfg_after_cfg_callback(params, incant_params) + #y3 = lambda params: self.cfg_after_cfg_callback(params, incant_params) + + logger.debug('Hooked callbacks') + #script_callbacks.on_cfg_denoised(y2) + script_callbacks.on_cfg_denoiser(y) + script_callbacks.on_cfg_after_cfg(y2) + script_callbacks.on_script_unloaded(self.unhook_callbacks) + + def calc_masked_prompt(self, incant_params: IncantStateParams, first_stage_cache): + fs = first_stage_cache + repl_word = '-' + prompt = incant_params.p.prompt + repl_threshold = incant_params.gamma * 100.0 + word_list = fs.matches_fine[0] + masked_prompt = self.mask_prompt(repl_threshold, word_list, incant_params.p.prompt, incant_params.word) + return masked_prompt + + def calc_quality_guidance(self, incant_params: IncantStateParams): + incant_params.loss_qual = [] + for i, (grad_img, grad_txt) in enumerate(zip(incant_params.grad_img, incant_params.grad_txt)): + incant_params.loss_qual.append(grad_img * grad_txt) + + def loss_sem(self, incant_params: IncantStateParams): + p = incant_params.p + caption = incant_params.prompt + incant_params.loss_sem = [] + prompt_list = [caption] * p.batch_size + prompts = prompt_parser.SdConditioning(prompt_list, width=p.width, height=p.height) + c = incant_params.get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, prompts, p.steps, self.cached_c, p.extra_network_data) + masked_prompts = incant_params.masked_prompt + for i, (emb_fine, emb_coarse) in enumerate(zip(incant_params.emb_txt_fine, incant_params.emb_txt_coarse)): + incant_params.loss_sem.append(emb_fine - emb_coarse) + + def postprocess_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + return self.incant_postprocess_batch(p, *args, **kwargs) + + def incant_postprocess_batch(self, p: StableDiffusionProcessing, inc_active, *args, **kwargs): + inc_active = getattr(p, "incant_active", inc_active) + if inc_active is False: + return + batch_number = kwargs.get('batch_number', -1) + images = kwargs.get('images', None) + incant_params: IncantStateParams = getattr(p, "incant_params", None) + to_pil = ToPILImage() + + n = p.iteration + if n % 2 == 0: + #fine_images = self.decode_images(images) + fine_images = [] + for img in images: + img = to_pil(img) + incant_params.img_fine.append(img) + + self.interrogate_images(incant_params, p) + devices.torch_gc() + + # compute masked_prompts + batch_start_idx = n * p.batch_size + batch_end_idx = (n + 1) * p.batch_size + for batch_idx, caption_matches_item in enumerate(incant_params.matches_fine[batch_start_idx:batch_end_idx]): + batch_mask_prompts = [] + for caption, matches in caption_matches_item: + batch_mask_prompts.append(self.mask_prompt(incant_params.gamma, matches, caption, incant_params.word)) + incant_params.masked_prompt.append(batch_mask_prompts) + + self.unhook_callbacks() + + def unhook_callbacks(self): + logger.debug('Unhooked callbacks') + interrogator = self.interrogator(False) + interrogator.unload() + script_callbacks.remove_current_script_callbacks() + + def on_cfg_denoiser_callback(self, params: CFGDenoiserParams, incant_params: IncantStateParams): + second_stage = incant_params.second_stage + if not second_stage: + return + + fs: IncantStateParams = incant_params.first_stage_cache + + # TODO: handle batches + + p = incant_params.p + sampling_step = params.sampling_step + text_cond = params.text_cond + text_uncond = params.text_uncond + gamma = incant_params.gamma * 100.0 + + # temp bypass + if incant_params.qual_scale != 0: + # quality guidance + grad_img_batch = [] + grad_txt_batch = [] + loss_qual_batch = [] + for i in range(len(fs.emb_img_fine)): + #for i, (emb_fine, emb_coarse) in enumerate(zip(fs.emb_img_fine, fs.emb_img_coarse)): + img_fine = fs.emb_img_fine[i] + img_norm_fine = torch.norm(img_fine, dim=-1, keepdim=True) + img_coarse = fs.emb_img_coarse[i] + img_norm_coarse = torch.norm(img_coarse, dim=-1, keepdim=True) + grad_img = (img_fine / img_norm_fine**2) - (img_coarse / img_norm_coarse**2) + grad_img_batch.append(grad_img) + + # compute the text guidance + #for i, (emb_fine, emb_coarse) in enumerate(zip(fs.emb_txt_fine, fs.emb_txt_coarse)): + #for b in range(incant_params.batch_size): + + # not sure what to with batch, when is batch > 0? + b = 0 + txt_fine = fs.emb_txt_fine[i].batch[b][0].schedules[0].cond + txt_norm_fine = torch.norm(txt_fine, dim=-1, keepdim=True) + txt_coarse = fs.emb_txt_coarse[i].batch[b][0].schedules[0].cond + txt_norm_coarse = torch.norm(txt_coarse, dim=-1, keepdim=True) + grad_txt = (txt_fine / txt_norm_fine**2) - (txt_coarse / txt_norm_coarse**2) + grad_txt_batch.append(grad_txt) + + for i, (grad_img, grad_txt) in enumerate(zip(grad_img_batch, grad_txt_batch)): + loss_qual = grad_img * grad_txt + loss_qual_batch.append(loss_qual) + t = loss_qual * incant_params.qual_scale + print(f'\nloss_qual:{t.norm()}') + t = t.unsqueeze(0) + + ## TODO: scale t by hyperparameter (and use correct formula) + if t.shape[1] != text_cond.shape[1]: + empty = shared.sd_model.cond_stage_model_empty_prompt + num_repeats = (t.shape[1] - text_cond.shape[1]) // empty.shape[1] + + if num_repeats < 0: + t = pad_cond(t, -num_repeats, empty) + elif num_repeats > 0: + t = pad_cond(t, num_repeats, empty) + text_cond[i] += t.squeeze(0) + + def mask_prompt(self, gamma, word_list, prompt, word_repl = '-'): + # TODO: refactor out removing <> + regex = r"\b{0}\b" + masked_prompt = prompt + mask_less_similar = gamma > 0 + gamma = abs(gamma) + for word, pct in word_list: + word = word.strip(', ') + if len(word) == 0: + continue + condition = (pct < gamma) if mask_less_similar else (pct > gamma) + if condition: + repl_regex = regex.format(word) + # replace word with - + masked_prompt = re.sub(repl_regex, word_repl, masked_prompt) + is_lora = word.startswith('<') and word.endswith('>') + if is_lora: + masked_prompt = masked_prompt.replace(word, '') + # hack: remove text between pairs of brackets like <...> + masked_prompt = re.sub(r'<[^>]*>', '', masked_prompt) + + return masked_prompt + + def cfg_after_cfg_callback(self, params: AfterCFGCallbackParams, incant_params: IncantStateParams): + p = incant_params.p + coarse_step = incant_params.coarse + second_stage = incant_params.second_stage + x = params.x + + ## BUG: webui params passes shared.state.sampling_step instead of the internal self.step + # meaning that the sampling step is usually incorrect + step = params.sampling_step + + ## FIXME: why is the max value of step 2 less than the total steps??? + if step == coarse_step and not second_stage: + print(f"\nCoarse step: {step}\n") + coarse_img = self.decode_images(x) + incant_params.img_coarse.extend(coarse_img) + + def compute_gradients(self, emb_fine, emb_coarse): + out_gradients = [] + # zip together list and iterate + for i, (fine, coarse) in enumerate(zip(emb_fine, emb_coarse)): + # calculate norm of fine and coarse embeddings + norm_fine = torch.norm(fine, dim=-1, keepdim=True) + norm_fine **= 2 + norm_coarse = torch.norm(coarse, dim=-1, keepdim=True) + norm_coarse **= 2 + grad = (fine/norm_fine) - (coarse/norm_coarse) + out_gradients.append(grad) + return out_gradients + + def calculate_embedding_gradients(self, incant_params, p, current_step): + # text embeddings + captions_coarse = incant_params.caption_coarse + captions_fine = incant_params.caption_fine + # txt_emb_coarse = incant_params.emb_txt_coarse + # txt_emb_fine = incant_params.emb_txt_fine + # txt_emb_fine = [] + # txt_emb_coarse = [] + for i in range(len(captions_coarse)): + out = [] + incant_params.grad_txt.append(out) + # image embeddings + img_emb_coarse = incant_params.emb_img_coarse + img_emb_fine = incant_params.emb_img_fine + # incant_params.grad_img = self.compute_gradients(img_emb_fine, img_emb_coarse) + + def interrogate_images(self, incant_params, p): + interrogator = self.interrogator(incant_params.deepbooru) + interrogator.load() + + # fine features + for batch_idx, pil_image in enumerate(incant_params.img_fine): + # generate caption + caption = interrogator.generate_caption(pil_image) + devices.torch_gc() + + # CLIP + if not incant_params.deepbooru: + matches_list = [] + # append caption + caption = interrogator.generate_caption(pil_image) + incant_params.caption_fine.append(caption) + + # calculate image embeddings + image_features = self.calc_img_embedding(interrogator, pil_image) + incant_params.emb_img_fine.append(image_features) + + # calculate image similarity to prompt + prompt_text_array = incant_params.prompt.split() + matches = self.clip_text_image_similarity(interrogator, prompt_text_array, image_features, top_count=len(prompt_text_array)) + matches_list.append((incant_params.prompt, matches)) + #print(f"\n{batch_idx}-prompt:{caption}\n{batch_idx}-fine:{matches}\n") + + # calculate image similarity to generated caption + if incant_params.quality: + caption_text_array = caption.split() + # upcast + try: + matches = self.clip_text_image_similarity(interrogator, caption_text_array, image_features, top_count=len(caption_text_array)) + matches_list.append((caption, matches)) + except RuntimeError: + print(f"\n{batch_idx}-fine:error computing matches to generated caption\n") + matches_list.append((caption, [(caption, 1.0)])) + + + + print(f"\n{batch_idx}-fine:{matches}\n") + + incant_params.matches_fine.append(matches_list) + + # deepbooru interrogate + else: + matches_list = [] + # TODO: separate options to append generated caption and append masked original prompt + # for deepbooru, if disabled, append generated caption will not append the ORIGINAL prompt + # mask the original prompt + if incant_params.quality: + new_prompt, prompt_matches_list = self.interrogate_deepbooru(incant_params.prompt, incant_params.gamma) + matches_list.append((new_prompt, prompt_matches_list)) + print(f"{batch_idx}-prompt:{new_prompt}\n") + + new_caption, caption_matches_list = self.interrogate_deepbooru(caption, incant_params.gamma) + matches_list.append((new_caption, caption_matches_list)) + print(f"{batch_idx}-caption:{new_caption}\n") + + incant_params.caption_fine.append(new_caption) + #incant_params.caption_fine.append(new_prompt) + + # append auto generated captions + incant_params.matches_fine.append(matches_list) + + devices.torch_gc() + + def interrogate_deepbooru(self, caption, gamma): + """_summary_ + + Args: + caption (_type_): _description_ + matches_list (_type_): _description_ + gamma (_type_): _description_ + mask_less_similar (_type_): _description_ + + Returns: + _type_: _description_ + """ + + mask_less_similar = gamma > 0 + gamma = abs(gamma) + + matches_list = [] + # preprocess caption + + # remove lora + caption = re.sub(r'<[^>]*>', '', caption) + + matches = prompt_parser.parse_prompt_attention(caption) + if mask_less_similar: + matches = [(tag, strength) for (tag, strength) in matches if strength >= gamma] + else: + matches = [(tag, strength) for (tag, strength) in matches if strength < gamma] + # matches = [(tag, strength) for (tag, strength) in matches] + # split by tags + for tags, strength in matches: + for tag in tags.split(', '): + if len(tag) == 0: + continue + # filter tags + matches_list.append((tag.strip(), strength)) + + new_caption = '' + for tag, strength in matches_list: + new_caption += f'({tag}:{strength}), ' + new_caption.removesuffix(', ') + return new_caption, matches_list + + def clip_text_image_similarity(self, interrogator, text_array, image_features, top_count=1) -> list[tuple[str, float]]: + """ Calculate similarity between text and image features using CLIP + + Args: + interrogator (): shared.interrogator + text_array (str): text to match similarity + image_features (tensor): image encoded with calc_img_embedding + top_count (int, optional): number of top matches to return + + Returns: + list[tuple[str, float]]: _description_ + """ + with torch.no_grad(), devices.autocast(): + matches = interrogator.rank(image_features, text_array, top_count=top_count) + matches = [(tag, strength/100.0) for (tag, strength) in matches] # rescale to 0-1 + return matches + + def calc_img_embedding(self, interrogator, pil_image): + clip_image = interrogator.clip_preprocess(pil_image).unsqueeze(0).type(interrogator.dtype).to(devices.device_interrogate) + with torch.no_grad(), devices.autocast(): + # calculate image embeddings + image_features = interrogator.clip_model.encode_image(clip_image).type(interrogator.dtype) + image_features /= image_features.norm(dim=-1, keepdim=True) + return image_features + + def decode_images(self, x): + batch_images = [] + already_decoded = False + x_samples_ddim = decode_latent_batch(shared.sd_model, x, target_device=devices.cpu, check_for_nans=True) + for i, x_sample in enumerate(x_samples_ddim): + x_sample = x_sample.to(torch.float32) + x_sample = torch.clamp((x_sample + 1.0) / 2.0, min=0.0, max=1.0) + x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) + x_sample = x_sample.astype(np.uint8) + image = Image.fromarray(x_sample) + batch_images.append(image) + return batch_images + + def get_xyz_axis_options(self) -> dict: + xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ in ("xyz_grid.py", "scripts.xyz_grid")][0].module + extra_axis_options = { + xyz_grid.AxisOption("[Incant] Active", str, incant_apply_override('incant_active', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[Incant] Append Caption Prompt", str, incant_apply_override('incant_append_prompt', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[Incant] Deepbooru Interrogate", str, incant_apply_override('incant_deepbooru', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[Incant] Delimiter", str, incant_apply_field("incant_delim")), + xyz_grid.AxisOption("[Incant] Replacement Word", str, incant_apply_field("incant_word")), + xyz_grid.AxisOption("[Incant] Gamma", float, incant_apply_field("incant_gamma")) + } + return extra_axis_options + + +def run_fn_on_attr(p, attr_name, fn, *args): + """ Run a function on an attribute of a class if it exists """ + try: + attr = getattr(p, attr_name) + setattr(p, attr_name, fn(attr, *args)) + except AttributeError: + # No attribute exists + return + except TypeError: + # If the attribute is not iterable, return + return + +def duplicate_alternate_elements(input_list: list, batch_size = 1) -> list: + """ Duplicate each element in a list and return a new list + >>> duplicate_list([1, 2, 3, 4], 1) + [1, 1, 3, 3] + >>> duplicate_list([1, 2, 3, 4], 2) + [1, 2, 1, 2] + >>> duplicate_list([1, 2, 3, 4, 5, 6, 7, 8], 4) + [1, 2, 3, 4, 1, 2, 3, 4] + """ + result = [] + for i in range(0, len(input_list), batch_size*2): + batch = input_list[i:i + batch_size] + result.extend(batch) + result.extend(batch) + return result + +def duplicate_list(input_list: list) -> list: + """ Duplicate each element in a list and return a new list + >>> duplicate_list([1,2,3]) + [1, 1, 2, 2, 3, 3] + """ + return [element for item in input_list for element in (item, item)] + + +# XYZ Plot +# untested +# Based on @mcmonkey4eva's XYZ Plot implementation here: https://github.com/mcmonkeyprojects/sd-dynamic-thresholding/blob/master/scripts/dynamic_thresholding.py +def incant_apply_override(field, boolean: bool = False): + def fun(p, x, xs): + if boolean: + x = True if x.lower() == "true" else False + setattr(p, field, x) + return fun + +def incant_apply_field(field): + def fun(p, x, xs): + if not hasattr(p, "incant_active"): + setattr(p, "incant_active", True) + setattr(p, field, x) + + return fun + diff --git a/extensions/sd-webui-incantations/scripts/incant_utils/__pycache__/module_hooks.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/incant_utils/__pycache__/module_hooks.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e34dba569f988d83f713be7bd5d886924a7c51d Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/incant_utils/__pycache__/module_hooks.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/incant_utils/__pycache__/plot_tools.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/incant_utils/__pycache__/plot_tools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01b146a0a74e66b97d0c849eef15281f69c32165 Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/incant_utils/__pycache__/plot_tools.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/incant_utils/__pycache__/prompt_utils.cpython-310.pyc b/extensions/sd-webui-incantations/scripts/incant_utils/__pycache__/prompt_utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b5b88056508423993a9688934ede9f9a5784fa6 Binary files /dev/null and b/extensions/sd-webui-incantations/scripts/incant_utils/__pycache__/prompt_utils.cpython-310.pyc differ diff --git a/extensions/sd-webui-incantations/scripts/incant_utils/module_hooks.py b/extensions/sd-webui-incantations/scripts/incant_utils/module_hooks.py new file mode 100644 index 0000000000000000000000000000000000000000..a8506d8570e63f4caa8aa81852333fc7cfe0ac36 --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/incant_utils/module_hooks.py @@ -0,0 +1,162 @@ +from typing import Optional, Callable, Dict +from collections import OrderedDict +from warnings import warn +import logging +import torch + + +from modules import shared + + +logger = logging.getLogger(__name__) + + +def modules_add_field(modules, field, value=None): + """ Add a field to a module if it isn't already added. + Args: + modules (list): Module or list of modules to add the field to + field (str): Field name to add + value (any): Value to assign to the field + Returns: + None + + """ + if not isinstance(modules, list): + modules = [modules] + for module in modules: + if not hasattr(module, field): + setattr(module, field, value) + else: + logger.warning(f"Field {field} already exists in module {module}") + + +def modules_remove_field(modules, field): + """ Remove a field from a module if it exists. + Args: + modules (list): Module or list of modules to add the field to + field (str): Field name to add + value (any): Value to assign to the field + Returns: + None + + """ + if not isinstance(modules, list): + modules = [modules] + for module in modules: + if hasattr(module, field): + delattr(module, field) + else: + # logger.warning(f"Field {field} does not exist in module {module}") + pass + + +def get_modules(network_layer_name_filter: Optional[str] = None, module_name_filter: Optional[str] = None): + """ Get all modules from the shared.sd_model that match the filters provided. If no filters are provided, all modules are returned. + + Args: + network_layer_name_filter (Optional[str], optional): Filters the modules by network layer name. Defaults to None. Example: "attn1" will return all modules that have "attn1" in their network layer name. + module_name_filter (Optional[str], optional): Filters the modules by module class name. Defaults to None. Example: "CrossAttention" will return all modules that have "CrossAttention" in their class name. + + Returns: + list: List of modules that match the filters provided. + """ + try: + m = shared.sd_model + nlm = m.network_layer_mapping + sd_model_modules = nlm.values() + + # Apply filters if they are provided + if network_layer_name_filter is not None: + sd_model_modules = list(filter(lambda m: network_layer_name_filter in m.network_layer_name, sd_model_modules)) + if module_name_filter is not None: + sd_model_modules = list(filter(lambda m: module_name_filter in m.__class__.__name__, sd_model_modules)) + return sd_model_modules + except AttributeError: + logger.exception("AttributeError in get_modules", stack_info=True) + return [] + except Exception: + logger.exception("Exception in get_modules", stack_info=True) + return [] + + +# workaround for torch remove hooks issue +# thank you to @ProGamerGov for this https://github.com/pytorch/pytorch/issues/70455 +def remove_module_forward_hook( + module: torch.nn.Module, hook_fn_name: Optional[str] = None +) -> None: + """ + This function removes all forward hooks in the specified module, without requiring + any hook handles. This lets us clean up & remove any hooks that weren't property + deleted. + + Warning: Various PyTorch modules and systems make use of hooks, and thus extreme + caution should be exercised when removing all hooks. Users are recommended to give + their hook function a unique name that can be used to safely identify and remove + the target forward hooks. + + Args: + + module (nn.Module): The module instance to remove forward hooks from. + hook_fn_name (str, optional): Optionally only remove specific forward hooks + based on their function's __name__ attribute. + Default: None + """ + + if hook_fn_name is None: + warn("Removing all active hooks can break some PyTorch modules & systems.") + + def _remove_hooks(m: torch.nn.Module, name: Optional[str] = None) -> None: + if hasattr(module, "_forward_hooks"): + if m._forward_hooks != OrderedDict(): + if name is not None: + dict_items = list(m._forward_hooks.items()) + m._forward_hooks = OrderedDict( + [(i, fn) for i, fn in dict_items if fn.__name__ != name] + ) + else: + m._forward_hooks: Dict[int, Callable] = OrderedDict() + + def _remove_child_hooks( + target_module: torch.nn.Module, hook_name: Optional[str] = None + ) -> None: + for name, child in target_module._modules.items(): + if child is not None: + _remove_hooks(child, hook_name) + _remove_child_hooks(child, hook_name) + + # Remove hooks from target submodules + _remove_child_hooks(module, hook_fn_name) + + # Remove hooks from the target module + _remove_hooks(module, hook_fn_name) + + +def module_add_forward_hook(module, hook_fn, hook_type="forward", with_kwargs=False): + """ Adds a forward hook to a module. + + hook_fn should be a function that accepts the following arguments: + forward hook, no kwargs: hook(module, args, output) -> None or modified output + forward hook, with kwargs: hook(module, args, kwargs output) -> None or modified output + + Args: + module (torch.nn.Module): Module to hook + hook_fn (Callable): Function to call when the hook is triggered + hook_type (str, optional): Type of hook to create. Defaults to "forward". Can be "forward" or "pre_forward". + with_kwargs (bool, optional): Whether the hook function should accept keyword arguments. Defaults to False. + + Returns: + torch.utils.hooks.RemovableHandle: Handle for the hook + """ + if module is None: + raise ValueError("module must be provided") + if not callable(hook_fn): + raise ValueError("hook_fn must be a callable function") + + if hook_type == "forward": + handle = module.register_forward_hook(hook_fn, with_kwargs=with_kwargs) + elif hook_type == "pre_forward": + handle = module.register_forward_pre_hook(hook_fn, with_kwargs=with_kwargs) + else: + raise ValueError(f"Invalid hook type {hook_type}. Must be 'forward' or 'pre_forward'.") + + return handle \ No newline at end of file diff --git a/extensions/sd-webui-incantations/scripts/incant_utils/plot_tools.py b/extensions/sd-webui-incantations/scripts/incant_utils/plot_tools.py new file mode 100644 index 0000000000000000000000000000000000000000..58ee47e974f2506a63747d424aa69b2f001bd735 --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/incant_utils/plot_tools.py @@ -0,0 +1,47 @@ +import torch +import matplotlib.pyplot as plt + +def plot_attention_map(attention_map: torch.Tensor, title, x_label="X", y_label="Y", save_path=None, plot_type="default"): + """ Plots an attention map using matplotlib.pyplot + Arguments: + attention_map: Tensor - The attention map to plot. Shape: (H, W) + title: str - The title of the plot + x_label: str (optional) - The x-axis label + y_label: str (optional) - The y-axis label + save_path: str (optional) - The path to save the plot + plot_type: str (optional) - The type of plot to create. Options: 'default', 'num', or any matplotlib colormap name. https://matplotlib.org/stable/gallery/color/colormap_reference.html + Returns: + None + """ + + # Convert attention map to numpy array + attention_map = attention_map.detach().cpu().numpy() + + # Create figure and axis + fig, ax = plt.subplots() + + cmap_name = 'viridis' + match plot_type: + case 'default': + cmap_name = 'viridis' + case 'num': + cmap_name = 'tab20c' + case _: + cmap_name = plot_type + # Plot the attention map + ax.imshow(attention_map, cmap=cmap_name, interpolation='nearest') + if plot_type == 'num': + elements = list(set(attention_map.flatten())) + labels = [f"{x}" for x in elements] + fig.legend(elements, labels, loc='lower left') + + # Set title and labels + ax.set_title(title) + ax.set_xlabel(x_label) + ax.set_ylabel(y_label) + + # Save the plot if save_path is provided + if save_path: + plt.savefig(save_path) + + plt.close(fig) \ No newline at end of file diff --git a/extensions/sd-webui-incantations/scripts/incant_utils/prompt_utils.py b/extensions/sd-webui-incantations/scripts/incant_utils/prompt_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..b69f89ff37e85d28beda2f7c6c1c596efc6facaa --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/incant_utils/prompt_utils.py @@ -0,0 +1,70 @@ +from functools import reduce +from modules import shared +from modules import extra_networks +from modules import prompt_parser +from modules import sd_hijack + +# taken from modules/ui.py +# https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/master/modules/ui.py +def get_token_count(text, steps, is_positive: bool = True, return_tokens = False): + """ Get token count and max length for a given prompt text. If return_tokens is True, return the tokens as well. + Returns: + token_count: int - The total number of tokens in the prompt text + max_length: int - The maximum length of the prompt text + """ + try: + text, _ = extra_networks.parse_prompt(text) + + if is_positive: + _, prompt_flat_list, _ = prompt_parser.get_multicond_prompt_list([text]) + else: + prompt_flat_list = [text] + + prompt_schedules = prompt_parser.get_learned_conditioning_prompt_schedules(prompt_flat_list, steps) + + except Exception: + # a parsing error can happen here during typing, and we don't want to bother the user with + # messages related to it in console + prompt_schedules = [[[steps, text]]] + + flat_prompts = reduce(lambda list1, list2: list1+list2, prompt_schedules) + prompts = [prompt_text for step, prompt_text in flat_prompts] + + token_count, max_length = max([sd_hijack.model_hijack.get_prompt_lengths(prompt) for prompt in prompts], key=lambda args: args[0]) + return token_count, max_length + + +def tokenize_prompt(text): + """ Tokenize the given prompt text using the current clip model. + Arguments: + text: str - The prompt text to tokenize + + Returns: + tokens: list[int] - If return_tokens is True, return the tokenized prompt as well + + """ + if isinstance(text, str): + prompts = [text] + + clip = getattr(sd_hijack.model_hijack, 'clip', None) + if clip is None: + return None, None + batch_chunks, token_count = clip.process_texts(prompts) + return batch_chunks, token_count + + +def decode_tokenized_prompt(tokens): + """ Decode the given tokenized prompt using the current clip model. + Arguments: + tokens: list[int] - The tokenized prompt to decode + Returns: + a list of tuples containing the token index, token, and decoded token + + """ + clip = getattr(sd_hijack.model_hijack, 'clip', None) + if clip is None: + return None + decoded_prompt = [ + [token_idx, token, clip.tokenizer.decoder[token]] for token_idx, token in enumerate(tokens) + ] + return decoded_prompt \ No newline at end of file diff --git a/extensions/sd-webui-incantations/scripts/incantation_base.py b/extensions/sd-webui-incantations/scripts/incantation_base.py new file mode 100644 index 0000000000000000000000000000000000000000..e13e4b9cf2ae9599f8d166da47ee3b4f66c50d79 --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/incantation_base.py @@ -0,0 +1,145 @@ +import logging +from os import environ +import modules.scripts as scripts +import gradio as gr +from dataclasses import dataclass +from typing import Any + +from modules import script_callbacks +from modules.processing import StableDiffusionProcessing +from scripts.ui_wrapper import UIWrapper +from scripts.incant import IncantExtensionScript +from scripts.t2i_zero import T2I0ExtensionScript +from scripts.scfg import SCFGExtensionScript +from scripts.pag import PAGExtensionScript +from scripts.save_attn_maps import SaveAttentionMapsScript +from scripts.cfg_combiner import CFGCombinerScript +from scripts.smoothed_energy_guidance import SEGExtensionScript + +logger = logging.getLogger(__name__) +logger.setLevel(environ.get("SD_WEBUI_LOG_LEVEL", logging.INFO)) + + +""" + +Author: v0xie +GitHub URL: https://github.com/v0xie/sd-webui-incantations + +""" +class SubmoduleInfo: + def __init__(self, module: UIWrapper, module_idx = 0, num_args = -1, arg_idx = -1): + self.module: UIWrapper = module + self.module_idx: int = num_args # the length of arg list + self.num_args: int = num_args # the length of arg list + self.arg_idx: int = arg_idx # where the list of args starts + +# main scripts +submodules: list[SubmoduleInfo] = [ + SubmoduleInfo(module=SEGExtensionScript()), + SubmoduleInfo(module=SCFGExtensionScript()), + SubmoduleInfo(module=PAGExtensionScript()), + SubmoduleInfo(module=T2I0ExtensionScript()), + SubmoduleInfo(module=IncantExtensionScript()), +] +# debug scripts +if environ.get("INCANT_DEBUG", default=False) != False: + submodules.append(SubmoduleInfo(module=SaveAttentionMapsScript())) +else: + logger.info("Incantation: Debug scripts are disabled. Set INCANT_DEBUG environment variable to enable them.") +# run these after submodules +end_submodules: list[SubmoduleInfo] = [ + SubmoduleInfo(module=CFGCombinerScript()) +] +submodules = submodules + end_submodules + + +class IncantBaseExtensionScript(scripts.Script): + def __init__(self): + pass + + # Extension title in menu UI + def title(self): + return "Incantations" + + # Decide to show menu in txt2img or img2img + def show(self, is_img2img): + return scripts.AlwaysVisible + + # Setup menu ui detail + def ui(self, is_img2img): + # setup UI + out = [] + with gr.Accordion('Incantations', open=False): + for idx, module_info in enumerate(submodules): + module_info.module_idx = idx + module = module_info.module + module_param_list = module.setup_ui(is_img2img) + module_info.num_args = len(module_param_list) + if module_info.num_args > 0: + arg_idx = max(len(out), 0) + module_info.arg_idx = arg_idx + out.extend(module_param_list) + # setup fields + self.infotext_fields = [] + self.paste_field_names = [] + for module_info in submodules: + module = module_info.module + self.infotext_fields.extend(module.get_infotext_fields()) + self.paste_field_names.extend(module.get_paste_field_names()) + return out + + def before_process(self, p: StableDiffusionProcessing, *args, **kwargs): + for m in submodules: + m.module.before_process(p, *self.m_args(m, *args), **kwargs) + + def process(self, p: StableDiffusionProcessing, *args, **kwargs): + for m in submodules: + m.module.process(p, *self.m_args(m, *args), **kwargs) + + def before_process_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + for m in submodules: + m.module.before_process_batch(p, *self.m_args(m, *args), **kwargs) + + def process_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + for m in submodules: + m.module.process_batch(p, *self.m_args(m, *args), **kwargs) + + def postprocess_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + for m in submodules: + m.module.postprocess_batch(p, *self.m_args(m, *args), **kwargs) + + def unhook_callbacks(self): + for m in submodules: + m.module.unhook_callbacks() + script_callbacks.remove_current_script_callbacks() + + def m_args(self, module: SubmoduleInfo, *args): + return args[module.arg_idx:module.arg_idx + module.num_args] + + +# XYZ Plot +# Based on @mcmonkey4eva's XYZ Plot implementation here: https://github.com/mcmonkeyprojects/sd-dynamic-thresholding/blob/master/scripts/dynamic_thresholding.py +def make_axis_options(extra_axis_options): + xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ in ("xyz_grid.py", "scripts.xyz_grid")][0].module + current_opts = [x.label for x in xyz_grid.axis_options] + # TODO: + for opt in extra_axis_options: + if opt.label in current_opts: + return + xyz_grid.axis_options.extend(extra_axis_options) + + +def callback_before_ui(): + try: + for module_info in submodules: + module = module_info.module + try: + extra_axis_options = module.get_xyz_axis_options() + except NotImplementedError: + logger.warning(f"Module {module.title()} does not implement get_xyz_axis_options") + extra_axis_options = {} + make_axis_options(extra_axis_options) + except: + logger.exception("Incantation: Error while making axis options") + +script_callbacks.on_before_ui(callback_before_ui) diff --git a/extensions/sd-webui-incantations/scripts/pag.py b/extensions/sd-webui-incantations/scripts/pag.py new file mode 100644 index 0000000000000000000000000000000000000000..c34ee7d0b8a33cf3905a7f28f0558d2272cd7130 --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/pag.py @@ -0,0 +1,891 @@ +import logging +from os import environ +import modules.scripts as scripts +import gradio as gr +import scipy.stats as stats + +from scripts.ui_wrapper import UIWrapper, arg +from modules import script_callbacks, patches +from modules.hypernetworks import hypernetwork +#import modules.sd_hijack_optimizations +from modules.script_callbacks import CFGDenoiserParams, CFGDenoisedParams, AfterCFGCallbackParams +from modules.prompt_parser import reconstruct_multicond_batch +from modules.processing import StableDiffusionProcessing +#from modules.shared import sd_model, opts +from modules.sd_samplers_cfg_denoiser import catenate_conds +from modules.sd_samplers_cfg_denoiser import CFGDenoiser +from modules import shared + +import math +import torch +from torch.nn import functional as F +from torchvision.transforms import GaussianBlur + +from warnings import warn +from typing import Callable, Dict, Optional +from collections import OrderedDict +import torch + +logger = logging.getLogger(__name__) +logger.setLevel(environ.get("SD_WEBUI_LOG_LEVEL", logging.INFO)) + +incantations_debug = environ.get("INCANTAIONS_DEBUG", False) + +""" +An unofficial implementation of "Self-Rectifying Diffusion Sampling with Perturbed-Attention Guidance" for Automatic1111 WebUI. + +@misc{ahn2024selfrectifying, + title={Self-Rectifying Diffusion Sampling with Perturbed-Attention Guidance}, + author={Donghoon Ahn and Hyoungwon Cho and Jaewon Min and Wooseok Jang and Jungwoo Kim and SeonHwa Kim and Hyun Hee Park and Kyong Hwan Jin and Seungryong Kim}, + year={2024}, + eprint={2403.17377}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} + +Include noise interval for CFG and PAG guidance in the sampling process from "Applying Guidance in a Limited Interval Improves +Sample and Distribution Quality in Diffusion Models" + +@misc{kynkäänniemi2024applying, + title={Applying Guidance in a Limited Interval Improves Sample and Distribution Quality in Diffusion Models}, + author={Tuomas Kynkäänniemi and Miika Aittala and Tero Karras and Samuli Laine and Timo Aila and Jaakko Lehtinen}, + year={2024}, + eprint={2404.07724}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} + +Include CFG schedulers from "Analysis of Classifier-Free Guidance Weight Schedulers" + +@misc{wang2024analysis, + title={Analysis of Classifier-Free Guidance Weight Schedulers}, + author={Xi Wang and Nicolas Dufour and Nefeli Andreou and Marie-Paule Cani and Victoria Fernandez Abrevaya and David Picard and Vicky Kalogeiton}, + year={2024}, + eprint={2404.13040}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} + +Saliency-adaptive noise fusion from arXiv:2311.10329 "High-fidelity Person-centric Subject-to-Image Synthesis" +@misc{wang2024highfidelity, + title={High-fidelity Person-centric Subject-to-Image Synthesis}, + author={Yibin Wang and Weizhong Zhang and Jianwei Zheng and Cheng Jin}, + year={2024}, + eprint={2311.10329}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} + +Author: v0xie +GitHub URL: https://github.com/v0xie/sd-webui-incantations + +""" + + +handles = [] +global_scale = 1 + +SCHEDULES = [ + 'Constant', + 'Clamp-Linear (c=4.0)', + 'Clamp-Linear (c=2.0)', + 'Clamp-Linear (c=1.0)', + 'Linear', + 'Inverse-Linear', + 'Cosine', + 'Clamp-Cosine (c=4.0)', + 'Clamp-Cosine (c=2.0)', + 'Clamp-Cosine (c=1.0)', + 'Sine', + 'Interval', + 'PCS (s=0.01)', + 'PCS (s=0.1)', + 'PCS (s=1.0)', + 'PCS (s=2.0)', + 'PCS (s=4.0)', +] + + +class PAGStateParams: + def __init__(self): + self.pag_active: bool = False # PAG guidance scale + self.pag_sanf: bool = False # saliency-adaptive noise fusion, handled in cfg_combiner + self.pag_scale: int = -1 # PAG guidance scale + self.pag_start_step: int = 0 + self.pag_end_step: int = 150 + self.cfg_interval_enable: bool = False + self.cfg_interval_schedule: str = 'Constant' + self.cfg_interval_low: float = 0 + self.cfg_interval_high: float = 50.0 + self.cfg_interval_scheduled_value: float = 7.0 + self.step : int = 0 + self.max_sampling_step : int = 1 + self.guidance_scale: int = -1 # CFG + self.current_noise_level: float = 100.0 + self.x_in = None + self.text_cond = None + self.image_cond = None + self.sigma = None + self.text_uncond = None + self.make_condition_dict = None # callable lambda + self.crossattn_modules = [] # callable lambda + self.to_v_modules = [] + self.to_out_modules = [] + self.pag_x_out = None + self.batch_size = -1 # Batch size + self.denoiser = None # CFGDenoiser + self.patched_combine_denoised = None + self.conds_list = None + self.uncond_shape_0 = None + + +class PAGExtensionScript(UIWrapper): + def __init__(self): + self.cached_c = [None, None] + self.handles = [] + + # Extension title in menu UI + def title(self) -> str: + return "Perturbed Attention Guidance" + + # Decide to show menu in txt2img or img2img + def show(self, is_img2img): + return scripts.AlwaysVisible + + # Setup menu ui detail + def setup_ui(self, is_img2img) -> list: + with gr.Accordion('Perturbed Attention Guidance', open=False): + active = gr.Checkbox(value=False, default=False, label="Active", elem_id='pag_active') + pag_sanf = gr.Checkbox(value=False, default=False, label="Use Saliency-Adaptive Noise Fusion", elem_id='pag_sanf') + with gr.Row(): + pag_scale = gr.Slider(value = 0, minimum = 0, maximum = 20.0, step = 0.5, label="PAG Scale", elem_id = 'pag_scale', info="") + with gr.Row(): + start_step = gr.Slider(value = 0, minimum = 0, maximum = 150, step = 1, label="Start Step", elem_id = 'pag_start_step', info="") + end_step = gr.Slider(value = 150, minimum = 0, maximum = 150, step = 1, label="End Step", elem_id = 'pag_end_step', info="") + + with gr.Accordion('CFG Scheduler', open=False): + cfg_interval_enable = gr.Checkbox(value=False, default=False, label="Enable CFG Scheduler", elem_id='cfg_interval_enable', info="If enabled, applies CFG only within noise interval with the selected schedule type. PAG must be enabled (scale can be 0). SDXL recommend CFG=15; CFG interval (0.28, 5.42]") + with gr.Row(): + cfg_schedule = gr.Dropdown( + value='Constant', + choices= SCHEDULES, + label="CFG Schedule Type", + elem_id='cfg_interval_schedule', + ) + cfg_interval_low = gr.Slider(value = 0, minimum = 0, maximum = 100, step = 0.1, label="CFG Noise Interval Low", elem_id = 'cfg_interval_low', info="") + cfg_interval_high = gr.Slider(value = 100, minimum = 0, maximum = 100, step = 0.1, label="CFG Noise Interval High", elem_id = 'cfg_interval_high', info="") + + active.do_not_save_to_config = True + pag_sanf.do_not_save_to_config = True + pag_scale.do_not_save_to_config = True + start_step.do_not_save_to_config = True + end_step.do_not_save_to_config = True + cfg_interval_enable.do_not_save_to_config = True + cfg_schedule.do_not_save_to_config = True + cfg_interval_low.do_not_save_to_config = True + cfg_interval_high.do_not_save_to_config = True + self.infotext_fields = [ + (active, lambda d: gr.Checkbox.update(value='PAG Active' in d)), + (pag_sanf, lambda d: gr.Checkbox.update(value='PAG SANF' in d)), + (pag_scale, 'PAG Scale'), + (start_step, 'PAG Start Step'), + (end_step, 'PAG End Step'), + (cfg_interval_enable, 'CFG Interval Enable'), + (cfg_schedule, 'CFG Interval Schedule'), + (cfg_interval_low, 'CFG Interval Low'), + (cfg_interval_high, 'CFG Interval High') + ] + self.paste_field_names = [ + 'pag_active', + 'pag_sanf', + 'pag_scale', + 'pag_start_step', + 'pag_end_step', + 'cfg_interval_enable', + 'cfg_interval_schedule', + 'cfg_interval_low', + 'cfg_interval_high', + ] + return [active, pag_scale, start_step, end_step, cfg_interval_enable, cfg_schedule, cfg_interval_low, cfg_interval_high, pag_sanf] + + def process_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + self.pag_process_batch(p, *args, **kwargs) + + def pag_process_batch(self, p: StableDiffusionProcessing, active, pag_scale, start_step, end_step, cfg_interval_enable, cfg_schedule, cfg_interval_low, cfg_interval_high, pag_sanf, *args, **kwargs): + # cleanup previous hooks always + script_callbacks.remove_current_script_callbacks() + self.remove_all_hooks() + + active = getattr(p, "pag_active", active) + pag_sanf = getattr(p, "pag_sanf", pag_sanf) + cfg_interval_enable = getattr(p, "cfg_interval_enable", cfg_interval_enable) + if active is False and cfg_interval_enable is False: + return + pag_scale = getattr(p, "pag_scale", pag_scale) + start_step = getattr(p, "pag_start_step", start_step) + end_step = getattr(p, "pag_end_step", end_step) + + cfg_schedule = getattr(p, "cfg_interval_schedule", cfg_schedule) + cfg_interval_low = getattr(p, "cfg_interval_low", cfg_interval_low) + cfg_interval_high = getattr(p, "cfg_interval_high", cfg_interval_high) + + if active: + p.extra_generation_params.update({ + "PAG Active": active, + "PAG SANF": pag_sanf, + "PAG Scale": pag_scale, + "PAG Start Step": start_step, + "PAG End Step": end_step, + }) + if cfg_interval_enable: + p.extra_generation_params.update({ + "CFG Interval Enable": cfg_interval_enable, + "CFG Interval Schedule": cfg_schedule, + "CFG Interval Low": cfg_interval_low, + "CFG Interval High": cfg_interval_high + }) + self.create_hook(p, active, pag_scale, start_step, end_step, cfg_interval_enable, cfg_schedule, cfg_interval_low, cfg_interval_high, pag_sanf) + + def create_hook(self, p: StableDiffusionProcessing, active, pag_scale, start_step, end_step, cfg_interval_enable, cfg_schedule, cfg_interval_low, cfg_interval_high, pag_sanf, *args, **kwargs): + # Create a list of parameters for each concept + pag_params = PAGStateParams() + + # Add to p's incant_cfg_params + if not hasattr(p, 'incant_cfg_params'): + logger.error("No incant_cfg_params found in p") + p.incant_cfg_params['pag_params'] = pag_params + + pag_params.pag_active = active + pag_params.pag_sanf = pag_sanf + pag_params.pag_scale = pag_scale + pag_params.pag_start_step = start_step + pag_params.pag_end_step = end_step + pag_params.cfg_interval_enable = cfg_interval_enable + pag_params.cfg_interval_schedule = cfg_schedule + pag_params.max_sampling_step = p.steps + pag_params.guidance_scale = p.cfg_scale + pag_params.batch_size = p.batch_size + pag_params.denoiser = None + pag_params.cfg_interval_scheduled_value = p.cfg_scale + + if pag_params.cfg_interval_enable: + # Refer to 3.1 Practice in the paper + # We want to round high and low noise levels to the nearest integer index + low_index = find_closest_index(cfg_interval_low, pag_params.max_sampling_step) + high_index = find_closest_index(cfg_interval_high, pag_params.max_sampling_step) + pag_params.cfg_interval_low = calculate_noise_level(low_index, pag_params.max_sampling_step) + pag_params.cfg_interval_high = calculate_noise_level(high_index, pag_params.max_sampling_step) + logger.debug(f"Step Aligned CFG Interval (low, high): ({low_index}, {high_index}), Step Aligned CFG Interval: ({round(pag_params.cfg_interval_low, 4)}, {round(pag_params.cfg_interval_high, 4)})") + + # Get all the qv modules + cross_attn_modules = self.get_cross_attn_modules() + if len(cross_attn_modules) == 0: + logger.error("No cross attention modules found, cannot proceed with PAG") + return + pag_params.crossattn_modules = [m for m in cross_attn_modules if 'CrossAttention' in m.__class__.__name__] + + # Use lambda to call the callback function with the parameters to avoid global variables + cfg_denoise_lambda = lambda callback_params: self.on_cfg_denoiser_callback(callback_params, pag_params) + cfg_denoised_lambda = lambda callback_params: self.on_cfg_denoised_callback(callback_params, pag_params) + #after_cfg_lambda = lambda x: self.cfg_after_cfg_callback(x, params) + unhook_lambda = lambda _: self.unhook_callbacks(pag_params) + + if pag_params.pag_active: + self.ready_hijack_forward(pag_params.crossattn_modules, pag_scale) + + logger.debug('Hooked callbacks') + script_callbacks.on_cfg_denoiser(cfg_denoise_lambda) + script_callbacks.on_cfg_denoised(cfg_denoised_lambda) + #script_callbacks.on_cfg_after_cfg(after_cfg_lambda) + script_callbacks.on_script_unloaded(unhook_lambda) + + + + def postprocess_batch(self, p, *args, **kwargs): + self.pag_postprocess_batch(p, *args, **kwargs) + + def pag_postprocess_batch(self, p, active, *args, **kwargs): + script_callbacks.remove_current_script_callbacks() + + logger.debug('Removed script callbacks') + active = getattr(p, "pag_active", active) + if active is False: + return + + def remove_all_hooks(self): + cross_attn_modules = self.get_cross_attn_modules() + for module in cross_attn_modules: + to_v = getattr(module, 'to_v', None) + self.remove_field_cross_attn_modules(module, 'pag_enable') + self.remove_field_cross_attn_modules(module, 'pag_last_to_v') + self.remove_field_cross_attn_modules(to_v, 'pag_parent_module') + _remove_all_forward_hooks(module, 'pag_pre_hook') + _remove_all_forward_hooks(to_v, 'to_v_pre_hook') + + def unhook_callbacks(self, pag_params: PAGStateParams): + global handles + return + + if pag_params is None: + logger.error("PAG params is None") + return + + if pag_params.denoiser is not None: + denoiser = pag_params.denoiser + setattr(denoiser, 'combine_denoised_patched', False) + try: + patches.undo(__name__, denoiser, "combine_denoised") + except KeyError: + logger.exception("KeyError unhooking combine_denoised") + pass + except RuntimeError: + logger.exception("RuntimeError unhooking combine_denoised") + pass + pag_params.denoiser = None + + + def ready_hijack_forward(self, crossattn_modules, pag_scale): + """ Create hooks in the forward pass of the cross attention modules + Copies the output of the to_v module to the parent module + Then applies the PAG perturbation to the output of the cross attention module (multiplication by identity) + """ + + # add field for last_to_v + for module in crossattn_modules: + to_v = getattr(module, 'to_v', None) + self.add_field_cross_attn_modules(module, 'pag_enable', False) + self.add_field_cross_attn_modules(module, 'pag_last_to_v', None) + self.add_field_cross_attn_modules(to_v, 'pag_parent_module', [module]) + # self.add_field_cross_attn_modules(to_out, 'pag_parent_module', [module]) + + def to_v_pre_hook(module, input, kwargs, output): + """ Copy the output of the to_v module to the parent module """ + parent_module = getattr(module, 'pag_parent_module', None) + # copy the output of the to_v module to the parent module + setattr(parent_module[0], 'pag_last_to_v', output.detach().clone()) + + def pag_pre_hook(module, input, kwargs, output): + if hasattr(module, 'pag_enable') and getattr(module, 'pag_enable', False) is False: + return + if not hasattr(module, 'pag_last_to_v'): + # oops we forgot to unhook + return + + # get the last to_v output and save it + last_to_v = getattr(module, 'pag_last_to_v', None) + + batch_size, seq_len, inner_dim = output.shape + identity = torch.eye(seq_len, dtype=last_to_v.dtype, device=shared.device).expand(batch_size, -1, -1) + if last_to_v is not None: + new_output = torch.einsum('bij,bjk->bik', identity, last_to_v[:, :seq_len, :]) + return new_output + else: + # this is bad + return output + + # Create hooks + for module in crossattn_modules: + handle_parent = module.register_forward_hook(pag_pre_hook, with_kwargs=True) + to_v = getattr(module, 'to_v', None) + handle_to_v = to_v.register_forward_hook(to_v_pre_hook, with_kwargs=True) + + def get_middle_block_modules(self): + """ Get all attention modules from the middle block + Refere to page 22 of the PAG paper, Appendix A.2 + + """ + try: + m = shared.sd_model + nlm = m.network_layer_mapping + middle_block_modules = [m for m in nlm.values() if 'middle_block_1_transformer_blocks_0_attn1' in m.network_layer_name and 'CrossAttention' in m.__class__.__name__] + return middle_block_modules + except AttributeError: + logger.exception("AttributeError in get_middle_block_modules", stack_info=True) + return [] + except Exception: + logger.exception("Exception in get_middle_block_modules", stack_info=True) + return [] + + def get_cross_attn_modules(self): + """ Get all cross attention modules """ + return self.get_middle_block_modules() + + def add_field_cross_attn_modules(self, module, field, value): + """ Add a field to a module if it doesn't exist """ + if not hasattr(module, field): + setattr(module, field, value) + + def remove_field_cross_attn_modules(self, module, field): + """ Remove a field from a module if it exists """ + if hasattr(module, field): + delattr(module, field) + + def on_cfg_denoiser_callback(self, params: CFGDenoiserParams, pag_params: PAGStateParams): + # always unhook + self.unhook_callbacks(pag_params) + + pag_params.step = params.sampling_step + + # CFG Interval + # TODO: set rho based on sdxl or sd1.5 + pag_params.current_noise_level = calculate_noise_level( + i = pag_params.step, + N = pag_params.max_sampling_step, + ) + + if pag_params.cfg_interval_enable: + if pag_params.cfg_interval_schedule != 'Constant': + # Calculate noise interval + start = pag_params.cfg_interval_low + end = pag_params.cfg_interval_high + begin_range = start if start <= end else end + end_range = end if start <= end else start + # Scheduled CFG Value + scheduled_cfg_scale = cfg_scheduler(pag_params.cfg_interval_schedule, pag_params.step, pag_params.max_sampling_step, pag_params.guidance_scale) + + pag_params.cfg_interval_scheduled_value = scheduled_cfg_scale if begin_range <= pag_params.current_noise_level <= end_range else 1.0 + + # Run PAG only if active and within interval + if not pag_params.pag_active or pag_params.pag_scale <= 0: + return + if not pag_params.pag_start_step <= params.sampling_step <= pag_params.pag_end_step or pag_params.pag_scale <= 0: + return + + if isinstance(params.text_cond, dict): + text_cond = params.text_cond['crossattn'] # SD XL + pag_params.text_cond = {} + pag_params.text_uncond = {} + for key, value in params.text_cond.items(): + pag_params.text_cond[key] = value.clone().detach() + pag_params.text_uncond[key] = value.clone().detach() + else: + text_cond = params.text_cond # SD 1.5 + pag_params.text_cond = text_cond.clone().detach() + pag_params.text_uncond = text_cond.clone().detach() + + pag_params.x_in = params.x.clone().detach() + pag_params.sigma = params.sigma.clone().detach() + pag_params.image_cond = params.image_cond.clone().detach() + pag_params.denoiser = params.denoiser + pag_params.make_condition_dict = get_make_condition_dict_fn(params.text_uncond) + + + def on_cfg_denoised_callback(self, params: CFGDenoisedParams, pag_params: PAGStateParams): + """ Callback function for the CFGDenoisedParams + Refer to pg.22 A.2 of the PAG paper for how CFG and PAG combine + + """ + # Run only within interval + # Run PAG only if active and within interval + if not pag_params.pag_active or pag_params.pag_scale <= 0: + return + if not pag_params.pag_start_step <= params.sampling_step <= pag_params.pag_end_step or pag_params.pag_scale <= 0: + return + + # passed from on_cfg_denoiser_callback + x_in = pag_params.x_in + tensor = pag_params.text_cond + uncond = pag_params.text_uncond + image_cond_in = pag_params.image_cond + sigma_in = pag_params.sigma + + # concatenate the conditions + # "modules/sd_samplers_cfg_denoiser.py:237" + cond_in = catenate_conds([tensor, uncond]) + make_condition_dict = get_make_condition_dict_fn(uncond) + conds = make_condition_dict(cond_in, image_cond_in) + + # set pag_enable to True for the hooked cross attention modules + for module in pag_params.crossattn_modules: + setattr(module, 'pag_enable', True) + + # get the PAG guidance (is there a way to optimize this so we don't have to calculate it twice?) + pag_x_out = params.inner_model(x_in, sigma_in, cond=conds) + + # update pag_x_out + pag_params.pag_x_out = pag_x_out + + # set pag_enable to False + for module in pag_params.crossattn_modules: + setattr(module, 'pag_enable', False) + + def cfg_after_cfg_callback(self, params: AfterCFGCallbackParams, pag_params: PAGStateParams): + #self.unhook_callbacks(pag_params) + pass + + def get_xyz_axis_options(self) -> dict: + xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ in ("xyz_grid.py", "scripts.xyz_grid")][0].module + extra_axis_options = { + xyz_grid.AxisOption("[PAG] Active", str, pag_apply_override('pag_active', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[PAG] SANF", str, pag_apply_override('pag_sanf', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[PAG] PAG Scale", float, pag_apply_field("pag_scale")), + xyz_grid.AxisOption("[PAG] PAG Start Step", int, pag_apply_field("pag_start_step")), + xyz_grid.AxisOption("[PAG] PAG End Step", int, pag_apply_field("pag_end_step")), + xyz_grid.AxisOption("[PAG] Enable CFG Scheduler", str, pag_apply_override('cfg_interval_enable', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[PAG] CFG Noise Interval Low", float, pag_apply_field("cfg_interval_low")), + xyz_grid.AxisOption("[PAG] CFG Noise Interval High", float, pag_apply_field("cfg_interval_high")), + xyz_grid.AxisOption("[PAG] CFG Schedule Type", str, pag_apply_override('cfg_interval_schedule', boolean=False), choices=lambda: SCHEDULES), + #xyz_grid.AxisOption("[PAG] ctnms_alpha", float, pag_apply_field("pag_ctnms_alpha")), + } + return extra_axis_options + + +def combine_denoised_pass_conds_list(*args, **kwargs): + """ Hijacked function for combine_denoised in CFGDenoiser """ + original_func = kwargs.get('original_func', None) + new_params = kwargs.get('pag_params', None) + + if new_params is None: + logger.error("new_params is None") + return original_func(*args) + + def new_combine_denoised(x_out, conds_list, uncond, cond_scale): + denoised_uncond = x_out[-uncond.shape[0]:] + denoised = torch.clone(denoised_uncond) + + noise_level = calculate_noise_level(new_params.step, new_params.max_sampling_step) + + # Calculate CFG Scale + cfg_scale = cond_scale + new_params.cfg_interval_scheduled_value = cfg_scale + + if new_params.cfg_interval_enable: + if new_params.cfg_interval_schedule != 'Constant': + # Calculate noise interval + start = new_params.cfg_interval_low + end = new_params.cfg_interval_high + begin_range = start if start <= end else end + end_range = end if start <= end else start + # Scheduled CFG Value + scheduled_cfg_scale = cfg_scheduler(new_params.cfg_interval_schedule, new_params.step, new_params.max_sampling_step, cond_scale) + # Only apply CFG in the interval + cfg_scale = scheduled_cfg_scale if begin_range <= noise_level <= end_range else 1.0 + new_params.cfg_interval_scheduled_value = scheduled_cfg_scale + + # This may be temporarily necessary for compatibility with scfg + # if not new_params.pag_start_step <= new_params.step <= new_params.pag_end_step: + # return original_func(*args) + + # This may be temporarily necessary for compatibility with scfg + # if not new_params.pag_start_step <= new_params.step <= new_params.pag_end_step: + # return original_func(*args) + + if incantations_debug: + logger.debug(f"Schedule: {new_params.cfg_interval_schedule}, CFG Scale: {cfg_scale}, Noise_level: {round(noise_level,3)}") + + for i, conds in enumerate(conds_list): + for cond_index, weight in conds: + if not new_params.cfg_interval_enable: + denoised[i] += (x_out[cond_index] - denoised_uncond[i]) * (weight * cfg_scale) + else: + denoised[i] += (x_out[cond_index] - denoised_uncond[i]) * (weight * cfg_scale) + + # Apply PAG guidance only within interval + if not new_params.pag_start_step <= new_params.step <= new_params.pag_end_step or new_params.pag_scale <= 0: + continue + else: + try: + denoised[i] += (x_out[cond_index] - new_params.pag_x_out[i]) * (weight * new_params.pag_scale) + except TypeError: + logger.exception("TypeError in combine_denoised_pass_conds_list") + except IndexError: + logger.exception("IndexError in combine_denoised_pass_conds_list") + #logger.debug(f"added PAG guidance to denoised - pag_scale:{global_scale}") + return denoised + return new_combine_denoised(*args) + + +# from modules/sd_samplers_cfg_denoiser.py:187-195 +def get_make_condition_dict_fn(text_uncond): + if shared.sd_model.model.conditioning_key == "crossattn-adm": + make_condition_dict = lambda c_crossattn, c_adm: {"c_crossattn": [c_crossattn], "c_adm": c_adm} + else: + if isinstance(text_uncond, dict): + make_condition_dict = lambda c_crossattn, c_concat: {**c_crossattn, "c_concat": [c_concat]} + else: + make_condition_dict = lambda c_crossattn, c_concat: {"c_crossattn": [c_crossattn], "c_concat": [c_concat]} + return make_condition_dict + + +def calculate_noise_level(i, N, sigma_min=0.002, sigma_max=80.0, rho=3): + """ + Calculate the noise level for a given sampling step index. + + Parameters: + i (int): Index of the current sampling step (0-based index). + N (int): Total number of sampling steps. + sigma_min (float): Minimum sigma value for min noise level, default 0.002. + sigma_max (float): Maximum sigma value for max noise level, default 80.0. + rho (int): Discretization parameter, default 3 for SD-XL, 7 for EDM2. + + Returns: + float: Calculated noise level for the given step. + """ + if i == 0: + return sigma_max + if i >= N: + return 0.0 + sigma_max_p = sigma_max ** (1/rho) + sigma_min_p = sigma_min ** (1/rho) + inner_term = sigma_max_p + (i / (N - 1)) * (sigma_min_p - sigma_max_p) + noise_level = inner_term ** rho + + return noise_level + + +def find_closest_index(noise_level: float, N: int, sigma_min=0.002, sigma_max=80.0, rho=3, tol=1e-6): + """ + Given a noise level, find the closest integer index in the range [0, N-1] that corresponds to the noise level. + + Parameters: + noise_level (float): Target noise level to find the closest index for. + N (int): Total number of sampling steps. + sigma_min (float): Minimum sigma value for min noise level, default 0.002. + sigma_max (float): Maximum sigma value for max noise level, default 80.0. + rho (int): Discretization parameter, default 3 for SD-XL, 7 for EDM2. + + Returns: + int: The closest index to the specified noise level. + """ + # Min/max noise levels for the given range + if noise_level <= sigma_min: + return N + if noise_level >= sigma_max: + return 0 + #return N - 1 + + low, high = 0, N - 1 + while low <= high: + mid = (low + high) // 2 + mid_nl = calculate_noise_level(mid, N) + if abs(mid_nl - noise_level) < tol: + return mid + elif mid_nl < noise_level: + high = mid - 1 + else: + low = mid + 1 + + # If exact match not found, return the index with noise level closest to the target + return low if abs(calculate_noise_level(low, N) - noise_level) < abs(calculate_noise_level(high, N) - noise_level) else high + + +### CFG Schedulers + + +# TODO: Refactor this into something cleaner +def cfg_scheduler(schedule: str, step: int, max_steps: int, w0: float) -> float: + """ + Constant scheduler for CFG guidance weight. + + Parameters: + step (int): Current sampling step. + max_steps (int): Total number of sampling steps. + w0 (float): Constant value for the guidance weight. + + Returns: + float: Scheduled guidance weight value. + """ + match schedule: + case 'Constant': + return constant_schedule(step, max_steps, w0) + case 'Linear': + return linear_schedule(step, max_steps, w0) + case 'Clamp-Linear (c=4.0)': + return clamp_linear_schedule(step, max_steps, w0, 4.0) + case 'Clamp-Linear (c=2.0)': + return clamp_linear_schedule(step, max_steps, w0, 2.0) + case 'Clamp-Linear (c=1.0)': + return clamp_linear_schedule(step, max_steps, w0, 1.0) + case 'Inverse-Linear': + return invlinear_schedule(step, max_steps, w0) + case 'PCS (s=0.01)': + return powered_cosine_schedule(step, max_steps, w0, 0.01) + case 'PCS (s=0.1)': + return powered_cosine_schedule(step, max_steps, w0, 0.1) + case 'PCS (s=1.0)': + return powered_cosine_schedule(step, max_steps, w0, 1.0) + case 'PCS (s=2.0)': + return powered_cosine_schedule(step, max_steps, w0, 2.0) + case 'PCS (s=4.0)': + return powered_cosine_schedule(step, max_steps, w0, 4.0) + case 'Clamp-Cosine (c=4.0)': + return clamp_cosine_schedule(step, max_steps, w0, 4.0) + case 'Clamp-Cosine (c=2.0)': + return clamp_cosine_schedule(step, max_steps, w0, 2.0) + case 'Clamp-Cosine (c=1.0)': + return clamp_cosine_schedule(step, max_steps, w0, 1.0) + case 'Cosine': + return cosine_schedule(step, max_steps, w0) + case 'Sine': + return sine_schedule(step, max_steps, w0) + case 'V-Shape': + return v_shape_schedule(step, max_steps, w0) + case 'A-Shape': + return a_shape_schedule(step, max_steps, w0) + case 'Interval': + return interval_schedule(step, max_steps, w0, 0.25, 5.42) + case _: + logger.error(f"Invalid CFG schedule: {schedule}") + return constant_schedule(step, max_steps, w0) + + +def constant_schedule(step: int, max_steps: int, w0: float): + """ + Constant scheduler for CFG guidance weight. + """ + return w0 + + +def linear_schedule(step: int, max_steps: int, w0: float): + """ + Normalized linear scheduler for CFG guidance weight. + Such that integral 0-> T ~ w(t) dt = w*T + """ + # return w0 * (1 - step / max_steps) + return w0 * 2 * (1 - step / max_steps) + + +def clamp_linear_schedule(step: int, max_steps: int, w0: float, c: float): + """ + Normalized clamp-linear scheduler for CFG guidance weight. + """ + return max(c, linear_schedule(step, max_steps, w0)) + + +def clamp_cosine_schedule(step: int, max_steps: int, w0: float, c: float): + """ + Normalized clamp-cosine scheduler for CFG guidance weight. + """ + return max(c, cosine_schedule(step, max_steps, w0)) + + +def invlinear_schedule(step: int, max_steps: int, w0: float): + """ + Normalized inverse linear scheduler for CFG guidance weight. + """ + # return w0 * (step / max_steps) + return w0 * 2 * (step / max_steps) + + +def powered_cosine_schedule(step: int, max_steps: int, w0: float, s: float): + """ + Normalized cosine scheduler for CFG guidance weight. + """ + return w0 * ((1 - math.cos(math.pi * ((max_steps - step) / max_steps)**s))/2.0) + + +def cosine_schedule(step: int, max_steps: int, w0: float): + """ + Normalized cosine scheduler for CFG guidance weight. + """ + return w0 * (1 + math.cos(math.pi * step / max_steps)) + + +def sine_schedule(step: int, max_steps: int, w0: float): + """ + Normalized sine scheduler for CFG guidance weight. + """ + return w0 * (math.sin((math.pi * step / max_steps) - (math.pi / 2)) + 1) + + +def v_shape_schedule(step: int, max_steps: int, w0: float): + """ + Normalized V-shape scheduler for CFG guidance weight. + """ + if step < max_steps / 2: + return invlinear_schedule(step, max_steps, w0) + return linear_schedule(step, max_steps, w0) + + +def a_shape_schedule(step: int, max_steps: int, w0: float): + """ + Normalized A-shape scheduler for CFG guidance weight. + """ + if step < max_steps / 2: + return linear_schedule(step, max_steps, w0) + return invlinear_schedule(step, max_steps, w0) + + +def interval_schedule(step: int, max_steps: int, w0: float, low: float, high: float): + """ + Normalized interval scheduler for CFG guidance weight. + """ + if low <= step <= high: + return w0 + return 1.0 + + + +# XYZ Plot +# Based on @mcmonkey4eva's XYZ Plot implementation here: https://github.com/mcmonkeyprojects/sd-dynamic-thresholding/blob/master/scripts/dynamic_thresholding.py +def pag_apply_override(field, boolean: bool = False): + def fun(p, x, xs): + if boolean: + x = True if x.lower() == "true" else False + setattr(p, field, x) + if not hasattr(p, "pag_active"): + setattr(p, "pag_active", True) + if 'cfg_interval_' in field and not hasattr(p, "cfg_interval_enable"): + setattr(p, "cfg_interval_enable", True) + return fun + + +def pag_apply_field(field): + def fun(p, x, xs): + if not hasattr(p, "pag_active"): + setattr(p, "pag_active", True) + setattr(p, field, x) + return fun + + +# thanks torch; removing hooks DOESN'T WORK +# thank you to @ProGamerGov for this https://github.com/pytorch/pytorch/issues/70455 +def _remove_all_forward_hooks( + module: torch.nn.Module, hook_fn_name: Optional[str] = None +) -> None: + """ + This function removes all forward hooks in the specified module, without requiring + any hook handles. This lets us clean up & remove any hooks that weren't property + deleted. + + Warning: Various PyTorch modules and systems make use of hooks, and thus extreme + caution should be exercised when removing all hooks. Users are recommended to give + their hook function a unique name that can be used to safely identify and remove + the target forward hooks. + + Args: + + module (nn.Module): The module instance to remove forward hooks from. + hook_fn_name (str, optional): Optionally only remove specific forward hooks + based on their function's __name__ attribute. + Default: None + """ + + if hook_fn_name is None: + warn("Removing all active hooks can break some PyTorch modules & systems.") + + + def _remove_hooks(m: torch.nn.Module, name: Optional[str] = None) -> None: + if hasattr(module, "_forward_hooks"): + if m._forward_hooks != OrderedDict(): + if name is not None: + dict_items = list(m._forward_hooks.items()) + m._forward_hooks = OrderedDict( + [(i, fn) for i, fn in dict_items if fn.__name__ != name] + ) + else: + m._forward_hooks: Dict[int, Callable] = OrderedDict() + + def _remove_child_hooks( + target_module: torch.nn.Module, hook_name: Optional[str] = None + ) -> None: + for name, child in target_module._modules.items(): + if child is not None: + _remove_hooks(child, hook_name) + _remove_child_hooks(child, hook_name) + + # Remove hooks from target submodules + _remove_child_hooks(module, hook_fn_name) + + # Remove hooks from the target module + _remove_hooks(module, hook_fn_name) diff --git a/extensions/sd-webui-incantations/scripts/save_attn_maps.py b/extensions/sd-webui-incantations/scripts/save_attn_maps.py new file mode 100644 index 0000000000000000000000000000000000000000..03db4111f9c341ed10eb1aa9abaf067e78b0c4d5 --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/save_attn_maps.py @@ -0,0 +1,485 @@ +import os +import logging +import copy +import gradio as gr +import torch +import re +from torchvision.transforms import GaussianBlur + + +from einops import rearrange +from modules import shared, script_callbacks +from modules.images import get_next_sequence_number +from modules.processing import StableDiffusionProcessing +from scripts.ui_wrapper import UIWrapper, arg +from scripts.incant_utils import module_hooks, plot_tools, prompt_utils + +logger = logging.getLogger(__name__) + + +module_field_map = { + 'savemaps': True, + 'savemaps_batch': None, + 'savemaps_step': None, + 'savemaps_save_steps': None, +} + + +SUBMODULES = ['to_q', 'to_k', 'to_v'] + + +class SaveAttentionMapsScript(UIWrapper): + def __init__(self): + self.infotext_fields: list = [] + self.paste_field_names: list = [] + + def title(self) -> str: + return "Save Attention Maps" + + def setup_ui(self, is_img2img) -> list: + with gr.Accordion('Save Attention Maps', open = False): + with gr.Row(): + active = gr.Checkbox(label = 'Active', default = False) + map_types = gr.CheckboxGroup( + label = 'Map Types', + choices = ['One-Hot Map', 'Per-Token Maps'], + value = ['One-Hot Map'], + info = 'Select the type of attention maps to save.', + ) + export_folder = gr.Textbox(visible=False, label = 'Export Folder', value = 'attention_maps', info = 'Folder to save attention maps to as a subdirectory of the outputs.') + module_name_filter = gr.Textbox(label = 'Module Names', value = 'input_blocks_5_1_transformer_blocks_0_attn2', info = 'Module name to save attention maps for. If the substring is found in the module name, the attention maps will be saved for that module.') + class_name_filter = gr.Textbox(label = 'Class Name Filter', value = 'CrossAttention', info = 'Filters eligible modules by the class name.') + save_every_n_step = gr.Slider(label = 'Save Every N Step', value = 0, min = 0, max = 100, step = 1, info = 'Save attention maps every N steps. 0 to save last step.') + print_modules = gr.Button(value = 'Print Modules To Console') + print_modules.click(self.print_modules, inputs=[module_name_filter, class_name_filter]) + + self.infotext_fields = [] + self.paste_field_names = [] + + opts = [active, module_name_filter, class_name_filter, save_every_n_step, map_types] + for opt in opts: + opt.do_not_save_to_config = True + return opts + + def before_process_batch(self, p: StableDiffusionProcessing, active, module_name_filter, class_name_filter, save_every_n_step, map_types, *args, **kwargs): + # Always unhook the modules first + module_list = self.get_modules_by_filter(module_name_filter, class_name_filter) + script_callbacks.remove_current_script_callbacks() + self.unhook_modules(module_list, copy.deepcopy(module_field_map)) + + setattr(p, 'savemaps_module_list', module_list) + setattr(p, 'savemaps_map_types', map_types) + + if not active: + return + + token_count, _= prompt_utils.get_token_count(p.prompt, p.steps, True) + + if token_count <= 0: + logger.warning("No tokens found in prompt. Skipping saving attention maps.") + return + + setattr(p, 'savemaps_token_count', token_count) + setattr(p, 'savemaps_step', 0) + + token_indices = [] + # Tokenize/decode the prompts + tokenized_prompts = [] + batch_chunks, _ = prompt_utils.tokenize_prompt(p.prompt) + for batch in batch_chunks: + for sub_batch in batch: + tokenized_prompts.append(prompt_utils.decode_tokenized_prompt(sub_batch.tokens)) + for tp_prompt in tokenized_prompts: + for tp in tp_prompt: + token_idx, token_id, word = tp + # jank + if token_id < 49406: + token_indices.append(token_idx) + # sanitize tokenized prompts + tp[2] = re.escape(word) + + + setattr(p, 'savemaps_tokenized_prompts', tokenized_prompts) + setattr(p, 'savemaps_token_indices', token_indices) + + + # Make sure the output folder exists + outpath_samples = p.outpath_samples + # Move this to plot tools? + if not outpath_samples: + logger.warning("No output path found. Skipping saving attention maps.") + return + output_folder_path = os.path.join(outpath_samples, 'attention_maps') + if not os.path.exists(output_folder_path): + logger.info(f"Creating directory: {output_folder_path}") + os.makedirs(output_folder_path) + + # sequence number for saving + seq_num = get_next_sequence_number(output_folder_path, basename='') + setattr(p, 'savemaps_seq_num', seq_num) + + latent_shape = [p.height // p.rng.shape[1], p.width // p.rng.shape[2]] # (height, width) + + save_steps = [] + min_step = max(save_every_n_step-1, 0) + if save_every_n_step > 0: + save_steps = list(range(min_step, p.steps, save_every_n_step)) + else: + save_steps = [p.steps-1] + # always save last step + if p.steps-1 not in save_steps: + save_steps.append(p.steps-1) + setattr(p, 'savemaps_save_steps', save_steps) + + # Create fields in module + value_map = copy.deepcopy(module_field_map) + value_map['savemaps_save_steps'] = save_steps + value_map['savemaps_step'] = 0 + #value_map['savemaps_shape'] = torch.tensor(latent_shape).to(device=shared.device, dtype=torch.int32) + self.hook_modules(module_list, value_map, p) + self.create_save_hook(module_list) + + def on_cfg_denoiser(params: script_callbacks.CFGDenoiserParams): + """ Sets the step for all modules + the webui reports an incorrect step so we just count it ourselves + """ + for module in module_list: + module.savemaps_step = p.savemaps_step + # logger.debug('Setting step to %d for %d modules', p.savemaps_step, len(module_list)) + p.savemaps_step += 1 + + script_callbacks.on_cfg_denoiser(on_cfg_denoiser) + + + def process(self, p, *args, **kwargs): + pass + + def before_process(self, p: StableDiffusionProcessing, active, module_name_filter, class_name_filter, save_every_n_step, map_types, *args, **kwargs): + module_list = self.get_modules_by_filter(module_name_filter, class_name_filter) + self.unhook_modules(module_list, copy.deepcopy(module_field_map)) + + def process_batch(self, p, *args, **kwargs): + pass + + def postprocess_batch(self, p: StableDiffusionProcessing, active, module_name_filter, class_name_filter, save_every_n_step, map_types, *args, **kwargs): + module_list = self.get_modules_by_filter(module_name_filter, class_name_filter) + + if getattr(p, 'savemaps_token_count', None) is None: + self.unhook_modules(module_list, copy.deepcopy(module_field_map)) + return + + base_seq_num = getattr(p, 'savemaps_seq_num', None) + map_types = getattr(p, 'savemaps_map_types', []) + tokenized_prompts = getattr(p, 'savemaps_tokenized_prompts', None) + token_indices = getattr(p, 'savemaps_token_indices', None) + save_steps = getattr(p, 'savemaps_save_steps', None) + save_image_path = os.path.join(p.outpath_samples, 'attention_maps') + + plot_is_self = False # kind of useless + + for module in module_list: + network_layer_name = module.network_layer_name + + if not hasattr(module, 'savemaps_batch') or module.savemaps_batch is None: + logger.error(f"No attention maps found for module: {network_layer_name}") + continue + + # self attn maps are kind of useless atm + is_self = getattr(module, 'savemaps_is_self', False) + if is_self and not plot_is_self: + continue + + # selfattn: seq_len = hw + # crossattn: seq_len = # of tokens + attn_maps = module.savemaps_batch # (attn_map num, 2 * batch_num, height * width, sequence_len) + attn_map_num, batch_num, hw, seq_len = attn_maps.shape + token_indices = p.savemaps_token_indices + save_steps = p.savemaps_save_steps + downscale_h = round((hw * (p.height / p.width)) ** 0.5) + downscale_w = hw // downscale_h + gaussian_blur = GaussianBlur(kernel_size=3, sigma=1) + + # Blur maps + if is_self: + attn_maps = attn_maps.view(attn_map_num * batch_num, downscale_h, downscale_w, seq_len) # if self-attn, we need to blur over the sequence length + attn_maps = attn_maps.permute(0, 3, 1, 2) # (ab, seq_len, height, width) + attn_maps = gaussian_blur(attn_maps) # Applying Gaussian smoothing + attn_maps = attn_maps.permute(0, 2, 3, 1) # (ab, height, width, seq_len) + if is_self: + attn_maps = attn_maps.view(attn_map_num, 2, batch_num // 2, downscale_h * downscale_w, seq_len).mean(dim=1) # (attn_map num, batch_num, hw, hw) + attn_maps = attn_maps.unsqueeze(2) # (attn_map num, batch_num, 1, hw, hw) + else: + attn_maps = rearrange(attn_maps, 'n (m b) (h w) t -> n m b t h w', m = 2, h = downscale_h).mean(dim=1) # (attn_map num, batch_num, token_idx, height, width) + attn_map_num, batch_num, token_dim, h, w = attn_maps.shape + + output_dict_maps = [] + per_token_dict_maps = [] + one_hot_dict_maps = [] + + if 'Per-Token Maps' in map_types: + + # write to dict + for attn_map_idx in range(attn_maps.shape[0]): + for batch_idx in range(batch_num): + for token_idx in token_indices: + + attnmap = attn_maps[attn_map_idx, batch_idx, token_idx] + _, token_id, word = tokenized_prompts[batch_idx][token_idx] + + plot_type = f"({token_idx}, {token_id}, '{word}')" + filename_info = f'token{token_idx:04}' + plot_color = 'viridis' + + map_info: dict = self.create_base_dict(plot_type, base_seq_num, network_layer_name, save_steps, attn_map_idx, batch_idx, attnmap, filename_info, plot_color) + map_info.update({ + 'token_idx': token_idx, + 'token_id': token_id, + 'token_word': word, + }) + output_dict_maps.append(map_info) + + if 'One-Hot Map' in map_types: + one_hot_map = attn_maps[:, :, token_indices] # (attn_map num, batch_num, token_idx, height, width) + one_hot_map = one_hot_map.argmax(dim=2, keepdim=True) + one_hot_map = one_hot_map.to(dtype=torch.float16) + + # quantize to stable number of colors s.t. + num_colors = max(len(token_indices), 1) + min_val, max_val = one_hot_map.min(), one_hot_map.max() + step = 1 / num_colors + one_hot_map *= step + one_hot_map = one_hot_map.sum(dim=2) # (attn_map num, batch_num, height, width) + + # write to dict + for attn_map_idx in range(one_hot_map.shape[0]): + for batch_idx in range(batch_num): + plot_type = "One Hot" + plot_color = 'plasma' + attnmap = one_hot_map[attn_map_idx, batch_idx] + ohm_info: dict = self.create_base_dict(plot_type, base_seq_num, network_layer_name, save_steps, attn_map_idx, batch_idx, attnmap, 'ohm', plot_color) + output_dict_maps.append(ohm_info) + + # Save maps from map dict + for md in output_dict_maps: + base_seq_num = md['seq_num'] + network_layer_name = md['network_layer_name'] + savestep_num = md['savestep_num'] + attn_map_idx = md['attn_map_idx'] + batch_idx = md['batch_idx'] + + # output filename and path + filename_info = md['filename_info'] + if len(filename_info) > 0: + filename_info = f'{filename_info}_' + + out_file_name = f'{base_seq_num:04}-{network_layer_name}_{filename_info}step{savestep_num:04}_attnmap_{attn_map_idx:04}_batch{batch_idx:04}.png' + out_save_path = os.path.join(save_image_path, out_file_name) + + # plot title + plot_type = md['plot_type'] + plot_color = md['plot_color'] + plot_title = f"{network_layer_name}\nStep {savestep_num}" + if len(plot_type) > 0: + plot_title += f", {plot_type}" + + attn_map = md['attnmap'] + plot_tools.plot_attention_map( + attention_map = attn_map, + title = plot_title, + save_path = out_save_path, + plot_type = plot_color, + ) + + if shared.state.interrupted: + self.unhook_modules(module_list, copy.deepcopy(module_field_map)) + return + self.unhook_modules(module_list, copy.deepcopy(module_field_map)) + + def create_base_dict(self, plot_type:str, base_seq_num: int, network_layer_name: str, save_steps: list, attn_map_idx: int, batch_idx: int, attnmap: torch.Tensor, filename_info: str, plot_color: str): + """ Create a base dictionary for saving attention maps for minimum metadata that the save function expects + Arguments: + plot_type: str - name of the type of plot, used in the plot title + base_seq_num: int - start sequence number for saving, prefixes the filename with "000xx-" where xx is the sequence number + module_name: str - the module's network layer name + save_steps: list[int] - list of steps to save attention maps for, should be same length as the number of attention maps + attn_map_idx: int - index of the attention map + batch_idx: int - index of the batch + attnmap: torch.Tensor - attention map of shape [C, H, W] + filename_info: str- a string that goes in the middle of the filename f"000xx-{filename_info}-000yy.png" + plot_color: str - one of the matplotlib color maps (default is 'viridis') + """ + network_layer_name = network_layer_name.removeprefix('diffusion_model_') + network_layer_name = network_layer_name.replace('transformer_blocks_', 'tr_bl_') + base_dict = { + 'plot_type': plot_type, + 'seq_num': base_seq_num + batch_idx, + 'step': save_steps[attn_map_idx] + 1, + 'network_layer_name': network_layer_name, + 'attn_map_idx': attn_map_idx, + 'savestep_num': save_steps[attn_map_idx] + 1, + 'batch_idx': batch_idx, + 'attnmap': attnmap, + 'filename_info': filename_info, + 'plot_color': plot_color, + } + return base_dict + + def unhook_callbacks(self) -> None: + pass + + def get_xyz_axis_options(self) -> dict: + return {} + + def get_infotext_fields(self) -> list: + return self.infotext_fields + + def create_save_hook(self, module_list): + pass + + def hook_modules(self, module_list: list, value_map: dict, p: StableDiffusionProcessing): + def savemaps_hook(module, input, kwargs, output): + """ Hook to save attention maps every N steps, or the last step if N is 0. + Saves attention maps to a field named 'savemaps_batch' in the module. + with shape (attn_map, batch_num, height * width). + + """ + #module.savemaps_step += 1 + + if not module.savemaps_step in module.savemaps_save_steps: + return + reweight_crossattn = True + + + is_self = getattr(module, 'savemaps_is_self', False) + to_q_map = getattr(module, 'savemaps_to_q_map', None) + to_k_map = to_q_map if module.savemaps_is_self else getattr(module, 'savemaps_to_k_map', None) + + # we want to reweight the attention scores by removing influence of the first token + orig_seq_len = to_k_map.shape[1] + # token_count = module.savemaps_token_count + # min_token = 0 + # max_token = min(token_count+1, orig_seq_len) + token_indices = module.savemaps_token_indices + + if not is_self and reweight_crossattn: + to_k_map = to_k_map[:, token_indices, :] + + attn_map = get_attention_scores(to_q_map, to_k_map, dtype=to_q_map.dtype) + b, hw, seq_len = attn_map.shape + + if not is_self and reweight_crossattn: + #to_attn_zeros = torch.zeros([b, hw]).unsqueeze(-1).to(device=shared.device, dtype=attn_map.dtype) # (batch, h*w, 1) + #attn_map = torch.cat([to_attn_zeros, attn_map], dim=-1) # re pad to original token dim size + left_pad = 1 + right_pad = orig_seq_len - seq_len - 1 + attn_map = torch.nn.functional.pad(attn_map, (left_pad, right_pad), value=0) # re pad to original token dim size + + # multiply into text embeddings + attn_map = attn_map.unsqueeze(0) + + #attn_map = attn_map.mean(dim=-1) + if module.savemaps_batch is None: + module.savemaps_batch = attn_map + else: + module.savemaps_batch = torch.cat([module.savemaps_batch, attn_map], dim=0) + + def savemaps_to_q_hook(module, input, kwargs, output): + setattr(module.savemaps_parent_module[0], 'savemaps_to_q_map', output) + + def savemaps_to_k_hook(module, input, kwargs, output): + if not module.savemaps_parent_module[0].savemaps_is_self: + setattr(module.savemaps_parent_module[0],'savemaps_to_k_map', output) + + def savemaps_to_v_hook(module, input, kwargs, output): + setattr(module.savemaps_parent_module[0],'savemaps_to_v_map', output) + + #for module, kv in zip(module_list, value_map.items()): + for module in module_list: + # logger.debug('Adding hook to %s', module.network_layer_name) + for key_name, default_value in value_map.items(): + module_hooks.modules_add_field(module, key_name, default_value) + + module_hooks.module_add_forward_hook(module, savemaps_hook, 'forward', with_kwargs=True) + module_hooks.modules_add_field(module, 'savemaps_token_count', p.savemaps_token_count) + module_hooks.modules_add_field(module, 'savemaps_token_indices', p.savemaps_token_indices) + + if module.network_layer_name.endswith('attn1'): # self attn + module_hooks.modules_add_field(module, 'savemaps_is_self', True) + if module.network_layer_name.endswith('attn2'): # self attn + module_hooks.modules_add_field(module, 'savemaps_is_self', False) + + for module_name in SUBMODULES: + if not hasattr(module, module_name): + logger.error(f"Submodule not found: {module_name} in module: {module.network_layer_name}") + continue + submodule = getattr(module, module_name) + hook_fn_name = f'savemaps_{module_name}_hook' + hook_fn = locals().get(hook_fn_name, None) + if not hook_fn: + logger.error(f"Hook function '{hook_fn_name}' not found for submodule: {module_name}") + continue + + module_hooks.modules_add_field(submodule, 'savemaps_parent_module', [module]) + module_hooks.module_add_forward_hook(submodule, hook_fn, 'forward', with_kwargs=True) + + def unhook_modules(self, module_list: list, value_map: dict): + for module in module_list: + for key_name, _ in value_map.items(): + module_hooks.modules_remove_field(module, key_name) + module_hooks.modules_remove_field(module, 'savemaps_is_self') + module_hooks.modules_remove_field(module, 'savemaps_token_count') + module_hooks.modules_remove_field(module, 'savemaps_token_indices') + module_hooks.remove_module_forward_hook(module, 'savemaps_hook') + for module_name in SUBMODULES: + module_hooks.modules_remove_field(module, f'savemaps_{module_name}_map') + + if hasattr(module, module_name): + submodule = getattr(module, module_name) + module_hooks.modules_remove_field(submodule, 'savemaps_parent_module') + module_hooks.remove_module_forward_hook(submodule, f'savemaps_{module_name}_hook') + + + def print_modules(self, module_name_filter, class_name_filter): + logger.info("Module name filter: '%s', Class name filter: '%s'", module_name_filter, class_name_filter) + modules = self.get_modules_by_filter(module_name_filter, class_name_filter) + module_names = [""] + if len(modules) > 0: + module_names = "\n".join([f"{m.network_layer_name}: {m.__class__.__name__}" for m in modules]) + logger.info("Modules found:\n----------\n%s\n----------\n", module_names) + + def get_modules_by_filter(self, module_name_filter, class_name_filter): + if len(class_name_filter) == 0: + class_name_filter = None + if len(module_name_filter) == 0: + module_name_filter = None + found_modules = module_hooks.get_modules(module_name_filter, class_name_filter) + if len(found_modules) == 0: + logger.warning(f"No modules found with module name filter: {module_name_filter} and class name filter") + return found_modules + + +def get_attention_scores(to_q_map, to_k_map, dtype): + """ Calculate the attention scores for the given query and key maps + Arguments: + to_q_map: torch.Tensor - query map + to_k_map: torch.Tensor - key map + dtype: torch.dtype - data type of the tensor + Returns: + torch.Tensor - attention scores + """ + # based on diffusers models/attention.py "get_attention_scores" + # use in place operations vs. softmax to save memory: https://stackoverflow.com/questions/53732209/torch-in-place-operations-to-save-memory-softmax + # 512x: 2.65G -> 2.47G + + attn_probs = to_q_map @ to_k_map.transpose(-1, -2) + attn_probs = attn_probs.to(dtype=torch.float32) # + + channel_dim = to_q_map.size(1) + attn_probs /= (channel_dim ** 0.5) + attn_probs -= attn_probs.max() + + # avoid nan by converting to float32 and subtracting max + attn_probs = attn_probs.softmax(dim=-1).to(device=shared.device, dtype=to_q_map.dtype) + attn_probs = attn_probs.to(dtype=dtype) + + return attn_probs \ No newline at end of file diff --git a/extensions/sd-webui-incantations/scripts/scfg.py b/extensions/sd-webui-incantations/scripts/scfg.py new file mode 100644 index 0000000000000000000000000000000000000000..93d24704e5f7b9dbf190ae158ab9d48c6aabf0f5 --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/scfg.py @@ -0,0 +1,830 @@ +import logging +from os import environ +import modules.scripts as scripts +import gradio as gr +import scipy.stats as stats + +from scripts.ui_wrapper import UIWrapper, arg +from modules import script_callbacks, patches +from modules.hypernetworks import hypernetwork +#import modules.sd_hijack_optimizations +from modules.script_callbacks import CFGDenoiserParams, CFGDenoisedParams, AfterCFGCallbackParams +from modules.prompt_parser import reconstruct_multicond_batch +from modules.processing import StableDiffusionProcessing +#from modules.shared import sd_model, opts +from modules.sd_samplers_cfg_denoiser import catenate_conds +from modules.sd_samplers_cfg_denoiser import CFGDenoiser +from modules import shared + +import math +import torch +from torch.nn import functional as F +from torchvision.transforms import GaussianBlur + +from warnings import warn +from typing import Callable, Dict, Optional +from collections import OrderedDict +import torch + +from scripts.incant_utils import module_hooks + +# from pytorch_memlab import LineProfiler, MemReporter +# reporter = MemReporter() + +logger = logging.getLogger(__name__) +logger.setLevel(environ.get("SD_WEBUI_LOG_LEVEL", logging.INFO)) + +incantations_debug = environ.get("INCANTAIONS_DEBUG", False) + + +""" +An unofficial implementation of "Rethinking the Spatial Inconsistency in Classifier-Free Diffusion Guidancee" for Automatic1111 WebUI. + +This builds upon the code provided in the official S-CFG repository: https://github.com/SmilesDZgk/S-CFG + + +@inproceedings{shen2024rethinking, + title={Rethinking the Spatial Inconsistency in Classifier-Free Diffusion Guidancee}, + author={Shen, Dazhong and Song, Guanglu and Xue, Zeyue and Wang, Fu-Yun and Liu, Yu}, + booktitle={Proceedings of The IEEE/CVF Computer Vision and Pattern Recognition Conference (CVPR)}, + year={2024} +} + +Parts of the code are based on Diffusers under the Apache License 2.0: +# Copyright 2024 The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Author: v0xie +GitHub URL: https://github.com/v0xie/sd-webui-incantations + +""" + + +handles = [] +global_scale = 1 + +SCFG_MODULES = ['to_q', 'to_k'] + + +class SCFGStateParams: + def __init__(self): + self.scfg_scale:float = 0.8 + self.rate_min = 0.8 + self.rate_max = 3.0 + self.rate_clamp = 15.0 + self.R = 4 + self.start_step = 0 + self.end_step = 150 + self.gaussian_smoothing = None + + self.max_sampling_steps = -1 + self.current_step = 0 + self.height = -1 + self.width = -1 + + self.statistics = { + "min_rate": float('inf'), + "max_rate": float('-inf'), + } + + self.mask_t = None + self.mask_fore = None + self.denoiser = None + self.all_crossattn_modules = None + self.patched_combined_denoised = None + + +class SCFGExtensionScript(UIWrapper): + def __init__(self): + self.cached_c = [None, None] + self.handles = [] + + # Extension title in menu UI + def title(self) -> str: + return "S-CFG" + + # Decide to show menu in txt2img or img2img + def show(self, is_img2img): + return scripts.AlwaysVisible + + # Setup menu ui detail + def setup_ui(self, is_img2img) -> list: + with gr.Accordion('S-CFG', open=False): + active = gr.Checkbox(value=False, default=False, label="Active", elem_id='scfg_active', info="Computationally expensive. A batch size of 4 for 1024x1024 will max out a 24GB card!") + with gr.Row(): + scfg_scale = gr.Slider(value = 1.0, minimum = 0, maximum = 10.0, step = 0.1, label="SCFG Scale", elem_id = 'scfg_scale', info="") + scfg_r = gr.Slider(value = 4, minimum = 1, maximum = 16, step = 1, label="SCFG R", elem_id = 'scfg_r', info="Scale factor. Greater R uses more memory.") + with gr.Row(): + scfg_rate_min = gr.Slider(value = 0.8, minimum = 0, maximum = 30.0, step = 0.1, label="Min Rate", elem_id = 'scfg_rate_min', info="") + scfg_rate_max = gr.Slider(value = 3.0, minimum = 0, maximum = 30.0, step = 0.1, label="Max Rate", elem_id = 'scfg_rate_max', info="") + scfg_rate_clamp = gr.Slider(value = 0.0, minimum = 0, maximum = 30.0, step = 0.1, label="Clamp Rate", elem_id = 'scfg_rate_clamp', info="If > 0, clamp max rate to Clamp Rate / CFG Scale. Overrides max rate.") + with gr.Row(): + start_step = gr.Slider(value = 0, minimum = 0, maximum = 150, step = 1, label="Start Step", elem_id = 'scfg_start_step', info="") + end_step = gr.Slider(value = 150, minimum = 0, maximum = 150, step = 1, label="End Step", elem_id = 'scfg_end_step', info="") + + active.do_not_save_to_config = True + scfg_scale.do_not_save_to_config = True + scfg_rate_min.do_not_save_to_config = True + scfg_rate_max.do_not_save_to_config = True + scfg_rate_clamp.do_not_save_to_config = True + scfg_r.do_not_save_to_config = True + start_step.do_not_save_to_config = True + end_step.do_not_save_to_config = True + + self.infotext_fields = [ + (active, lambda d: gr.Checkbox.update(value='SCFG Active' in d)), + (scfg_scale, 'SCFG Scale'), + (scfg_rate_min, 'SCFG Rate Min'), + (scfg_rate_max, 'SCFG Rate Max'), + (scfg_rate_clamp, 'SCFG Rate Clamp'), + (start_step, 'SCFG Start Step'), + (end_step, 'SCFG End Step'), + (scfg_r, 'SCFG R'), + ] + self.paste_field_names = [ + 'scfg_active', + 'scfg_scale', + 'scfg_rate_min', + 'scfg_rate_max', + 'scfg_rate_clamp', + 'scfg_start_step', + 'scfg_end_step', + 'scfg_r', + ] + return [active, scfg_scale, scfg_rate_min, scfg_rate_max, scfg_rate_clamp, start_step, end_step, scfg_r] + + def process_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + self.pag_process_batch(p, *args, **kwargs) + + def pag_process_batch(self, p: StableDiffusionProcessing, active, scfg_scale, scfg_rate_min, scfg_rate_max, scfg_rate_clamp, start_step, end_step, scfg_r, *args, **kwargs): + # cleanup previous hooks always + script_callbacks.remove_current_script_callbacks() + self.remove_all_hooks() + + active = getattr(p, "scfg_active", active) + if active is False: + return + scfg_scale = getattr(p, "scfg_scale", scfg_scale) + scfg_rate_min = getattr(p, "scfg_rate_min", scfg_rate_min) + scfg_rate_max = getattr(p, "scfg_rate_max", scfg_rate_max) + scfg_rate_clamp = getattr(p, "scfg_rate_clamp", scfg_rate_clamp) + start_step = getattr(p, "scfg_start_step", start_step) + end_step = getattr(p, "scfg_end_step", end_step) + scfg_r = getattr(p, "scfg_r", scfg_r) + + p.extra_generation_params.update({ + "SCFG Active": active, + "SCFG Scale": scfg_scale, + "SCFG Rate Min": scfg_rate_min, + "SCFG Rate Max": scfg_rate_max, + "SCFG Rate Clamp": scfg_rate_clamp, + "SCFG Start Step": start_step, + "SCFG End Step": end_step, + "SCFG R": scfg_r, + }) + self.create_hook(p, active, scfg_scale, scfg_rate_min, scfg_rate_max, scfg_rate_clamp, start_step, end_step, scfg_r) + + def create_hook(self, p: StableDiffusionProcessing, active, scfg_scale, scfg_rate_min, scfg_rate_max, scfg_rate_clamp, start_step, end_step, scfg_r): + # Create a list of parameters for each concept + scfg_params = SCFGStateParams() + + # Add to p + if not hasattr(p, 'incant_cfg_params'): + logger.error("No incant_cfg_params found in p") + p.incant_cfg_params['scfg_params'] = scfg_params + + scfg_params.denoiser = None + scfg_params.all_crossattn_modules = self.get_all_crossattn_modules() + scfg_params.max_sampling_steps = p.steps + scfg_params.scfg_scale = scfg_scale + scfg_params.rate_min = scfg_rate_min + scfg_params.rate_max = scfg_rate_max + scfg_params.rate_clamp = scfg_rate_clamp + scfg_params.start_step = start_step + scfg_params.end_step = end_step + scfg_params.R = scfg_r + scfg_params.height = p.height + scfg_params.width = p.width + kernel_size = 3 + sigma=0.5 + scfg_params.gaussian_smoothing = GaussianSmoothing(channels=1, kernel_size=kernel_size, sigma=sigma, dim=2).to(shared.device) + + + # Use lambda to call the callback function with the parameters to avoid global variables + #cfg_denoise_lambda = lambda callback_params: self.on_cfg_denoiser_callback(callback_params, scfg_params) + cfg_denoised_lambda = lambda callback_params: self.on_cfg_denoised_callback(callback_params, scfg_params) + unhook_lambda = lambda _: self.unhook_callbacks(scfg_params) + + self.ready_hijack_forward(scfg_params.all_crossattn_modules) + + logger.debug('Hooked callbacks') + #script_callbacks.on_cfg_denoiser(cfg_denoise_lambda) + script_callbacks.on_cfg_denoised(cfg_denoised_lambda) + script_callbacks.on_script_unloaded(unhook_lambda) + + def postprocess_batch(self, p, *args, **kwargs): + self.scfg_postprocess_batch(p, *args, **kwargs) + + def scfg_postprocess_batch(self, p, active, *args, **kwargs): + script_callbacks.remove_current_script_callbacks() + + logger.debug('Removed script callbacks') + active = getattr(p, "scfg_active", active) + if active is False: + return + + if hasattr(p, 'incant_cfg_params') and 'scfg_params' in p.incant_cfg_params: + stats = p.incant_cfg_params['scfg_params'].statistics + logger.debug('SCFG Statistics: %s', stats) + + + self.remove_all_hooks() + + def remove_all_hooks(self): + all_crossattn_modules = self.get_all_crossattn_modules() + for module in all_crossattn_modules: + self.remove_field_cross_attn_modules(module, 'scfg_last_to_q_map') + self.remove_field_cross_attn_modules(module, 'scfg_last_to_k_map') + if hasattr(module, 'to_q'): + handle_scfg_to_q = _remove_all_forward_hooks(module.to_q, 'scfg_to_q_hook') + self.remove_field_cross_attn_modules(module.to_q, 'scfg_parent_module') + if hasattr(module, 'to_k'): + handle_scfg_to_k = _remove_all_forward_hooks(module.to_k, 'scfg_to_k_hook') + self.remove_field_cross_attn_modules(module.to_k, 'scfg_parent_module') + + def unhook_callbacks(self, scfg_params: SCFGStateParams): + pass + + def ready_hijack_forward(self, all_crossattn_modules): + """ Create hooks in the forward pass of the cross attention modules + Copies the output of the to_v module to the parent module + """ + + def scfg_self_attn_hook(module, input, kwargs, output): + # scfg_q_map = output.detach().clone() + scfg_q_map = prepare_attn_map(output, module.scfg_heads) + attn_scores = get_attention_scores(scfg_q_map, scfg_q_map) + setattr(module.scfg_parent_module[0], 'scfg_last_qv_map', attn_scores) + + def scfg_cross_attn_hook(module, input, kwargs, output): + scfg_q_map = prepare_attn_map(module.scfg_parent_module[0].scfg_last_to_q_map, module.scfg_heads) + scfg_k_map = prepare_attn_map(output, module.scfg_heads) + #scfg_k_map = output.detach().clone() + attn_scores = get_attention_scores(scfg_q_map, scfg_k_map) + setattr(module.scfg_parent_module[0], 'scfg_last_qv_map', attn_scores) + # del module.parent_module[0].scfg_last_to_q_map + + def scfg_to_q_hook(module, input, kwargs, output): + setattr(module.scfg_parent_module[0], 'scfg_last_to_q_map', output) + + def scfg_to_k_hook(module, input, kwargs, output): + setattr(module.scfg_parent_module[0], 'scfg_last_to_k_map', output) + + for module in all_crossattn_modules: + if not hasattr(module, 'to_q') or not hasattr(module, 'to_k'): + logger.error("CrossAttention module '%s' does not have to_q or to_k", module.network_layer_name) + continue + + # to_q + self.add_field_cross_attn_modules(module.to_q, 'scfg_parent_module', [module]) + self.add_field_cross_attn_modules(module, 'scfg_last_to_q_map', None) + handle_scfg_to_q = module_hooks.module_add_forward_hook( + module.to_q, + scfg_to_q_hook, + with_kwargs=True + ) + + # to_k + self.add_field_cross_attn_modules(module.to_k, 'scfg_parent_module', [module]) + if module.network_layer_name.endswith('attn2'): # cross attn + self.add_field_cross_attn_modules(module, 'scfg_last_to_k_map', None) + handle_scfg_to_k = module_hooks.module_add_forward_hook( + module.to_k, + scfg_to_k_hook, + with_kwargs=True + ) + + def get_all_crossattn_modules(self): + """ + Get ALL attention modules + """ + modules = module_hooks.get_modules( + module_name_filter='CrossAttention' + ) + return modules + + def add_field_cross_attn_modules(self, module, field, value): + """ Add a field to a module if it doesn't exist """ + module_hooks.modules_add_field(module, field, value) + + def remove_field_cross_attn_modules(self, module, field): + """ Remove a field from a module if it exists """ + module_hooks.modules_remove_field(module, field) + + def on_cfg_denoiser_callback(self, params: CFGDenoiserParams, scfg_params: SCFGStateParams): + # always unhook + self.unhook_callbacks(scfg_params) + + def on_cfg_denoised_callback(self, params: CFGDenoisedParams, scfg_params: SCFGStateParams): + """ Callback function for the CFGDenoisedParams + Refer to pg.22 A.2 of the PAG paper for how CFG and PAG combine + + """ + scfg_params.current_step = params.sampling_step + + # Run only within interval + if not scfg_params.start_step <= params.sampling_step <= scfg_params.end_step: + return + + if scfg_params.scfg_scale <= 0: + return + + # S-CFG + R = scfg_params.R + max_latent_size = [params.x.shape[-2] // R, params.x.shape[-1] // R] + + #with LineProfiler(get_mask) as lp: + ca_mask, fore_mask = get_mask(scfg_params.all_crossattn_modules, + scfg_params, + r = scfg_params.R, + latent_size = max_latent_size, + ) + #lp.print_stats() + + # todo parameterize this + mask_t = F.interpolate(ca_mask, scale_factor=R, mode='nearest') + mask_fore = F.interpolate(fore_mask, scale_factor=R, mode='nearest') + scfg_params.mask_t = mask_t + scfg_params.mask_fore = mask_fore + + + def get_xyz_axis_options(self) -> dict: + xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ in ("xyz_grid.py", "scripts.xyz_grid")][0].module + extra_axis_options = { + xyz_grid.AxisOption("[SCFG] Active", str, scfg_apply_override('scfg_active', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[SCFG] SCFG Scale", float, scfg_apply_field("scfg_scale")), + xyz_grid.AxisOption("[SCFG] SCFG Rate Min", float, scfg_apply_field("scfg_rate_min")), + xyz_grid.AxisOption("[SCFG] SCFG Rate Max", float, scfg_apply_field("scfg_rate_max")), + xyz_grid.AxisOption("[SCFG] SCFG Rate Clamp", float, scfg_apply_field("scfg_rate_clamp")), + xyz_grid.AxisOption("[SCFG] SCFG Start Step", int, scfg_apply_field("scfg_start_step")), + xyz_grid.AxisOption("[SCFG] SCFG End Step", int, scfg_apply_field("scfg_end_step")), + xyz_grid.AxisOption("[SCFG] SCFG R", int, scfg_apply_field("scfg_r")), + } + return extra_axis_options + + +def scfg_combine_denoised(model_delta, cfg_scale, scfg_params: SCFGStateParams): + """ The inner loop of the S-CFG denoiser + Arguments: + model_delta: torch.Tensor - defined by `x_out[cond_index] - denoised_uncond[i]` + cfg_scale: float - guidance scale + scfg_params: SCFGStateParams - the state parameters for the S-CFG denoiser + + Returns: + int or torch.Tensor - 1.0 if not within interval or scale is 0, else the rate map tensor + """ + + current_step = scfg_params.current_step + start_step = scfg_params.start_step + end_step = scfg_params.end_step + scfg_scale = scfg_params.scfg_scale + + if not start_step <= current_step <= end_step: + return 1.0 + + if scfg_scale <= 0: + return 1.0 + + mask_t = scfg_params.mask_t + mask_fore = scfg_params.mask_fore + min_rate = scfg_params.rate_min + max_rate = scfg_params.rate_max + rate_clamp = scfg_params.rate_clamp + + model_delta = model_delta.unsqueeze(0) + model_delta_norm = model_delta.norm(dim=1, keepdim=True) + + eps = lambda dtype: torch.finfo(dtype).eps + + # rescale map if necessary + if mask_t.shape[2:] != model_delta_norm.shape[2:]: + logger.debug('Rescaling mask_t from %s to %s', mask_t.shape[2:], model_delta_norm.shape[2:]) + mask_t = F.interpolate(mask_t, size=model_delta_norm.shape[2:], mode='bilinear') + if mask_fore.shape[-2] != model_delta_norm.shape[-2]: + logger.debug('Rescaling mask_fore from %s to %s', mask_fore.shape[2:], model_delta_norm.shape[2:]) + mask_fore = F.interpolate(mask_fore, size=model_delta_norm.shape[2:], mode='bilinear') + + delta_mask_norms = (model_delta_norm * mask_t).sum([2,3])/(mask_t.sum([2,3])+eps(mask_t.dtype)) + upnormmax = delta_mask_norms.max(dim=1)[0] + upnormmax = upnormmax.unsqueeze(-1) + + fore_norms = (model_delta_norm * mask_fore).sum([2,3])/(mask_fore.sum([2,3])+eps(mask_fore.dtype)) + + up = fore_norms + down = delta_mask_norms + + tmp_mask = (mask_t.sum([2,3])>0).float() + rate = up*(tmp_mask)/(down+eps(down.dtype)) # b 257 + rate = (rate.unsqueeze(-1).unsqueeze(-1)*mask_t).sum(dim=1, keepdim=True) # b 1, 64 64 + + del model_delta_norm, delta_mask_norms, upnormmax, fore_norms, up, down, tmp_mask + + # unscaled min/max rate + if rate.min().item() < scfg_params.statistics["min_rate"]: + scfg_params.statistics["min_rate"] = rate.min().item() + if rate.max().item() > scfg_params.statistics["max_rate"]: + scfg_params.statistics["max_rate"] = rate.max().item() + + # should this go before or after the gaussian blur, or before/after the rate + rate = rate * scfg_scale + + rate = torch.clamp(rate,min=min_rate, max=max_rate) + + if rate_clamp > 0: + rate = torch.clamp_max(rate, rate_clamp/cfg_scale) + + ###Gaussian Smoothing + #kernel_size = 3 + #sigma=0.5 + #smoothing = GaussianSmoothing(channels=1, kernel_size=kernel_size, sigma=sigma, dim=2).to(rate.device) + smoothing = scfg_params.gaussian_smoothing + rate = F.pad(rate, (1, 1, 1, 1), mode='reflect') + rate = smoothing(rate) + + return rate.squeeze(0) + + +# XYZ Plot +# Based on @mcmonkey4eva's XYZ Plot implementation here: https://github.com/mcmonkeyprojects/sd-dynamic-thresholding/blob/master/scripts/dynamic_thresholding.py +def scfg_apply_override(field, boolean: bool = False): + def fun(p, x, xs): + if boolean: + x = True if x.lower() == "true" else False + setattr(p, field, x) + if not hasattr(p, "scfg_active"): + setattr(p, "scfg_active", True) + return fun + + +def scfg_apply_field(field): + def fun(p, x, xs): + if not hasattr(p, "scfg_active"): + setattr(p, "scfg_active", True) + setattr(p, field, x) + return fun + + +def _remove_all_forward_hooks( + module: torch.nn.Module, hook_fn_name: Optional[str] = None +) -> None: + module_hooks.remove_module_forward_hook(module, hook_fn_name) + + +""" +# below code modified from https://github.com/SmilesDZgk/S-CFG +@inproceedings{shen2024rethinking, + title={Rethinking the Spatial Inconsistency in Classifier-Free Diffusion Guidancee}, + author={Shen, Dazhong and Song, Guanglu and Xue, Zeyue and Wang, Fu-Yun and Liu, Yu}, + booktitle={Proceedings of The IEEE/CVF Computer Vision and Pattern Recognition Conference (CVPR)}, + year={2024} +} +""" + + +import math +import numbers +import torch +from torch import nn +from torch.nn import functional as F + + +class GaussianSmoothing(nn.Module): + """ + Apply gaussian smoothing on a + 1d, 2d or 3d tensor. Filtering is performed seperately for each channel + in the input using a depthwise convolution. + Arguments: + channels (int, sequence): Number of channels of the input tensors. Output will + have this number of channels as well. + kernel_size (int, sequence): Size of the gaussian kernel. + sigma (float, sequence): Standard deviation of the gaussian kernel. + dim (int, optional): The number of dimensions of the data. + Default value is 2 (spatial). + """ + def __init__(self, channels, kernel_size, sigma, dim=2): + super(GaussianSmoothing, self).__init__() + if isinstance(kernel_size, numbers.Number): + kernel_size = [kernel_size] * dim + if isinstance(sigma, numbers.Number): + sigma = [sigma] * dim + + # The gaussian kernel is the product of the + # gaussian function of each dimension. + kernel = 1 + meshgrids = torch.meshgrid( + [ + torch.arange(size, dtype=torch.float32) + for size in kernel_size + ] + ) + for size, std, mgrid in zip(kernel_size, sigma, meshgrids): + mean = (size - 1) / 2 + kernel *= 1 / (std * math.sqrt(2 * math.pi)) * \ + torch.exp(-((mgrid - mean) / (2 * std)) ** 2) + + # Make sure sum of values in gaussian kernel equals 1. + kernel = kernel / torch.sum(kernel) + + # Reshape to depthwise convolutional weight + kernel = kernel.view(1, 1, *kernel.size()) + kernel = kernel.repeat(channels, *[1] * (kernel.dim() - 1)) + + self.register_buffer('weight', kernel) + self.groups = channels + + if dim == 1: + self.conv = F.conv1d + elif dim == 2: + self.conv = F.conv2d + elif dim == 3: + self.conv = F.conv3d + else: + raise RuntimeError( + 'Only 1, 2 and 3 dimensions are supported. Received {}.'.format(dim) + ) + + def forward(self, input): + """ + Apply gaussian filter to input. + Arguments: + input (torch.Tensor): Input to apply gaussian filter on. + Returns: + filtered (torch.Tensor): Filtered output. + """ + return self.conv(input, weight=self.weight.to(input.dtype), groups=self.groups) + +# based on diffusers/models/attention_processor.py Attention head_to_batch_dim +def head_to_batch_dim(x, heads, out_dim=3): + head_size = heads + if x.ndim == 3: + + batch_size, seq_len, dim = x.shape + extra_dim = 1 + else: + batch_size, extra_dim, seq_len, dim = x.shape + x = x.reshape(batch_size, seq_len * extra_dim, head_size, dim // head_size) + x = x.permute(0, 2, 1, 3) + if out_dim == 3: + x = x.reshape(batch_size * head_size, seq_len * extra_dim, dim // head_size) + return x + + +# based on diffusers/models/attention_processor.py Attention batch_to_head_dim +def batch_to_head_dim(x, heads): + head_size = heads + batch_size, seq_len, dim = x.shape + x = x.reshape(batch_size // head_size, head_size, seq_len, dim) + x = x.permute(0, 2, 1, 3).reshape(batch_size // head_size, seq_len, dim * head_size) + return x + + +def average_over_head_dim(x, heads): + x = rearrange(x, '(b h) s t -> b h s t', h=heads).mean(1) + return x + + +import torch.nn.functional as F +from einops import rearrange +def get_mask(attn_modules, scfg_params: SCFGStateParams, r, latent_size): + """ Aggregates the attention across the different layers and heads at the specified resolution. + In the original paper, r is a hyper-parameter set to 4. + Arguments: + attn_modules: List of attention modules + scfg_params: SCFGStateParams + r: int - + latent_size: tuple + + """ + height = scfg_params.height + width = scfg_params.width + max_dims = height * width + latent_size = latent_size[-2:] + module_attn_sizes = set() + + key_corss = f"r{r}_cross" + key_self = f"r{r}_self" + + + # The maximum value of the sizes of attention map to aggregate + max_r = r + max_sizes = r + + # The current number of attention map resolutions aggregated + attnmap_r = 0 + + r_r = 1 + new_ca = 0 + new_fore=0 + a_n=0 + # corresponds to diffusers pipe.unet.config.sample_size + # sample_size = 64 + # get a layer wise mapping + attention_store_proxy = {"r2_cross": [], "r4_cross": [], "r8_cross": [], "r16_cross": [], + "r2_self": [], "r4_self": [], "r8_self": [], "r16_self": []} + for module in attn_modules: + module_type = 'cross' if 'attn2' in module.network_layer_name else 'self' + + to_q_map = getattr(module, 'scfg_last_to_q_map', None) + to_k_map = getattr(module, 'scfg_last_to_k_map', None) + # self-attn + if to_k_map is None: + to_k_map = to_q_map + + to_q_map = prepare_attn_map(to_q_map, module.heads) + to_k_map = prepare_attn_map(to_k_map, module.heads) + + module_attn_size = to_q_map.size(1) + module_attn_sizes.add(module_attn_size) + downscale_h = int((module_attn_size * (height / width)) ** 0.5) + downscale_w = module_attn_size // downscale_h + module_key = f"r{module_attn_size}_{module_type}" + + attn_probs = get_attention_scores(to_q_map, to_k_map, to_q_map.dtype) + + if module_type == 'self': + del module.scfg_last_to_q_map + else: + del module.scfg_last_to_q_map, module.scfg_last_to_k_map + + if module_key not in attention_store_proxy: + attention_store_proxy[module_key] = [] + try: + attention_store_proxy[module_key].append(attn_probs) + except KeyError: + continue + + module_attn_sizes = sorted(list(module_attn_sizes)) + attention_maps = attention_store_proxy + + curr_r = module_attn_sizes.pop(0) + while curr_r != None and attnmap_r < max_sizes: + key_corss = f"r{curr_r}_cross" + key_self = f"r{curr_r}_self" + + if key_self not in attention_maps.keys() or key_corss not in attention_maps.keys(): + next_r = module_attn_sizes.pop(0) + attnmap_r += 1 + curr_r = next_r + continue + if len(attention_maps[key_self]) == 0 or len(attention_maps[key_corss]) == 0: + curr_r = module_attn_sizes.pop(0) + attnmap_r += 1 + curr_r = next_r + continue + + sa = torch.stack(attention_maps[key_self], dim=1) + ca = torch.stack(attention_maps[key_corss], dim=1) + attn_num = sa.size(1) + sa = rearrange(sa, 'b n h w -> (b n) h w') + ca = rearrange(ca, 'b n h w -> (b n) h w') + + curr = 0 # b hw c=hw + curr +=sa + + # 4.1.2 Self-Attentiion + ssgc_sa = curr + ssgc_n = max_r + + # summation from r=2 to R, we set ssgc_sa to curr which would be sa^1 + # major memory hog + # active_bytes peak from 3.41G to 4.04G + # reserved_bytes peak from 3.70G to 4.64G + # optimization 1: active 4.03G -> 3.72G = 0.31G, reserved 4.64G -> 4.16G = 0.48G + for r_value in range(1, ssgc_n): + r_pow = r_value + 1 + curr @= sa # optimization 1 +# curr = torch.linalg.matrix_power(sa, r_pow) # sa^r + ssgc_sa += curr + + ssgc_sa/=ssgc_n + sa = ssgc_sa + + ########smoothing ca + ca = sa@ca # b hw c + + hw = ca.size(1) + + downscale_h = round((hw * (height / width)) ** 0.5) + + ca = rearrange(ca, 'b (h w) c -> b c h w', h=downscale_h ) + + # Scale the attention map to the expected size + max_size = latent_size + scale_factor = [ + max_size[0] / ca.shape[-2], + max_size[1] / ca.shape[-1] + ] + mode = 'bilinear' #'nearest' # + ca = F.interpolate(ca, scale_factor=scale_factor, mode=mode) # b 77 32 32 + + #####Gaussian Smoothing + #kernel_size = 3 + #sigma = 0.5 + #smoothing = GaussianSmoothing(channels=1, kernel_size=kernel_size, sigma=sigma, dim=2).to(ca.device) + smoothing = scfg_params.gaussian_smoothing + channel = ca.size(1) + ca= rearrange(ca, ' b c h w -> (b c) h w' ).unsqueeze(1) + ca = F.pad(ca, (1, 1, 1, 1), mode='reflect') + ca = smoothing(ca.float()).squeeze(1) + ca = rearrange(ca, ' (b c) h w -> b c h w' , c= channel) + + ca_norm = ca/(ca.mean(dim=[2,3], keepdim=True)+torch.finfo(ca.dtype).eps) ### spatial normlization + + new_ca+=rearrange(ca_norm, '(b n) c h w -> b n c h w', n=attn_num).sum(1) + + fore_ca = torch.stack([ca[:,0],ca[:,1:].sum(dim=1)], dim=1) + froe_ca_norm = fore_ca/fore_ca.mean(dim=[2,3], keepdim=True) ### spatial normlization + new_fore += rearrange(froe_ca_norm, '(b n) c h w -> b n c h w', n=attn_num).sum(1) + a_n+=attn_num + + if len(module_attn_sizes) > 0: + curr_r = module_attn_sizes.pop(0) + else: + curr_r = None + attnmap_r += 1 + # r_r *= 2 + + # optimization 2: memory savings: 3.09G - 2.47G = 0.62G + del ca_norm, froe_ca_norm, fore_ca + + # no memory savings + del attention_maps + del sa, ca, ssgc_sa, ssgc_n, curr + + # variables used from above: + # new_ca, new_fore, a_n + new_ca = new_ca/a_n + new_fore = new_fore/a_n + _,new_ca = new_ca.chunk(2, dim=0) #[1] + fore_ca, _ = new_fore.chunk(2, dim=0) + + max_ca, inds = torch.max(new_ca[:,:], dim=1) + max_ca = max_ca.unsqueeze(1) # + ca_mask = (new_ca==max_ca).float() # b 77/10 16 16 + + max_fore, inds = torch.max(fore_ca[:,:], dim=1) + max_fore = max_fore.unsqueeze(1) # + fore_mask = (fore_ca==max_fore).float() # b 77/10 16 16 + fore_mask = 1.0-fore_mask[:,:1] # b 1 16 16 + + # no memory savings + del new_ca, new_fore, a_n, max_ca, max_fore, inds + + return [ ca_mask, fore_mask] + + +def prepare_attn_map(to_k_map, heads): + to_k_map = head_to_batch_dim(to_k_map, heads) + to_k_map = average_over_head_dim(to_k_map, heads) + to_k_map = torch.stack([to_k_map[0], to_k_map[0]], dim=0) + return to_k_map + + +def get_attention_scores(to_q_map, to_k_map, dtype): + """ Calculate the attention scores for the given query and key maps + Arguments: + to_q_map: torch.Tensor - query map + to_k_map: torch.Tensor - key map + dtype: torch.dtype - data type of the tensor + Returns: + torch.Tensor - attention scores + """ + # based on diffusers models/attention.py "get_attention_scores" + # use in place operations vs. softmax to save memory: https://stackoverflow.com/questions/53732209/torch-in-place-operations-to-save-memory-softmax + # 512x: 2.65G -> 2.47G + # attn_probs = attn_scores.softmax(dim=-1).to(device=shared.device, dtype=to_q_map.dtype) + + attn_probs = to_q_map @ to_k_map.transpose(-1, -2) + + # avoid nan by converting to float32 and subtracting max + attn_probs = attn_probs.to(dtype=torch.float32) # + attn_probs -= torch.max(attn_probs) + + torch.exp(attn_probs, out = attn_probs) + summed = attn_probs.sum(dim=-1, keepdim=True, dtype=torch.float32) + attn_probs /= summed + + attn_probs = attn_probs.to(dtype=dtype) + + return attn_probs \ No newline at end of file diff --git a/extensions/sd-webui-incantations/scripts/smoothed_energy_guidance.py b/extensions/sd-webui-incantations/scripts/smoothed_energy_guidance.py new file mode 100644 index 0000000000000000000000000000000000000000..c7d446bf4d1ee209c26e977d5d067d9366c3b02b --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/smoothed_energy_guidance.py @@ -0,0 +1,321 @@ +import logging +from os import environ +import math + +import modules.scripts as scripts +import gradio as gr + +from modules import script_callbacks +from modules.script_callbacks import CFGDenoiserParams, AfterCFGCallbackParams +from modules.processing import StableDiffusionProcessing +from modules import shared + +from scripts.ui_wrapper import UIWrapper +from scripts.incant_utils import module_hooks + +import torch +from torch.nn import functional as F + +logger = logging.getLogger(__name__) +logger.setLevel(environ.get("SD_WEBUI_LOG_LEVEL", logging.INFO)) + +incantations_debug = environ.get("INCANTAIONS_DEBUG", False) + +""" +An unofficial implementation of "Smoothed Energy Guidance for SDXL" for Automatic1111 WebUI. + +@article{hong2024smoothed, + title={Smoothed Energy Guidance: Guiding Diffusion Models with Reduced Energy Curvature of Attention}, + author={Hong, Susung}, + journal={arXiv preprint arXiv:2408.00760}, + year={2024} +} + +Parts of the code are based off the author's official implementation at https://github.com/SusungHong/SEG-SDXL + +Author: v0xie +GitHub URL: https://github.com/v0xie/sd-webui-incantations + +""" + + +handles = [] +global_scale = 1 + +class SEGStateParams: + def __init__(self): + self.seg_active: bool = False # SEG guidance scale + self.seg_scale: int = -1 # SEG guidance scale + self.seg_blur_sigma: float = 1.0 + self.seg_blur_threshold: float = 15.0 # 2^13 ~= 8192 + self.seg_start_step: int = 0 + self.seg_end_step: int = 150 + self.crossattn_modules = [] # callable lambda + + +class SEGExtensionScript(UIWrapper): + def __init__(self): + self.cached_c = [None, None] + self.paste_field_names = [] + self.infotext_fields = [] + self.handles = [] + + # Extension title in menu UI + def title(self) -> str: + return "Smoothed Energy Guidance" + + # Decide to show menu in txt2img or img2img + def show(self, is_img2img): + return scripts.AlwaysVisible + + # Setup menu ui detail + def setup_ui(self, is_img2img) -> list: + with gr.Accordion('Smoothed Energy Guidance', open=False): + active = gr.Checkbox(value=False, default=False, label="Active", elem_id='seg_active', info="Recommended to keep CFG Scale fixed at 3.0, use Sigma to adjust.") + with gr.Row(): + seg_blur_sigma = gr.Slider(value = 11.0, minimum = 0.0, maximum = 11.0, step = 0.5, label="SEG Blur Sigma", elem_id = 'seg_blur_sigma', info="Exponential (2^n). Values >= 11 are infinite blur") + with gr.Row(): + start_step = gr.Slider(value = 0, minimum = 0, maximum = 150, step = 1, label="Start Step", elem_id = 'seg_start_step', info="") + end_step = gr.Slider(value = 150, minimum = 0, maximum = 150, step = 1, label="End Step", elem_id = 'seg_end_step', info="") + + params = [active, seg_blur_sigma, start_step, end_step] + + self.infotext_fields = [ + (active, lambda d: gr.Checkbox.update(value='SEG Active' in d)), + (seg_blur_sigma, 'SEG Blur Sigma'), + (start_step, 'SEG Start Step'), + (end_step, 'SEG End Step'), + ] + for p in params: + p.do_not_save_to_config = True + self.paste_field_names.append(p.elem_id) + + return params + + def process_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + self.seg_process_batch(p, *args, **kwargs) + + def seg_process_batch(self, p: StableDiffusionProcessing, active, seg_blur_sigma, start_step, end_step, *args, **kwargs): + # cleanup previous hooks always + script_callbacks.remove_current_script_callbacks() + self.remove_all_hooks() + + active = getattr(p, "seg_active", active) + if active is False: + return + seg_blur_sigma = getattr(p, "seg_blur_sigma", seg_blur_sigma) + if seg_blur_sigma == 0.0: + logger.info("SEG Blur Sigma is 0, skipping SEG") + return + start_step = getattr(p, "seg_start_step", start_step) + end_step = getattr(p, "seg_end_step", end_step) + + if active: + p.extra_generation_params.update({ + "SEG Active": active, + "SEG Blur Sigma": seg_blur_sigma, + "SEG Start Step": start_step, + "SEG End Step": end_step, + }) + self.create_hook(p, active, seg_blur_sigma, start_step, end_step) + + def create_hook(self, p: StableDiffusionProcessing, active, seg_blur_sigma, start_step, end_step, *args, **kwargs): + # Create a list of parameters for each concept + seg_params = SEGStateParams() + + # Add to p's incant_cfg_params + if not hasattr(p, 'incant_cfg_params'): + logger.error("No incant_cfg_params found in p") + p.incant_cfg_params['seg_params'] = seg_params + + seg_params.seg_active = active + seg_params.seg_blur_sigma = seg_blur_sigma + seg_params.seg_blur_threshold = 10.5 + seg_params.seg_start_step = start_step + seg_params.seg_end_step = end_step + + # Get all the qv modules + self_attn_modules = self.get_cross_attn_modules() + if len(self_attn_modules) == 0: + logger.error("No self attention modules found, cannot proceed with SEG") + return + seg_params.crossattn_modules = self_attn_modules + + cfg_denoise_lambda = lambda callback_params: self.on_cfg_denoiser_callback(callback_params, seg_params) + unhook_lambda = lambda _: self.unhook_callbacks(seg_params) + + if seg_params.seg_active: + self.ready_hijack_forward(seg_params.crossattn_modules, seg_blur_sigma, seg_params.seg_blur_threshold, p.height, p.width) + + logger.debug('Hooked callbacks') + script_callbacks.on_cfg_denoiser(cfg_denoise_lambda) + script_callbacks.on_script_unloaded(unhook_lambda) + + def postprocess_batch(self, p, *args, **kwargs): + self.seg_postprocess_batch(p, *args, **kwargs) + + def seg_postprocess_batch(self, p, active, seg_blur_sigma, start_step, end_step, *args, **kwargs): + script_callbacks.remove_current_script_callbacks() + + logger.debug('Removed script callbacks') + active = getattr(p, "seg_active", active) + if active is False: + return + + def remove_all_hooks(self): + self_attn_modules = self.get_cross_attn_modules() + for module in self_attn_modules: + module_hooks.modules_remove_field(module.to_q, 'seg_enable') + module_hooks.modules_remove_field(module.to_q, 'seg_parent_module') + module_hooks.remove_module_forward_hook(module.to_q, 'seg_to_q_hook') + + def unhook_callbacks(self, seg_params: SEGStateParams): + global handles + return + + def ready_hijack_forward(self, selfattn_modules, seg_blur_sigma, seg_blur_threshold, height, width): + for module in selfattn_modules: + module_hooks.modules_add_field(module.to_q, 'seg_enable', False) + module_hooks.modules_add_field(module.to_q, 'seg_parent_module', [module]) + + def seg_to_q_hook(module, input, kwargs, output): + if not hasattr(module, 'seg_enable'): + return + if not module.seg_enable: + return + batch_size, seq_len, inner_dim = input[0].shape + h = module.seg_parent_module[0].heads + head_dim = inner_dim // h + + module_attn_size = seq_len + downscale_h = int((module_attn_size * (height / width)) ** 0.5) + downscale_w = module_attn_size // downscale_h + + # actual sigma value is calculated as 2 ^ sigma + is_inf_blur = seg_blur_sigma > seg_blur_threshold + blur_sigma_exp = 2 ** seg_blur_sigma + kernel_size = math.ceil(6 * blur_sigma_exp) + 1 - math.ceil(6 * blur_sigma_exp) % 2 + + q_uncond, q= output.chunk(2, dim=0) + q = q.view(batch_size//2, -1, h, head_dim).transpose(1, 2) # (batch, num_heads, seq_len, head_dim) + q = q.permute(0, 1, 3, 2).reshape(batch_size//2 * h, head_dim, downscale_h, downscale_w) # (batch * num_heads, head_dim, height, width) + + if is_inf_blur: + q = gaussian_blur_inf(q, 1.0, blur_sigma_exp) + else: + q = gaussian_blur_2d(q, kernel_size, blur_sigma_exp) + + q = q.reshape(batch_size // 2, h, head_dim, downscale_h * downscale_w) # (batch, num_heads, head_dim, seq_len) + q = q.view(batch_size // 2, h * head_dim, seq_len).transpose(1, 2) # (batch, inner_dim, seq_len) + q = torch.cat((q_uncond, q), dim=0) + + return q + + # Create hooks + for module in selfattn_modules: + module_hooks.module_add_forward_hook(module.to_q, seg_to_q_hook, hook_type="forward", with_kwargs=True) + + def get_middle_block_modules(self): + """ Get all attention modules from the middle block + Refere to page 22 of the SEG paper, Appendix A.2 + + """ + middle_block_modules = module_hooks.get_modules( + network_layer_name_filter = 'middle_block_', + module_name_filter = 'CrossAttention' + ) + middle_block_modules = [m for m in middle_block_modules if 'attn1' in m.network_layer_name] + return middle_block_modules + + def get_cross_attn_modules(self): + """ Get all cross attention modules """ + return self.get_middle_block_modules() + + def on_cfg_denoiser_callback(self, params: CFGDenoiserParams, seg_params: SEGStateParams): + # always unhook + self.unhook_callbacks(seg_params) + if not seg_params.seg_active: + return + + in_interval = seg_params.seg_start_step <= params.sampling_step <= seg_params.seg_end_step + for module in seg_params.crossattn_modules: + if hasattr(module.to_q, 'seg_enable'): + module.to_q.seg_enable = in_interval + + def cfg_after_cfg_callback(self, params: AfterCFGCallbackParams, seg_params: SEGStateParams): + pass + + def get_xyz_axis_options(self) -> dict: + xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ in ("xyz_grid.py", "scripts.xyz_grid")][0].module + extra_axis_options = { + xyz_grid.AxisOption("[SEG] Active", str, seg_apply_override('seg_active', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[SEG] SEG Blur Sigma", float, seg_apply_field("seg_blur_sigma")), + xyz_grid.AxisOption("[SEG] SEG Start Step", int, seg_apply_field("seg_start_step")), + xyz_grid.AxisOption("[SEG] SEG End Step", int, seg_apply_field("seg_end_step")), + } + return extra_axis_options + + +# from modules/sd_samplers_cfg_denoiser.py:187-195 +def get_make_condition_dict_fn(text_uncond): + if shared.sd_model.model.conditioning_key == "crossattn-adm": + make_condition_dict = lambda c_crossattn, c_adm: {"c_crossattn": [c_crossattn], "c_adm": c_adm} + else: + if isinstance(text_uncond, dict): + make_condition_dict = lambda c_crossattn, c_concat: {**c_crossattn, "c_concat": [c_concat]} + else: + make_condition_dict = lambda c_crossattn, c_concat: {"c_crossattn": [c_crossattn], "c_concat": [c_concat]} + return make_condition_dict + + +# XYZ Plot +# Based on @mcmonkey4eva's XYZ Plot implementation here: https://github.com/mcmonkeyprojects/sd-dynamic-thresholding/blob/master/scripts/dynamic_thresholding.py +def seg_apply_override(field, boolean: bool = False): + def fun(p, x, xs): + if boolean: + x = True if x.lower() == "true" else False + setattr(p, field, x) + if not hasattr(p, "seg_active"): + setattr(p, "seg_active", True) + if 'cfg_interval_' in field and not hasattr(p, "cfg_interval_enable"): + setattr(p, "cfg_interval_enable", True) + return fun + + +def seg_apply_field(field): + def fun(p, x, xs): + if not hasattr(p, "seg_active"): + setattr(p, "seg_active", True) + setattr(p, field, x) + return fun + + +# Gaussian blur +# taken from https://github.com/SusungHong/SEG-SDXL/blob/master/pipeline_seg.py +def gaussian_blur_2d(img, kernel_size, sigma): + height = img.shape[-1] + kernel_size = min(kernel_size, height - (height % 2 - 1)) + ksize_half = (kernel_size - 1) * 0.5 + + x = torch.linspace(-ksize_half, ksize_half, steps=kernel_size) + + pdf = torch.exp(-0.5 * (x / sigma).pow(2)) + + x_kernel = pdf / pdf.sum() + x_kernel = x_kernel.to(device=img.device, dtype=img.dtype) + + kernel2d = torch.mm(x_kernel[:, None], x_kernel[None, :]) + kernel2d = kernel2d.expand(img.shape[-3], 1, kernel2d.shape[0], kernel2d.shape[1]) + + padding = [kernel_size // 2, kernel_size // 2, kernel_size // 2, kernel_size // 2] + + img = F.pad(img, padding, mode="reflect") + img = F.conv2d(img, kernel2d, groups=img.shape[-3]) + + return img + + +def gaussian_blur_inf(img, kernel_size, sigma): + img[:] = img.mean(dim=(-2, -1), keepdim=True) + + return img \ No newline at end of file diff --git a/extensions/sd-webui-incantations/scripts/t2i_zero.py b/extensions/sd-webui-incantations/scripts/t2i_zero.py new file mode 100644 index 0000000000000000000000000000000000000000..35ed7280529eac15927daffb370772d390606fe2 --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/t2i_zero.py @@ -0,0 +1,737 @@ +import logging +from os import environ +import modules.scripts as scripts +import gradio as gr + +from functools import reduce + +from scripts.incant_utils import plot_tools +from einops import rearrange + + +from scripts.ui_wrapper import UIWrapper +from modules import script_callbacks +from modules import extra_networks +from modules import prompt_parser +from modules import sd_hijack +from modules.script_callbacks import CFGDenoiserParams +from modules.processing import StableDiffusionProcessing +from modules import shared + +import math +import torch +from torch.nn import functional as F +from torchvision.transforms import GaussianBlur + +from warnings import warn +from typing import Callable, Dict, Optional +from collections import OrderedDict + +logger = logging.getLogger(__name__) +logger.setLevel(environ.get("SD_WEBUI_LOG_LEVEL", logging.INFO)) + +""" + +Unofficial implementation of algorithms in "Multi-Concept T2I-Zero: Tweaking Only The Text Embeddings and Nothing Else" + +Also implements some "Reduce distortion in generation" algorithms from "Enhancing Semantic Fidelity in Text-to-Image Synthesis: Attention Regulation in Diffusion Models" + + +@misc{tunanyan2023multiconcept, + title={Multi-Concept T2I-Zero: Tweaking Only The Text Embeddings and Nothing Else}, + author={Hazarapet Tunanyan and Dejia Xu and Shant Navasardyan and Zhangyang Wang and Humphrey Shi}, + year={2023}, + eprint={2310.07419}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} + +@misc{zhang2024enhancing, + title={Enhancing Semantic Fidelity in Text-to-Image Synthesis: Attention Regulation in Diffusion Models}, + author={Yang Zhang and Teoh Tze Tzun and Lim Wei Hern and Tiviatis Sim and Kenji Kawaguchi}, + year={2024}, + eprint={2403.06381}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} + +Author: v0xie +GitHub URL: https://github.com/v0xie/sd-webui-incantations + +""" + +handles = [] +token_indices = [0] + +class T2I0StateParams: + def __init__(self): + self.attnreg: bool = False + self.ema_smoothing_factor: float = 2.0 + self.step_start : int = 0 + self.step_end : int = 25 + self.token_count: int = 0 + self.tokens: list[int] = [] # [0, 20] + self.window_size_period: int = 10 # [0, 20] + self.ctnms_alpha: float = 0.05 # [0., 1.] if abs value of difference between uncodition and concept-conditioned is less than this, then zero out the concept-conditioned values less than this + self.correction_threshold: float = 0.5 # [0., 1.] + self.correction_strength: float = 0.25 # [0., 1.) # larger bm is less volatile changes in momentum + self.strength = 1.0 + self.width = None + self.height = None + self.dims = [] + self.cbs_similarities: list = None # we can precompute this? + +class T2I0ExtensionScript(UIWrapper): + def __init__(self): + self.cached_c = [None, None] + self.handles = [] + + # Extension title in menu UI + def title(self) -> str: + return "Multi T2I-Zero" + + # Decide to show menu in txt2img or img2img + def show(self, is_img2img): + return scripts.AlwaysVisible + + # Setup menu ui detail + def setup_ui(self, is_img2img) -> list: + with gr.Accordion('Multi-Concept T2I-Zero', open=False): + active = gr.Checkbox(value=False, default=False, label="Active", elem_id='t2i0_active') + step_start = gr.Slider(value=1, minimum=0, maximum=150, default=1, step=1, label="Step Start", elem_id='t2i0_step_start', info="Start applying the correction at this step. Set to > 1 if using EMA.") + step_end = gr.Slider(value=25, minimum=0, maximum=150, default=1, step=1, label="Step End", elem_id='t2i0_step_end') + with gr.Row(): + tokens = gr.Textbox(visible=True, value="", label="Tokens", elem_id='t2i0_tokens', info="Comma separated list of indices of tokens to condition on. Leave empty to condition on all tokens. Example: For prompt 'A cat and a dog', 'A': 0, 'cat': 1, 'and': 2, 'a': 3, 'dog': 4") + with gr.Row(): + window_size = gr.Slider(value = 2, minimum = 0, maximum = 100, step = 1, label="Correction by Similarities Window Size", elem_id = 't2i0_window_size', info="Exclude contribution of tokens with indices += this value from the current token index.") + correction_threshold = gr.Slider(value = 0.5, minimum = 0., maximum = 1.0, step = 0.01, label="CbS Score Threshold", elem_id = 't2i0_correction_threshold', info="Filter dimensions with similarity below this threshold") + correction_strength = gr.Slider(value = 0.0, minimum = 0.0, maximum = 2.0, step = 0.01, label="CbS Correction Strength", elem_id = 't2i0_correction_strength', info="The strength of the correction") + with gr.Row(): + attnreg = gr.Checkbox(visible=False, value=False, default=False, label="Use Attention Regulation", elem_id='t2i0_use_attnreg') + ctnms_alpha = gr.Slider(value = 0.1, minimum = 0.0, maximum = 1.0, step = 0.01, label="Alpha for Cross-Token Non-Maximum Suppression", elem_id = 't2i0_ctnms_alpha', info="Contribution of the suppressed attention map, default 0.1") + ema_factor = gr.Slider(value=0.0, minimum=0.0, maximum=4.0, default=2.0, label="EMA Smoothing Factor", elem_id='t2i0_ema_factor', info="Based on method from [arXiv:2403.06381]") + active.do_not_save_to_config = True + attnreg.do_not_save_to_config = True + step_start.do_not_save_to_config = True + step_end.do_not_save_to_config = True + window_size.do_not_save_to_config = True + correction_threshold.do_not_save_to_config = True + correction_strength.do_not_save_to_config = True + attnreg.do_not_save_to_config = True + ctnms_alpha.do_not_save_to_config = True + ema_factor.do_not_save_to_config = True + tokens.do_not_save_to_config = True + self.infotext_fields = [ + (active, lambda d: gr.Checkbox.update(value='T2I-0 Active' in d)), + #(attnreg, lambda d: gr.Checkbox.update(value='T2I-0 AttnReg' in d)), + (window_size, 'T2I-0 Window Size'), + (step_start, 'T2I-0 Step Start'), + (step_end, 'T2I-0 Step End'), + (correction_threshold, 'T2I-0 CbS Score Threshold'), + (correction_strength, 'T2I-0 CbS Correction Strength'), + (ctnms_alpha, 'T2I-0 CTNMS Alpha'), + (ema_factor, 'T2I-0 CTNMS EMA Smoothing Factor'), + (tokens, 'T2I-0 Tokens'), + ] + self.paste_field_names = [ + 't2i0_active', + 't2i0_attnreg', + 't2i0_window_size', + 't2i0_ctnms_alpha', + 't2i0_correction_threshold', + 't2i0_correction_strength' + 't2i0_ema_factor', + 't2i0_step_start', + 't2i0_step_end', + 't2i0_tokens' + ] + return [active, attnreg, window_size, ctnms_alpha, correction_threshold, correction_strength, tokens, ema_factor, step_end, step_start] + + def process_batch(self, p: StableDiffusionProcessing, *args, **kwargs): + self.t2i0_process_batch(p, *args, **kwargs) + + def t2i0_process_batch(self, p: StableDiffusionProcessing, active, attnreg, window_size, ctnms_alpha, correction_threshold, correction_strength, tokens, ema_factor, step_end, step_start, *args, **kwargs): + active = getattr(p, "t2i0_active", active) + # use_attnreg = getattr(p, "t2i0_attnreg", attnreg) + ema_factor = getattr(p, "t2i0_ema_factor", ema_factor) + step_start = getattr(p, "t2i0_step_start", step_start) + step_end = getattr(p, "t2i0_step_end", step_end) + if active is False: + return + window_size = getattr(p, "t2i0_window_size", window_size) + ctnms_alpha = getattr(p, "t2i0_ctnms_alpha", ctnms_alpha) + correction_threshold = getattr(p, "t2i0_correction_threshold", correction_threshold) + correction_strength = getattr(p, "t2i0_correction_strength", correction_strength) + tokens = getattr(p, "t2i0_tokens", tokens) + p.extra_generation_params.update({ + "T2I-0 Active": active, + #"T2I-0 AttnReg": attnreg, + "T2I-0 Window Size": window_size, + "T2I-0 Step Start": step_start, + "T2I-0 Step End": step_end, + "T2I-0 CbS Score Threshold": correction_threshold, + "T2I-0 CbS Correction Strength": correction_strength, + "T2I-0 CTNMS Alpha": ctnms_alpha, + "T2I-0 CTNMS EMA Smoothing Factor": ema_factor, + "T2I-0 Tokens": tokens, + }) + + self.create_hook(p, active, attnreg, window_size, ctnms_alpha, correction_threshold, correction_strength, tokens, ema_factor, step_end, step_start, p.width, p.height) + + def parse_concept_prompt(self, prompt:str) -> list[str]: + """ + Separate prompt by comma into a list of concepts + TODO: parse prompt into a list of concepts using A1111 functions + >>> g = lambda prompt: self.parse_concept_prompt(prompt) + >>> g("") + [] + >>> g("apples") + ['apples'] + >>> g("apple, banana, carrot") + ['apple', 'banana', 'carrot'] + """ + if len(prompt) == 0: + return [] + return [x.strip() for x in prompt.split(",")] + + def create_hook(self, p, active, attnreg, window_size, ctnms_alpha, correction_threshold, correction_strength, tokens, ema_factor, step_end, step_start, width, height, *args, **kwargs): + # Sanity check + cross_attn_modules = self.get_cross_attn_modules() + if len(cross_attn_modules) == 0: + logger.error("No cross attention modules found, cannot run T2I-0") + return + + if len(tokens) > 0: + try: + token_indices = [int(x) for x in tokens.split(",")] + except ValueError: + logger.error("Invalid token indices, must be comma separated integers") + raise + else: + token_indices = [] + + + # Create a list of parameters for each concept + t2i0_params = [] + + #for _, strength in concept_conds: + params = T2I0StateParams() + params.attnreg = attnreg + params.ema_smoothing_factor = ema_factor + params.step_start = step_start + params.step_end = step_end + params.window_size_period = window_size + params.ctnms_alpha = ctnms_alpha + params.correction_threshold = correction_threshold + params.correction_strength = correction_strength + params.strength = 1.0 + params.width = width + params.height = height + params.dims = [width, height] + + params.token_count, _ = get_token_count(p.prompt, p.steps, True) + token_indices = [x+1 for x in token_indices if x >= 0 and x < params.token_count] + params.tokens = token_indices + + t2i0_params.append(params) + + + + # Use lambda to call the callback function with the parameters to avoid global variables + y = lambda params: self.on_cfg_denoiser_callback(params, t2i0_params) + # un = lambda params: self.unhook_callbacks() + + # Hook callbacks + if ctnms_alpha > 0: + self.ready_hijack_forward(ctnms_alpha, width, height, ema_factor, step_start, step_end, token_indices, params.token_count) + + logger.debug('Hooked callbacks') + script_callbacks.on_cfg_denoiser(y) + script_callbacks.on_script_unloaded(self.unhook_callbacks) + + def postprocess_batch(self, p, *args, **kwargs): + self.t2i0_postprocess_batch(p, *args, **kwargs) + + def t2i0_postprocess_batch(self, p, active, *args, **kwargs): + self.unhook_callbacks() + active = getattr(p, "t2i0_active", active) + if active is False: + return + + def unhook_callbacks(self): + global handles + logger.debug('Unhooked callbacks') + cross_attn_modules = self.get_cross_attn_modules() + for module in cross_attn_modules: + self.remove_field_cross_attn_modules(module, 't2i0_last_attn_map') + self.remove_field_cross_attn_modules(module, 't2i0_step') + self.remove_field_cross_attn_modules(module, 't2i0_step_start') + self.remove_field_cross_attn_modules(module, 't2i0_step_end') + self.remove_field_cross_attn_modules(module, 't2i0_ema_factor') + self.remove_field_cross_attn_modules(module, 't2i0_ema') + self.remove_field_cross_attn_modules(module, 'plot_num') + self.remove_field_cross_attn_modules(module, 't2i0_tokens') + self.remove_field_cross_attn_modules(module, 't2i0_token_count') + self.remove_field_cross_attn_modules(module, 't2i0_to_v_map') + self.remove_field_cross_attn_modules(module.to_k, 't2i0_parent_module') + self.remove_field_cross_attn_modules(module.to_v, 't2i0_parent_module') + _remove_all_forward_hooks(module, 'cross_token_non_maximum_suppression') + # _remove_all_forward_hooks(module, 'cross_token_non_maximum_suppression_pre') + # _remove_all_forward_hooks(module.to_k, 't2i0_to_k_hook') + _remove_all_forward_hooks(module.to_v, 't2i0_to_v_hook') + script_callbacks.remove_current_script_callbacks() + + def apply_attnreg(self, f, C, alpha, B, *args, **kwargs): + """ + Apply attention regulation on an embedding. + + Args: + f (Tensor): The embedding tensor of shape (n, d). + C (list): Indices of selected tokens. + alpha (float): Attnreg strength. + B (float): Lagrange multiplier B > 0 + gamma (int): Window size for the windowing function. + + Returns: + Tensor: The corrected embedding tensor. + """ + + n, d = f.shape + f_tilde = f.detach().clone() # Copy the embedding tensor + + # for token_idx, c in enumerate(C): + # pass + return f_tilde + + def correction_by_similarities(self, f, C, percentile, gamma, alpha, tokens=None, token_count=77): + """ + Apply the Correction by Similarities algorithm on embeddings. + + Args: + f (Tensor): The embedding tensor of shape (n, d). + C (list): Indices of selected tokens. + percentile (float): Percentile to use for score threshold. + gamma (int): Window size for the windowing function. + alpha (float): Correction strength. + tokens (list): List of token indices to condition on (default is all tokens if empty list). + + Returns: + Tensor: The corrected embedding tensor. + """ + if alpha == 0: + return f + + n, d = f.shape + + token_indices = tokens + min_idx = 1 + max_idx = min(token_count+1, n) + if token_indices is None: + token_indices = list(range(min_idx, max_idx)) + if token_indices == []: + token_indices = list(range(min_idx, max_idx)) + else: + token_indices = [x+1 for x in token_indices if x >= 0 and x < n] + + + f_tilde = f.detach().clone() # Copy the embedding tensor + + # Define a windowing function + def psi(c, gamma, n, dtype, device, min_idx, max_idx): + window = torch.zeros(n, dtype=dtype, device=device) + start = max(min_idx, c - gamma) + end = min(max_idx, c + gamma + 1) + window[start:end] = 1 + return window + + def threshold_filter(t, tau): + """ Threshold filter function + Filters product values below a threshold tau and normalizes them to leave only the most similar dimensions. + Arguments: + t: torch.Tensor - The tensor to threshold + tau: float - The threshold value + Returns: + bool: True if the value is above the threshold, False otherwise + """ + pass + + + + + for c in token_indices: + if c < 0 or c >= n: + continue + Sc = f[c] * f # Element-wise multiplication + + # calculate score threshold to filter out values under score threshold + # often there is a huge difference between the max and min values, so we use a log-like function instead + k = 10 + e= 2.718281 + pct_max = 1/(1+1e-10) + pct_min = 1e-16 + # max of 0.999... to 0.0000...1 + pct = min(pct_max, max(pct_min, 1 - e**(-k * percentile))) + + tau = torch.quantile(Sc, pct) + + Sc_tilde = Sc * (Sc > tau) # Apply threshold and filter + Sc_tilde /= Sc_tilde.max() # Normalize + + window = psi(c, gamma, n, Sc_tilde.dtype, Sc_tilde.device, min_idx, max_idx).unsqueeze(1) # Apply windowing function + + Sc_tilde *= window + f_c_tilde = torch.sum(Sc_tilde * f, dim=0) # Combine embeddings + f_tilde[c] = (1 - alpha) * f[c] + alpha * f_c_tilde # Blend embeddings + + return f_tilde + + def ready_hijack_forward(self, alpha, width, height, ema_factor, step_start, step_end, tokens, token_count): + """ Create a hook to modify the output of the forward pass of the cross attention module + Arguments: + alpha: float - The strength of the CTNMS correction, default 0.1 + width: int - The width of the final output image map + height: int - The height of the final output image map + ema_factor: float - EMA smoothing factor, default 2.0 + step_start: int - Wait to apply CTNMS until this step + step_end: int - The number of steps to apply the CTNMS correction, after which don't + tokens: list[int] - List of token indices to condition on + token_count: int - The number of tokens in the prompt + + Only modifies the output of the cross attention modules that get context (i.e. text embedding) + """ + cross_attn_modules = self.get_cross_attn_modules() + if len(cross_attn_modules) == 0: + logger.error("No cross attention modules found, cannot run T2I-0") + return + # add field for last_attn_map + plot_num = 0 + for module in cross_attn_modules: + self.add_field_cross_attn_modules(module, 't2i0_last_attn_map', None) + self.add_field_cross_attn_modules(module, 't2i0_step', int(-1)) + self.add_field_cross_attn_modules(module, 't2i0_step_start', int(step_start)) + self.add_field_cross_attn_modules(module, 't2i0_step_end', int(step_end)) + self.add_field_cross_attn_modules(module, 't2i0_ema', None) + self.add_field_cross_attn_modules(module, 't2i0_ema_factor', float(ema_factor)) + self.add_field_cross_attn_modules(module, 'plot_num', int(plot_num)) + self.add_field_cross_attn_modules(module, 't2i0_to_v_map', None) + self.add_field_cross_attn_modules(module.to_v, 't2i0_parent_module', [module]) + self.add_field_cross_attn_modules(module, 't2i0_token_count', int(token_count)) + self.add_field_cross_attn_modules(module, 'gaussian_blur', GaussianBlur(kernel_size=3, sigma=1).to(device=shared.device)) + if tokens is not None: + self.add_field_cross_attn_modules(module, 't2i0_tokens', torch.tensor(tokens).to(device=shared.device, dtype=torch.int64)) + else: + self.add_field_cross_attn_modules(module, 't2i0_tokens', None) + + plot_num += 1 + + # def cross_token_non_maximum_suppression_pre(module, args, kwargs): + # pass + # pass + + def cross_token_non_maximum_suppression(module, input, kwargs, output): + module.t2i0_step += 1 + + context = kwargs.get('context', None) + if context is None: + return + if context.shape[1] % 77 != 0: + logger.error("Context shape is not divisible by 77, cannot run T2I-0") + return + + current_step = module.t2i0_step + start_step = module.t2i0_step_start + end_step = module.t2i0_step_end + + # Select token indices, default is ALL tokens + token_count = module.t2i0_token_count + token_indices = module.t2i0_tokens + + if current_step > end_step and end_step > 0: + return + if current_step < start_step: + return + + batch_size, sequence_length, inner_dim = output.shape + + max_dims = width*height + factor = math.isqrt(max_dims // sequence_length) # should be a square of 2 + downscale_width = width // factor + downscale_height = height // factor + if downscale_width * downscale_height != sequence_length: + print(f"Error: Width: {width}, height: {height}, Downscale width: {downscale_width}, height: {downscale_height}, Factor: {factor}, Max dims: {max_dims}\n") + return + + # h = module.heads + # head_dim = inner_dim // h + dtype = output.dtype + device = output.device + + # Multiply text embeddings into visual embeddings + to_v_map = module.t2i0_to_v_map.detach().clone() + to_v_inner_dim = to_v_map.size(-2) + to_v_map = (to_v_map @ output.transpose(1, 2)).transpose(1, 2) + + to_v_attention_map = to_v_map.view(batch_size, downscale_height, downscale_width, to_v_inner_dim) + + # Original attention map + attention_map = output.view(batch_size, downscale_height, downscale_width, inner_dim) + + if token_indices is None: + selected_tokens = torch.arange(1, token_count, device=output.device) + elif len(token_indices) == 0: + selected_tokens = torch.arange(1, token_count, device=output.device) + else: + selected_tokens = module.t2i0_tokens + + if module.t2i0_ema is None: + module.t2i0_ema = output.detach().clone() + + # Extract the attention maps for the selected tokens + AC = to_v_attention_map[:, :, :, selected_tokens] # Extracting relevant attention maps + + # Extract and process the selected attention maps + # GaussianBlur expects the input [..., C, H, W] + gaussian_blur = module.gaussian_blur + AC = AC.permute(0, 3, 1, 2) + AC = gaussian_blur(AC) # Applying Gaussian smoothing + AC = AC.permute(0, 2, 3, 1) + + # Find the maximum contributing token for each pixel + M = torch.argmax(AC, dim=-1) + one_hot_M = F.one_hot(M, num_classes=to_v_attention_map.size(-1)).to(dtype=dtype, device=device) + + # the attention map is of shape [batch_size, height, width, inner_dim] + one_hot_M_z = rearrange(one_hot_M, 'b h w c -> b (h w) c') + one_hot_M_z = one_hot_M_z @ module.t2i0_to_v_map + one_hot_M_z = rearrange(one_hot_M_z, 'b (h w) c -> b h w c', h=downscale_height, w=downscale_width) + + suppressed_attention_map = one_hot_M_z * attention_map + + # Reshape back to original dimensions + suppressed_attention_map = suppressed_attention_map.view(batch_size, sequence_length, inner_dim) + + # Calculate the EMA of the suppressed attention map + if module.t2i0_ema_factor > 0: + ema = module.t2i0_ema + ema_factor = module.t2i0_ema_factor / (1 + current_step) + # Add the suppressed attention map to the EMA + ema = ema_factor * ema + (1 - ema_factor) * suppressed_attention_map + module.t2i0_ema = ema + out_tensor = (1 -alpha) * output + (alpha) * ema + #out_tensor = (1-alpha) * ema + alpha * suppressed_attention_map + else: + out_tensor = (1-alpha) * output + alpha * suppressed_attention_map + + return out_tensor + + def t2i0_to_k_hook(module, input, kwargs, output): + pass + pass + + def t2i0_to_v_hook(module, input, kwargs, output): + module.t2i0_parent_module[0].t2i0_to_v_map = output + + # Hook + for module in cross_attn_modules: + # handle = module.to_k.register_forward_hook(t2i0_to_k_hook, with_kwargs=True) + handle = module.to_v.register_forward_hook(t2i0_to_v_hook, with_kwargs=True) + handle = module.register_forward_hook(cross_token_non_maximum_suppression, with_kwargs=True) + # handle = module.register_forward_pre_hook(cross_token_non_maximum_suppression_pre, with_kwargs=True) + + def get_cross_attn_modules(self): + """ Get all cross attention modules """ + try: + m = shared.sd_model + nlm = m.network_layer_mapping + cross_attn_modules = [m for m in nlm.values() if 'CrossAttention' in m.__class__.__name__ and 'attn2' in m.network_layer_name] + return cross_attn_modules + except AttributeError: + logger.exception("AttributeError while getting cross attention modules") + return [] + except Exception: + logger.exception("Error while getting cross attention modules") + return [] + + def add_field_cross_attn_modules(self, module, field, value): + """ Add a field to a module if it doesn't exist """ + if not hasattr(module, field): + setattr(module, field, value) + + def remove_field_cross_attn_modules(self, module, field): + """ Remove a field from a module if it exists """ + if hasattr(module, field): + delattr(module, field) + + def on_cfg_denoiser_callback(self, params: CFGDenoiserParams, t2i0_params: list[T2I0StateParams]): + if isinstance(params.text_cond, dict): + text_cond = params.text_cond['crossattn'] # SD XL + else: + text_cond = params.text_cond # SD 1.5 + + sp = t2i0_params[0] + window_size = sp.window_size_period + correction_strength = sp.correction_strength + score_threshold = sp.correction_threshold + + step = params.sampling_step + step_start = sp.step_start + step_end = sp.step_end + + tokens = sp.tokens if sp.tokens is not None else [] + + + if step_start > step: + return + if step > step_end: + return + + for batch_idx, batch in enumerate(text_cond): + window = list(range(0, len(batch))) + f_bar = self.correction_by_similarities(batch, window, score_threshold, window_size, correction_strength, tokens) + if isinstance(params.text_cond, dict): + params.text_cond['crossattn'][batch_idx] = f_bar + else: + params.text_cond[batch_idx] = f_bar + return + + def get_xyz_axis_options(self) -> dict: + xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ in ("xyz_grid.py", "scripts.xyz_grid")][0].module + extra_axis_options = { + xyz_grid.AxisOption("[T2I-0] Active", str, t2i0_apply_override('t2i0_active', boolean=True), choices=xyz_grid.boolean_choice(reverse=True)), + xyz_grid.AxisOption("[T2I-0] Step Start", int, t2i0_apply_field("t2i0_step_start")), + xyz_grid.AxisOption("[T2I-0] Step End", int, t2i0_apply_field("t2i0_step_end")), + xyz_grid.AxisOption("[T2I-0] CbS Window Size", int, t2i0_apply_field("t2i0_window_size")), + xyz_grid.AxisOption("[T2I-0] CbS Score Threshold", float, t2i0_apply_field("t2i0_correction_threshold")), + xyz_grid.AxisOption("[T2I-0] CbS Correction Strength", float, t2i0_apply_field("t2i0_correction_strength")), + xyz_grid.AxisOption("[T2I-0] CTNMS Alpha", float, t2i0_apply_field("t2i0_ctnms_alpha")), + xyz_grid.AxisOption("[T2I-0] CTNMS EMA Smoothing Factor", float, t2i0_apply_field("t2i0_ema_factor")), + } + return extra_axis_options + + +def plot_attention_map(attention_map: torch.Tensor, title, x_label="X", y_label="Y", save_path=None, plot_type="default"): + """ Plots an attention map using matplotlib.pyplot + Arguments: + attention_map: Tensor - The attention map to plot + title: str - The title of the plot + x_label: str (optional) - The x-axis label + y_label: str (optional) - The y-axis label + save_path: str (optional) - The path to save the plot + Returns: + PIL.Image: The plot as a PIL image + """ + if attention_map.dim() == 3: + attention_map = attention_map.squeeze(0).mean(2) + + plot_tools.plot_attention_map(attention_map, title, x_label, y_label, save_path, plot_type) + +def debug_plot_attention_map(attention_map): + """ Plots an attention map using matplotlib.pyplot + Arguments: + attention_map: Tensor - The attention map to plot + title: str - The title of the plot + x_label: str (optional) - The x-axis label + y_label: str (optional) - The y-axis label + save_path: str (optional) - The path to save the plot + Returns: + PIL.Image: The plot as a PIL image + """ + + plot_attention_map( + attention_map, + "Debug Output", + save_path="F:\\incant\\temp\\AAA_out_temp.png" + ) + + +# XYZ Plot +# Based on @mcmonkey4eva's XYZ Plot implementation here: https://github.com/mcmonkeyprojects/sd-dynamic-thresholding/blob/master/scripts/dynamic_thresholding.py +def t2i0_apply_override(field, boolean: bool = False): + def fun(p, x, xs): + if boolean: + x = True if x.lower() == "true" else False + setattr(p, field, x) + return fun + +def t2i0_apply_field(field): + def fun(p, x, xs): + if not hasattr(p, "t2i0_active"): + p.t2i0_active = True + setattr(p, field, x) + return fun + + +# taken from modules/ui.py +def get_token_count(text, steps, is_positive: bool = True): + try: + text, _ = extra_networks.parse_prompt(text) + + if is_positive: + _, prompt_flat_list, _ = prompt_parser.get_multicond_prompt_list([text]) + else: + prompt_flat_list = [text] + + prompt_schedules = prompt_parser.get_learned_conditioning_prompt_schedules(prompt_flat_list, steps) + + except Exception: + # a parsing error can happen here during typing, and we don't want to bother the user with + # messages related to it in console + prompt_schedules = [[[steps, text]]] + + flat_prompts = reduce(lambda list1, list2: list1+list2, prompt_schedules) + prompts = [prompt_text for step, prompt_text in flat_prompts] + token_count, max_length = max([sd_hijack.model_hijack.get_prompt_lengths(prompt) for prompt in prompts], key=lambda args: args[0]) + return token_count, max_length + + +# thanks torch; removing hooks DOESN'T WORK +# thank you to @ProGamerGov for this https://github.com/pytorch/pytorch/issues/70455 +def _remove_all_forward_hooks( + module: torch.nn.Module, hook_fn_name: Optional[str] = None +) -> None: + """ + This function removes all forward hooks in the specified module, without requiring + any hook handles. This lets us clean up & remove any hooks that weren't property + deleted. + + Warning: Various PyTorch modules and systems make use of hooks, and thus extreme + caution should be exercised when removing all hooks. Users are recommended to give + their hook function a unique name that can be used to safely identify and remove + the target forward hooks. + + Args: + + module (nn.Module): The module instance to remove forward hooks from. + hook_fn_name (str, optional): Optionally only remove specific forward hooks + based on their function's __name__ attribute. + Default: None + """ + + if hook_fn_name is None: + warn("Removing all active hooks can break some PyTorch modules & systems.") + + + def _remove_hooks(m: torch.nn.Module, name: Optional[str] = None) -> None: + if hasattr(module, "_forward_hooks"): + if m._forward_hooks != OrderedDict(): + if name is not None: + dict_items = list(m._forward_hooks.items()) + m._forward_hooks = OrderedDict( + [(i, fn) for i, fn in dict_items if fn.__name__ != name] + ) + else: + m._forward_hooks: Dict[int, Callable] = OrderedDict() + + def _remove_child_hooks( + target_module: torch.nn.Module, hook_name: Optional[str] = None + ) -> None: + for _, child in target_module._modules.items(): + if child is not None: + _remove_hooks(child, hook_name) + _remove_child_hooks(child, hook_name) + + # Remove hooks from target submodules + _remove_child_hooks(module, hook_fn_name) + + # Remove hooks from the target module + _remove_hooks(module, hook_fn_name) diff --git a/extensions/sd-webui-incantations/scripts/ui_wrapper.py b/extensions/sd-webui-incantations/scripts/ui_wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..6685930f5115a679c45e1610ff6de0ef6ace067c --- /dev/null +++ b/extensions/sd-webui-incantations/scripts/ui_wrapper.py @@ -0,0 +1,43 @@ +import os + +class UIWrapper: + def __init__(self): + self.infotext_fields: list = [] + self.paste_field_names: list = [] + + def title(self) -> str: + raise NotImplementedError + + def setup_ui(self, is_img2img) -> list: + raise NotImplementedError + + def get_infotext_fields(self) -> list: + return self.infotext_fields + + def get_paste_field_names(self) -> list: + return self.paste_field_names + + def before_process(self, p, *args, **kwargs): + pass + + def process(self, p, *args, **kwargs): + pass + + def before_process_batch(self, p, *args, **kwargs): + pass + + def process_batch(self, p, *args, **kwargs): + pass + + def postprocess_batch(self, p, *args, **kwargs): + pass + + def unhook_callbacks(self) -> None: + pass + + def get_xyz_axis_options(self) -> dict: + raise NotImplementedError + +def arg(p, field_name: str, variable_name:str, default=None, **kwargs): + """ Get argument from field_name or variable_name, or default if not found """ + return getattr(p, field_name, kwargs.get(variable_name, None)) diff --git a/extensions/sd-webui-infinite-image-browsing/.env.example b/extensions/sd-webui-infinite-image-browsing/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..36aae60cebd7ee4ec4921ef3b6913adf2d2538ba --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/.env.example @@ -0,0 +1,41 @@ +# This document is used for additional control over the security and privacy of IIB. By default, IIB is already sufficiently secure, and you do not need to take any additional actions. +# Copy this file and rename it to ".env" . Then, fill in the necessary values for your specific use case. +# Remember to never share your .env file with anyone else as it may contain sensitive information. + +# This attribute is used for authentication. If you input a key here, it will be validated for authentication purposes. +# It will be prompted to enter your key when you open the extension. If the authentication fails, all your requests will be rejected. +IIB_SECRET_KEY= + +# Configuring the server-side language for this extension, +# including the tab title and most of the server-side error messages returned. Options are 'zh', 'en', or 'auto'. +# If you want to configure the language for the front-end pages, please set it on the extension's global settings page. +IIB_SERVER_LANG=auto + +# This configuration parameter specifies the maximum number of database file backups for the IIB . +IIB_DB_FILE_BACKUP_MAX=8 + +# Set the cache directory for IIB, including image cache and video cover cache. +# The default is the system's temporary directory, but if you want to specify a custom directory, set it here. +# You can use --generate_video_cover and --generate_image_cache to pre-generate the cache. +# IIB_CACHE_DIR= + + +# ---------------------------- ACCESS_CONTROL ---------------------------- + +# Used to configure whether to enable access control to the file system. +# If enabled, only access to the provided pre-set folders (including those provided by sd-webui and manually \ +# added to Quick Move or specified via IIB_ACCESS_CONTROL_ALLOWED_PATHS) will be allowed. +# The available options are 'enable', 'disable', and 'auto'. +# The default value is 'auto', which will be determined based on the command-line parameters used to start sd-webui (such as --server-name, --share, --listen). +IIB_ACCESS_CONTROL=auto + +# This variable is used to define a list of allowed paths for the application to access when access control mode is enabled. +# It can be set to a comma-separated string of file paths or directory paths, representing the resources that are allowed to be accessed by the application. +# In addition, if sd_webui_config or sd_webui_dir has been configured, or if you're running this repository as an extension of sd-webui, +# you can use the following shortcuts (txt2img, img2img, extra, save) as values for the ALLOWED_PATHS variable. +# IIB_ACCESS_CONTROL_ALLOWED_PATHS=save,extra,/output ...etc + +# This variable is used to control fine-grained access control for different types of requests, but only if access control mode is enabled. +# It can be set to a string value that represents a specific permission or set of permissions, such as "read-only", "write-only", "read-write", or "no-access". +# This variable can be used to restrict access to certain API endpoints or data sources based on the permissions required by the user. +# IIB_ACCESS_CONTROL_PERMISSION=read-write diff --git a/extensions/sd-webui-infinite-image-browsing/.github/FUNDING.yml b/extensions/sd-webui-infinite-image-browsing/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..4173aa12afce4920be16c9243d49aef687c10964 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +ko_fi: zanllp +custom: https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/.github/wechat_funding.jpg diff --git a/extensions/sd-webui-infinite-image-browsing/.github/wechat_funding.jpg b/extensions/sd-webui-infinite-image-browsing/.github/wechat_funding.jpg new file mode 100644 index 0000000000000000000000000000000000000000..02eed2dba13e10167c4c7a256cc3db2d0c22532a Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/.github/wechat_funding.jpg differ diff --git a/extensions/sd-webui-infinite-image-browsing/.github/workflows/tauri_app_build.yml b/extensions/sd-webui-infinite-image-browsing/.github/workflows/tauri_app_build.yml new file mode 100644 index 0000000000000000000000000000000000000000..806d916907d0d5d793cc3abf2cfa37126d530e44 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/.github/workflows/tauri_app_build.yml @@ -0,0 +1,205 @@ +name: tauri_app_build + +on: + push: + branches: + - 'releases/**' + +jobs: + build: + strategy: + matrix: + os: [windows-latest,ubuntu-20.04] + + runs-on: ${{ matrix.os }} + + permissions: + contents: write + steps: + - name: Check-out repository + uses: actions/checkout@v3 + + - run: echo "VERSION=$(jq -r '.package.version' vue/src-tauri/tauri.conf.json)" >> "$GITHUB_ENV" + if: matrix.os == 'ubuntu-20.04' + - run: echo "VERSION=$(jq -r '.package.version' vue/src-tauri/tauri.conf.json)" >> $env:GITHUB_ENV + if: matrix.os == 'windows-latest' + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + cache: 'pip' + cache-dependency-path: | + **/requirements*.txt + + - name: Install Dependencies + run: | + pip install -r requirements.txt + + - name: Build Executable + uses: Nuitka/Nuitka-Action@main + with: + nuitka-version: main + script-name: app.py + output-file: iib_api_server + output-dir: out + + - name: Upload Server Artifacts + uses: actions/upload-artifact@v3 + with: + name: iib_api_server-${{ env.VERSION }}-${{ runner.os }} + path: | + out/iib_api_server.exe + out/iib_api_server + + - run: mv out/iib_api_server.exe vue/src-tauri/iib_api_server-x86_64-pc-windows-msvc.exe + if: matrix.os == 'windows-latest' + + - run: mv out/iib_api_server vue/src-tauri/iib_api_server-x86_64-unknown-linux-gnu + if: matrix.os == 'ubuntu-20.04' + + - name: Install frontend dependencies + run: yarn install + working-directory: vue + + - name: Rust setup + uses: dtolnay/rust-toolchain@stable + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: './vue/src-tauri -> target' + + - name: Install dependencies (ubuntu only) + if: matrix.os == 'ubuntu-20.04' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libayatana-appindicator3-dev librsvg2-dev patchelf + + - name: Build the app + run: | + yarn tauri-build + working-directory: vue + + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: bundle-${{ env.VERSION }}-${{ runner.os }} + path: | + vue/src-tauri/target/release/bundle/nsis/Infinite Image Browsing_${{ env.VERSION }}_x64-setup.exe + vue/src-tauri/target/release/bundle/deb/infinite-image-browsing_${{ env.VERSION }}_amd64.deb + + + build-by-pyinstaller: + strategy: + matrix: + os: [windows-latest] + + runs-on: ${{ matrix.os }} + + permissions: + contents: write + steps: + - name: Check-out repository + uses: actions/checkout@v3 + + - run: echo "VERSION=$(jq -r '.package.version' vue/src-tauri/tauri.conf.json)" >> $env:GITHUB_ENV + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + cache: 'pip' + cache-dependency-path: | + **/requirements*.txt + + - name: Install Dependencies + run: | + pip install -r requirements.txt + + - uses: sayyid5416/pyinstaller@v1 + with: + spec: 'app.py' + upload_exe_with_name: 'My executable' + options: --onefile + + - run: mv dist/app.exe vue/src-tauri/iib_api_server-x86_64-pc-windows-msvc.exe + + - name: Install frontend dependencies + run: yarn install + working-directory: vue + + - name: Rust setup + uses: dtolnay/rust-toolchain@stable + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: './vue/src-tauri -> target' + + - name: Install dependencies (ubuntu only) + if: matrix.os == 'ubuntu-20.04' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libayatana-appindicator3-dev librsvg2-dev patchelf + + - name: Build the app + run: | + yarn tauri-build + working-directory: vue + + - run: | + cd vue/src-tauri/target/release/bundle/nsis + mv "Infinite Image Browsing_${{ env.VERSION }}_x64-setup.exe" "Infinite Image Browsing_${{ env.VERSION }}_x64-setup-pyinstaller.exe" + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: bundle-${{ env.VERSION }}-${{ runner.os }} + path: | + vue/src-tauri/target/release/bundle/nsis/Infinite Image Browsing_${{ env.VERSION }}_x64-setup-pyinstaller.exe + + release: + needs: [build-by-pyinstaller, build] + runs-on: ubuntu-latest + + permissions: + contents: write + steps: + - name: Check-out repository + uses: actions/checkout@v3 + - run: echo "VERSION=$(jq -r '.package.version' vue/src-tauri/tauri.conf.json)" >> "$GITHUB_ENV" + + - name: Delete drafts + uses: hugo19941994/delete-draft-releases@v1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/download-artifact@v3 + with: + name: bundle-${{ env.VERSION }}-Windows + path: artifacts + + - uses: actions/download-artifact@v3 + with: + name: bundle-${{ env.VERSION }}-Linux + path: artifacts + + - name: Release + uses: softprops/action-gh-release@v1 + with: + draft: true + tag_name: v${{ env.VERSION }} + files: artifacts/**/* + diff --git a/extensions/sd-webui-infinite-image-browsing/.gitignore b/extensions/sd-webui-infinite-image-browsing/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d1e745cdcd81ba8fc3aa9f6c4b632bf9934bc78f --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/.gitignore @@ -0,0 +1,20 @@ +*.log +__pycache__ +iib.db +tags-translate.csv +launch.sh +conf.json +iib.db-journal +.env +standalone.cmd +.vscode +build/**/* +dist/**/* +*.spec +out/**/* +venv/**/* +zip_temp/*.zip +iib_db_backup +db_migrate_temp.db +plugins/* +!plugins/.gitkeep \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/.vscode/settings.json b/extensions/sd-webui-infinite-image-browsing/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..6ba1afd2f5a4843c84363308d83a515e8f3bb589 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "python.formatting.provider": "none" +} \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/LICENSE b/extensions/sd-webui-infinite-image-browsing/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..46d1025428452c2dfcfb56411d8a1bd4f5d026d2 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 zanllp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/sd-webui-infinite-image-browsing/README-zh.md b/extensions/sd-webui-infinite-image-browsing/README-zh.md new file mode 100644 index 0000000000000000000000000000000000000000..930e2fa4c5083449e7b0745a2bb44af2121ffd26 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/README-zh.md @@ -0,0 +1,162 @@ +> 🌐 在线体验: http://39.105.110.128:0721 , 这是我一个空闲的2c2g3m的云主机没有cdn +> +# Stable-Diffusion-WebUI无边图像浏览 + + +[查看近期更新](https://github.com/zanllp/sd-webui-infinite-image-browsing/wiki/Change-log) + +[安装/运行](#安装运行) + + +## 主要特性 + +### 🔥 极佳性能 +- 存在缓存的情况下后,图像可以在几毫秒内显示。 +- 默认使用缩略图显示图像,默认大小为512像素,您可以在全局设置页中调整缩略图分辨率。 +- 你还可以控制网格图像的宽度,允许以64px到1024px的宽度范围进行显示 +- 支持通过`--generate_video_cover`和`--generate_image_cache`来预先生成缩略图和视频封面,以提高性能。 +- 支持通过`IIB_CACHE_DIR`环境变量来指定缓存目录。 + +### 🔍 图像搜索和收藏 +- 将会把Prompt、Model、Lora等信息转成标签,将根据使用频率排序以供进行精确的搜索。 +- 支持标签自动完成、[翻译](https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/39)和自定义。 +- 可通过在右键菜单切换自定义标签来实现图像收藏。 +- 支持类似谷歌的高级搜索。 +- 同样支持模糊搜索,您可以使用文件名或生成信息的一部分进行搜索。 +- 支持添加自定义搜索路径,方便管理自己创建的文件夹集合。 + +### 🖼️ 查看图像/视频和“发送到” +- 支持查看图像生成信息。全屏预览下同样支持。 +- 支持将图像发送到其他选项卡和其他插件,例如 ControlNet, openOutpaint。 +- 支持全屏预览,并且支持在全屏预览下使用自定义快捷键进行操作 +- 支持在全屏预览模式下通过按下方向键或点击按钮移动到前一个或后一个图像。 +- 支持播放远程服务器上的视频文件 + +### 💻 多种使用方法 +- 您可以将其作为 SD-webui 的扩展安装。 +- 您可以使用 Python 独立运行它。 +- 还提供桌面应用程序版本。 +- 支持多种流行的AI软件 + +### 🚶‍♀️ Walk模式 +- 自动加载下一个文件夹 `(类似于 os.walk)`,可让您无需分页浏览所有图像。 +- 已测试可正常处理超过 27,000 个文件。 +- 当存在文件夹的情况下你可以通过右上角的walk按钮从其他模式切换到walk模式,它会将所有的文件夹打平,避免来回进出文件夹的繁琐操作。 + +### 🌳 基于文件树结构的预览和文件操作 +- 支持基于文件树结构的预览。 +- 支持自动刷新。 +- 支持基本文件操作以及多选删除/移动/复制,新建文件夹等。 +- 按住 Ctrl、Shift 或 Cmd 键可选择多个项目。 + - 支持多选的操作有:删除、移动、复制、打包下载、添加标签、移除标签,移动到其他文件夹,复制到其他文件夹,拖拽 + - 你可以通过右下角的保持多选按钮来保持多选的状态,对选中的文件集合可以很方便的进行多次操作 + +### 🆚 图像对比 (类似ImgSli) +- 提供两张图片的并排比较 +- 同时提供图像生成信息的比较 + +### 🌐 多语言支持 +- 目前支持简体中文/繁体中文/英文/德语。 +- 如果您希望添加新的语言,请参考 [i18n.ts](https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/vue/src/i18n/zh-hans.ts) 并提交相关的代码。 + + +### 🔐 隐私和安全 +- 支持自定义secret key来进行身份验证 +- 支持配置对文件系统的访问控制,默认将在服务允许公开访问时启用(仅限作为sd-webui的拓展时) +- 支持自定义访问控制允许的路径。 +- 支持控制访问权限。你可以让IIB以只读模式运行 +- [点击这里查看详情](.env.example) + +### ⌨️ 快捷键 +- 支持删除和添加/移除Tag,在全局设置页进行自定义触发按钮 + +### 📦 打包 / 批量下载 +- 允许你一次性打包下载多个图像 +- 数据来源可以是搜索结果/普通的图像网格查看页面/walk模式等。使用拖拽或者“发送到”都可将图片添加待处理列表 + + +如果您喜欢这个项目并且觉得它对您有帮助,请考虑给我点个⭐️。这将对我持续开发和维护这个项目非常重要。如果您有任何建议或者想法,请随时在issue中提出,我会尽快回复。再次感谢您的支持! + + +[在微信上赞助我](.github/wechat_funding.jpg) + +Buy Me a Coffee at ko-fi.com + + +[视频演示可以在Bilibili上观看](https://space.bilibili.com/27227392/channel/series) + +# 安装/运行 + +## 作为SD-webui的扩展程序: +1. 在SD-webui中打开`扩展`选项卡。 +2. 选择`从URL安装`选项。 +3. 输入 `https://github.com/zanllp/sd-webui-infinite-image-browsing`。 +4. 点击`安装`按钮。 +5. 等待安装完成,然后点击`应用并重启UI`。 + +## 作为使用Python运行的独立程序(不需要SD-webui): +请参考[Can the extension function without the web UI?](https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/47) + +如果需要查看ComfyUI/Fooocus/NovelAI生成的图片相关,请先参考 https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/202#issuecomment-1655764627 + +如果你需要dockerfile 参考 https://github.com/zanllp/sd-webui-infinite-image-browsing/discussions/366 + +## 作为桌面应用程序(不需要SD-webui和Python): +exe版本同样支持ComfyUI/Fooocus/NovelAI + +从仓库页面右侧的`releases`部分下载并安装程序。如果提示检测到病毒忽略即可这是误报。在windows下的编译有两个版本, pyinstaller版本拥有比较低的误报率。 + +如果你需要自行编译请参考 https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/.github/workflows/tauri_app_build.yml +## 作为库使用 + +使用iframe接入IIB,将IIB作为你应用的文件浏览器使用。 参考 https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/vue/usage.md + +# 预览 + +image + +## 图像搜索 + +在第一次使用时,你需要点击等待索引的生成,我2万张图像的情况下大概需要15秒(配置是amd 5600x和pcie ssd)。后续使用他会检查文件夹是否发生变化,如果发生变化则需要重新生成索引,通常这个过程极快。 + +图像搜索支持翻译,具体看这个 https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/39 。 +image +image +## 图像比较 + +![ezgif com-video-to-gif](https://github.com/zanllp/sd-webui-infinite-image-browsing/assets/25872019/4023317b-0b2d-41a3-8155-c4862eb43846) + +## 全屏预览 (并排布局) +![11](https://github.com/zanllp/sd-webui-infinite-image-browsing/assets/25872019/ee941bfc-0c1b-4777-91df-115435cc8542) + +## 全屏预览 +image + +在全屏预览下同样可以查看图片信息和进行上下文菜单上的的操作,支持拖拽/调整/展开收起 + +https://user-images.githubusercontent.com/25872019/235327735-bfb50ea7-7682-4e50-b303-38159456e527.mp4 + + +如果你和我一样不需要查看生成信息,你可以选择直接缩小这个面板,所有上下文操作仍然可用 + +image + +### 右键菜单 +image + +也可以通过右上角的图标来触发 +image + +### Walk模式 + + +https://user-images.githubusercontent.com/25872019/230768207-daab786b-d4ab-489f-ba6a-e9656bd530b8.mp4 + + + + +### 深色模式 + +image + + diff --git a/extensions/sd-webui-infinite-image-browsing/README.md b/extensions/sd-webui-infinite-image-browsing/README.md new file mode 100644 index 0000000000000000000000000000000000000000..59c152e20482d84eea3fd252f320407177abdf38 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/README.md @@ -0,0 +1,176 @@ +> 🌍 i18n Advisory: Some translations may be incomplete or inaccurate. Pull requests are welcome for improvements! + +> 🌐 Try our application online at: http://39.105.110.128:0721. This is my idle 2c2g3m cloud server without CDN. + +[中文文档](./README-zh.md) +[Change log](https://github.com/zanllp/sd-webui-infinite-image-browsing/wiki/Change-log) +[Installation / Running](#installation--running) + + +# Stable Diffusion webui Infinite Image Browsing + +### Software Support and Development Progress Overview +| Software | Support | Provided by | +| ---------------------- | ---------------- | ----------- | +| Stable Diffusion web UI| Supported | Built-in | +| ComfyUI | Partially supported | Built-in | +| Fooocus | Supported | Built-in | +| NovelAI | Supported | Built-in | +| StableSwarmUI | Supported | Built-in | +| Pixiv | Supported | [pixiv_iib_plugin](https://github.com/zanllp/pixiv_iib_plugin) | + +If you would like to support more software, please refer to: [parsers](https://github.com/zanllp/sd-webui-infinite-image-browsing/tree/main/scripts/iib/parsers) or [pixiv_iib_plugin](https://github.com/zanllp/pixiv_iib_plugin) + +## Key Features + +### 🔥 Excellent Performance +- Once caching is generated, images can be displayed in just a few milliseconds. +- Images are displayed with thumbnails by default, with a default size of 512 pixels. You can adjust the thumbnail resolution on the global settings page. +- You can also control the width of the grid images, allowing them to be displayed in widths ranging from 64px to 1024px. +- Supports pre-generating thumbnails and video covers to improve performance using `--generate_video_cover` and `--generate_image_cache`. +- Supports specifying the cache directory through the `IIB_CACHE_DIR` environment variable. + +### 🔍 Image Search & Favorite +- The prompt, model, Lora, and other information will be converted into tags and sorted by frequency of use for precise searching. +- Supports tag autocomplete, [auto-translation](https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/39), and customization. +- Image favorite can be achieved by toggling custom tags for images in the right-click menu. +- Support for advanced search similar to Google +- Also supports fuzzy search, you can search by a part of the filename or generated information. +- Support adding custom search paths for easy management of folders created by the user. + +### 🖼️ View Images/Videos & `Send To` +- Supports viewing image generation information. Also supported in full-screen preview mode. +- Supports sending images to other tabs and third-party extensions such as ControlNet , openOutpaint. +- Support full-screen preview and enable custom shortcut key operations while in full-screen preview mode. +- Support navigating to the previous or next image in full-screen preview mode by pressing arrow keys or clicking buttons. +- Support playing video files from a remote server. + +### 💻 Multiple Usage Methods +- You can install it as an extension on SD-webui. +- You can run it independently using Python. +- The desktop app version is also available. +- Supports multiple popular AI software. + + +### 🚶‍♀️ Walk Mode +- Automatically load the next folder `(similar to os.walk)`, allowing you to browse all images without paging. +- Tested to work properly with over 27,000 files. +- When there are folders, you can switch to walk mode from other modes by clicking the walk button in the upper right corner. It will flatten all the folders, avoiding the tedious operation of going in and out of folders. + +### 🌳 Preview based on File Tree Structure & File operations +- Supports file tree-based preview. +- Supports automatic refreshing. +- Supports basic file operations, such as multiple selection for deleting/moving/copying, and creating new folders. +- Hold down the Ctrl, Shift, or Cmd key to select multiple items. + - Supported multi-select operations include: delete, move, copy, pack download, add tags, remove tags, move to another folder, copy to another folder, drag and drop. + - You can keep the multi-select state by clicking the "Keep Multi-Select" button in the lower right corner, allowing you to perform multiple operations on the selected file collection conveniently. + +### 🆚 image comparison (similar to Imgsli) +- Provides a side-by-side comparison of two images. +- Provides a comparison of image generation information at the same time. + +### 🌐 Multilingual Support +- Currently supports Simplified Chinese/Traditional Chinese/English/German. +- If you would like to add a new language, please refer to [i18n.ts](https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/vue/src/i18n/zh-hans.ts) and submit the relevant code. + +### 🔐 Privacy and Security +- Supports custom secret key for authentication. +- Supports configuring access control for the file system, which will be enabled by default when the service allows public access (Only when used as an extension of sd-webui). +- Supports customizing the allowed paths for access control. +- Supports controlling access permissions. You can run IIB in read-only mode. +- [Click here to see details](.env.example) + + +### 📦 Packaging/Batch Download +- Allows you to download multiple images at once. +- The data source can be search results, a regular image grid view page, walk mode, etc. Images can be added to the processing list through drag-and-drop or "Send To". +### ⌨️ Keyboard Shortcuts +- Allows for deleting and adding/removing tags, with customizable trigger buttons in the global settings page. + + +If you like this project and find it helpful, please consider giving it a ⭐️. This would be very important for me to continue developing and maintaining this project. If you have any suggestions or ideas, please feel free to raise them in the issue section, and I will respond as soon as possible. Thank you again for your support! + + +Buy Me a Coffee at ko-fi.com + +[Sponsor me on WeChat](.github/wechat_funding.jpg) + +# Installation / Running +## As an extension for SD-webui: +1. Open the `Extensions` tab in SD-webui. +2. Select the `Install from URL` option. +3. Enter `https://github.com/zanllp/sd-webui-infinite-image-browsing`. +4. Click on the `Install` button. +5. Wait for the installation to complete and click on `Apply and restart UI`. + +## As a standalone program that runs using Python. (without SD-webui): + +Refer to [Can the extension function without the web UI?](https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/47) + +If you need to view images generated by ComfyUI/Fooocus/NovelAI, please refer to [https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/202](https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/202#issuecomment-1655764627). + +If you need a Dockerfile, you can refer to this link. https://github.com/zanllp/sd-webui-infinite-image-browsing/discussions/366 + +## As a desktop application (without SD-webui and Python): +The executable version also supports ComfyUI/Fooocus/NovelAI. + +Download and install the program from the `releases` section on the right-hand side of the repository page. +If the antivirus detects a virus, it can be ignored as a false positive. There are two versions of the compiled version for Windows, with the pyinstaller version having a lower false positive rate. + +If you need to compile it yourself, please refer to https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/.github/workflows/tauri_app_build.yml. + +## As a Library Usage: + +Use iframe to access IIB and use it as a file browser for your application. Refer to https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/vue/usage.md + + +# Preview + +image + +## Image Search + +During the first use, you need to click and wait for the index generation. For my case with 20,000 images, it took about 45 seconds (with an AMD 5600X CPU and PCIe SSD). For subsequent uses, it will check whether there are changes in the folder, and if so, it needs to regenerate the index. Usually, this process is very fast. + +Image search supports translation, see https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/39 for more detail. Feel free to share files for other languages to facilitate everyone's use. +image +image + +## Full Screen Preview (Side-by-Side Layout) +![11](https://github.com/zanllp/sd-webui-infinite-image-browsing/assets/25872019/ee941bfc-0c1b-4777-91df-115435cc8542) + +## Full Screen Preview + +image + +In full-screen preview mode, you can also view image information and perform operations on the context menu. It supports dragging, resizing and expanding/collapsing . + +https://user-images.githubusercontent.com/25872019/235327735-bfb50ea7-7682-4e50-b303-38159456e527.mp4 + +If you, like me, don't need to view the generation information, you can choose to simply minimize this panel, and all contextual operations will still be available. + +image + +## Image comparison + +![ezgif com-video-to-gif](https://github.com/zanllp/sd-webui-infinite-image-browsing/assets/25872019/4023317b-0b2d-41a3-8155-c4862eb43846) +## Transfer files between different tab panes. +https://github.com/zanllp/sd-webui-infinite-image-browsing/assets/25872019/e631e3c3-1cbf-49bc-8577-f2963a6c9e4d +### Right-click menu +image + +You can also trigger it by hovering your mouse over the icon in the top right corner. + +image + +### Walk mode + + +https://user-images.githubusercontent.com/25872019/230768207-daab786b-d4ab-489f-ba6a-e9656bd530b8.mp4 + + + + +### Dark mode + +image diff --git a/extensions/sd-webui-infinite-image-browsing/app.py b/extensions/sd-webui-infinite-image-browsing/app.py new file mode 100644 index 0000000000000000000000000000000000000000..8088ceb638fd015bb382fb39635b4a023281be49 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/app.py @@ -0,0 +1,299 @@ +import codecs +from typing import List +from fastapi import FastAPI, Response +from fastapi.responses import FileResponse +import uvicorn +import os +from scripts.iib.api import infinite_image_browsing_api, index_html_path, DEFAULT_BASE +from scripts.iib.tool import ( + get_sd_webui_conf, + get_valid_img_dirs, + sd_img_dirs, + normalize_paths, +) +from scripts.iib.db.datamodel import DataBase, Image, ExtraPath +from scripts.iib.db.update_image_data import update_image_data +import argparse +from typing import Optional, Coroutine +import json + +tag = "\033[31m[warn]\033[0m" + +default_port = 8000 +default_host = "127.0.0.1" + +def get_all_img_dirs(sd_webui_config: str, relative_to_config: bool): + dirs = get_valid_img_dirs( + get_sd_webui_conf( + sd_webui_config=sd_webui_config, + sd_webui_path_relative_to_config=relative_to_config, + ) + ) + dirs += list(map(lambda x: x.path, ExtraPath.get_extra_paths(DataBase.get_conn()))) + return dirs + + +def sd_webui_paths_check(sd_webui_config: str, relative_to_config: bool): + conf = {} + with codecs.open(sd_webui_config, "r", "utf-8") as f: + conf = json.loads(f.read()) + if relative_to_config: + for dir in sd_img_dirs: + if not os.path.isabs(conf[dir]): + conf[dir] = os.path.normpath( + os.path.join(sd_webui_config, "../", conf[dir]) + ) + paths = [conf.get(key) for key in sd_img_dirs] + paths_check(paths) + + +def paths_check(paths): + for path in paths: + if not path or len(path.strip()) == 0: + continue + if os.path.isabs(path): + abs_path = path + else: + abs_path = os.path.normpath(os.path.join(os.getcwd(), path)) + if not os.path.exists(abs_path): + print(f"{tag} The path '{abs_path}' will be ignored (value: {path}).") + + +def do_update_image_index(sd_webui_config: str, relative_to_config=False): + dirs = get_all_img_dirs(sd_webui_config, relative_to_config) + if not len(dirs): + return print(f"{tag} no valid image directories, skipped") + conn = DataBase.get_conn() + update_image_data(dirs) + if Image.count(conn=conn) == 0: + return print(f"{tag} it appears that there is some issue") + print("update image index completed. ✨") + + +class AppUtils: + def __init__( + self, + sd_webui_config: Optional[str] = None, + update_image_index: bool = False, + extra_paths: List[str] = [], + sd_webui_path_relative_to_config=False, + allow_cors=False, + enable_shutdown=False, + sd_webui_dir: Optional[str] = None, + base: Optional[str] = None, + export_fe_fn=False, + **args: dict, + ): + """ + Parameter definitions can be found by running the `python app.py -h `command or by examining the setup_parser() function. + """ + self.sd_webui_config = sd_webui_config + self.update_image_index = update_image_index + self.extra_paths = extra_paths + self.sd_webui_path_relative_to_config = sd_webui_path_relative_to_config + self.allow_cors = allow_cors + self.enable_shutdown = enable_shutdown + self.sd_webui_dir = sd_webui_dir + if base and not base.startswith("/"): + base = "/" + base + self.base = base + self.export_fe_fn = export_fe_fn + if sd_webui_dir: + DataBase.path = os.path.join( + sd_webui_dir, "extensions/sd-webui-infinite-image-browsing/iib.db" + ) + self.sd_webui_config = os.path.join(sd_webui_dir, "config.json") + self.sd_webui_path_relative_to_config = True + + def set_params(self, *args, **kwargs) -> None: + """改变参数,与__init__的行为一致""" + self.__init__(*args, **kwargs) + + @staticmethod + def async_run( + app: FastAPI, port: int = default_port, host=default_host + ) -> Coroutine: + """ + 用于从异步运行的 FastAPI,在 Jupyter Notebook 环境中非常有用 + """ + # 不建议改成 async def,并且用 await 替换 return, + # 因为这样会失去对 server.serve() 的控制。 + config = uvicorn.Config(app, host=host, port=port) + server = uvicorn.Server(config) + return server.serve() + + def wrap_app(self, app: FastAPI) -> None: + """ + 为传递的app挂载上infinite_image_browsing后端 + """ + sd_webui_config = self.sd_webui_config + update_image_index = self.update_image_index + extra_paths = self.extra_paths + + if sd_webui_config: + sd_webui_paths_check(sd_webui_config, self.sd_webui_path_relative_to_config) + if update_image_index: + do_update_image_index( + sd_webui_config, self.sd_webui_path_relative_to_config + ) + paths_check(extra_paths) + + infinite_image_browsing_api( + app, + sd_webui_config=sd_webui_config, + extra_paths_cli=normalize_paths(extra_paths, os.getcwd()), + sd_webui_path_relative_to_config=self.sd_webui_path_relative_to_config, + allow_cors=self.allow_cors, + enable_shutdown=self.enable_shutdown, + launch_mode="server", + base=self.base, + export_fe_fn=self.export_fe_fn, + ) + + def get_root_browser_app(self) -> FastAPI: + """ + 获取首页挂载在"/"上的infinite_image_browsing FastAPI实例 + """ + app = FastAPI() + + # 用于在首页显示 + @app.get("/") + def index(): + if isinstance(self.base, str): + with open(index_html_path, "r", encoding="utf-8") as file: + content = file.read().replace(DEFAULT_BASE, self.base) + return Response(content=content, media_type="text/html") + return FileResponse(index_html_path) + + self.wrap_app(app) + return app + + +def setup_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="A fast and powerful image/video browser with infinite scrolling and advanced search capabilities. It also supports parsing/viewing image information generated by multiple AI software." + ) + parser.add_argument( + "--host", type=str, default=default_host, help="The host to use" + ) + parser.add_argument( + "--port", type=int, help="The port to use", default=default_port + ) + parser.add_argument( + "--sd_webui_config", type=str, default=None, help="The path to the config file" + ) + parser.add_argument( + "--update_image_index", action="store_true", help="Update the image index" + ) + parser.add_argument( + "--generate_video_cover", + action="store_true", + help="Pre-generate video cover images to speed up browsing.", + ) + parser.add_argument( + "--generate_image_cache", + action="store_true", + help="Pre-generate image cache to speed up browsing. By default, only the extra paths added by the user are processed, not the paths in sd_webui_config. If you need to process paths in sd_webui_config, you must use the --sd_webui_config and --sd_webui_path_relative_to_config parameters.", + ) + parser.add_argument( + "--generate_image_cache_size", + type=str, + default="512x512", + help="The size of the image cache to generate. Default is 512x512", + ) + parser.add_argument( + "--gen_cache_verbose", + action="store_true", + help="Verbose mode for cache generation.", + ) + parser.add_argument( + "--extra_paths", + nargs="+", + help="Extra paths to use, these paths will be added to the homepage but the images in these paths will not be indexed. They are only used for browsing. If you need to index them, please add them via the '+ Add' button on the homepage.", + default=[], + ) + parser.add_argument( + "--sd_webui_path_relative_to_config", + action="store_true", + help="Use the file path of the sd_webui_config file as the base for all relative paths provided within the sd_webui_config file.", + ) + parser.add_argument( + "--allow_cors", + action="store_true", + help="Allow Cross-Origin Resource Sharing (CORS) for the API.", + ) + parser.add_argument( + "--enable_shutdown", + action="store_true", + help="Enable the shutdown endpoint.", + ) + parser.add_argument( + "--sd_webui_dir", + type=str, + default=None, + help="The path to the sd_webui folder. When specified, the sd_webui's configuration will be used and the extension must be installed within the sd_webui. Data will be shared between the two.", + ) + parser.add_argument( + "--export_fe_fn", + default=True, + action="store_true", + help="Export front-end functions to enable external access through iframe.", + ) + parser.add_argument("--base", type=str, help="The base URL for the IIB Api.") + return parser + + +def launch_app( + port: int = default_port, host: str = default_host, *args, **kwargs: dict +) -> None: + """ + Launches the application on the specified port. + + Args: + **kwargs (dict): Optional keyword arguments that can be used to configure the application. + These can be viewed by running 'python app.py -h' or by checking the setup_parser() function. + """ + app_utils = AppUtils(*args, **kwargs) + app = app_utils.get_root_browser_app() + uvicorn.run(app, host=host, port=port) + + +async def async_launch_app( + port: int = default_port, host: str = default_host, *args, **kwargs: dict +) -> None: + """ + Asynchronously launches the application on the specified port. + + Args: + **kwargs (dict): Optional keyword arguments that can be used to configure the application. + These can be viewed by running 'python app.py -h' or by checking the setup_parser() function. + """ + app_utils = AppUtils(*args, **kwargs) + app = app_utils.get_root_browser_app() + await app_utils.async_run(app, host=host, port=port) + + +if __name__ == "__main__": + parser = setup_parser() + args = parser.parse_args() + args_dict = vars(args) + + if args_dict.get("generate_video_cover"): + from scripts.iib.video_cover_gen import generate_video_covers + + conn = DataBase.get_conn() + generate_video_covers( + dirs = map(lambda x: x.path, ExtraPath.get_extra_paths(conn)), + verbose=args.gen_cache_verbose, + ) + exit(0) + if args_dict.get("generate_image_cache"): + from scripts.iib.img_cache_gen import generate_image_cache + generate_image_cache( + dirs = get_all_img_dirs(args.sd_webui_config, args.sd_webui_path_relative_to_config), + size = args.generate_image_cache_size, + verbose = args.gen_cache_verbose + ) + exit(0) + + launch_app(**vars(args)) \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/iib.db b/extensions/sd-webui-infinite-image-browsing/iib.db new file mode 100644 index 0000000000000000000000000000000000000000..ea020c62d35ab53b51f73e35e3a62a8a661c193b Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/iib.db differ diff --git a/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 13-28-50 b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 13-28-50 new file mode 100644 index 0000000000000000000000000000000000000000..da67bb432688f1ecf67795ef878d285d048d405e Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 13-28-50 differ diff --git a/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 14-09-29 b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 14-09-29 new file mode 100644 index 0000000000000000000000000000000000000000..ffae6014582fa8b8fdd12ab8c0f3be9c81b42263 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 14-09-29 differ diff --git a/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 17-32-27 b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 17-32-27 new file mode 100644 index 0000000000000000000000000000000000000000..9a66c7003786eeb244fdb08df969aae803969672 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 17-32-27 differ diff --git a/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 18-21-27 b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 18-21-27 new file mode 100644 index 0000000000000000000000000000000000000000..2d918dd4ed78776ad6fdb83f7eec1840cf067032 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 18-21-27 differ diff --git a/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 19-38-38 b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 19-38-38 new file mode 100644 index 0000000000000000000000000000000000000000..f1bac249c6ec02e969a44e212de13e7a2f5227d7 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 19-38-38 differ diff --git a/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 19-38-48 b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 19-38-48 new file mode 100644 index 0000000000000000000000000000000000000000..f1bac249c6ec02e969a44e212de13e7a2f5227d7 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 19-38-48 differ diff --git a/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 19-40-33 b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 19-40-33 new file mode 100644 index 0000000000000000000000000000000000000000..f1bac249c6ec02e969a44e212de13e7a2f5227d7 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-27 19-40-33 differ diff --git a/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-28 00-50-11 b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-28 00-50-11 new file mode 100644 index 0000000000000000000000000000000000000000..413f499ef1bc7a128974d3be218df5ca3f2a5455 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/iib_db_backup/iib.db_2024-08-28 00-50-11 differ diff --git a/extensions/sd-webui-infinite-image-browsing/install.py b/extensions/sd-webui-infinite-image-browsing/install.py new file mode 100644 index 0000000000000000000000000000000000000000..a0acdf9723dfcbdf7a5ad4bf0bc7a39b11a14b4b --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/install.py @@ -0,0 +1,28 @@ +import launch +import os +import pkg_resources + +req_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "requirements.txt") + +def dist2package(dist: str): + return ({ + "python-dotenv": "dotenv", + "Pillow": "PIL", + "pillow-avif-plugin": "pillow_avif" + }).get(dist, dist) + +# copy from controlnet, thanks +with open(req_file) as file: + for package in file: + try: + package = package.strip() + if '==' in package: + package_name, package_version = package.split('==') + installed_version = pkg_resources.get_distribution(package_name).version + if installed_version != package_version: + launch.run_pip(f"install {package}", f"sd-webui-infinite-image-browsing requirement: changing {package_name} version from {installed_version} to {package_version}") + elif not launch.is_installed(dist2package(package)): + launch.run_pip(f"install {package}", f"sd-webui-infinite-image-browsing requirement: {package}") + except Exception as e: + print(e) + print(f'Warning: Failed to install {package}, something may not work.') diff --git a/extensions/sd-webui-infinite-image-browsing/javascript/index.js b/extensions/sd-webui-infinite-image-browsing/javascript/index.js new file mode 100644 index 0000000000000000000000000000000000000000..33651dd0bfa583cc248160f7e4855978dec41267 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/javascript/index.js @@ -0,0 +1,186 @@ +/* eslint-disable no-undef */ +Promise.resolve().then(async () => { + /** + * This is a file generated using `yarn build`. + * If you want to make changes, please modify `index.tpl.js` and run the command to generate it again. + */ + const html = ` + + + + + + + + Infinite Image Browsing + + + + + + +
+ + + +`.replace(/\/infinite_image_browsing/g, (window.location.pathname + '/infinite_image_browsing').replace(/\/\//g, '/')) + let containerSelector = '#infinite_image_browsing_container_wrapper' + let shouldMaximize = true + + try { + containerSelector = __iib_root_container__ + shouldMaximize = __iib_should_maximize__ + } catch (e) { /* empty */ } + + const delay = (timeout = 0) => new Promise((resolve) => setTimeout(resolve, timeout)) + const asyncCheck = async (getter, checkSize = 100, timeout = 1000) => { + let target = getter() + let num = 0 + while (checkSize * num < timeout && (target === undefined || target === null)) { + await delay(checkSize) + target = getter() + num++ + } + return target + } + + const getTabIdxById = (id) => { + const tabList = gradioApp().querySelectorAll('#tabs > .tabitem[id^=tab_]') + return Array.from(tabList).findIndex((v) => v.id.includes(id)) + } + + const switch2targetTab = (idx) => { + try { + gradioApp().querySelector('#tabs').querySelectorAll('button')[idx].click() + } catch (error) { + console.error(error) + } + } + + const isLobe = () => { + try { + return !!gradioApp().querySelector('[alt*="lobehub"]') + } catch (error) { + return false + } + } + + /** + * @type {HTMLDivElement} + */ + const wrap = await asyncCheck(() => gradioApp().querySelector(containerSelector), 500, Infinity) + wrap.childNodes.forEach((v) => wrap.removeChild(v)) + const iframe = document.createElement('iframe') + iframe.srcdoc = html + iframe.style = 'width: 100%;height:100vh' + wrap.appendChild(iframe) + + if (shouldMaximize) { + onUiTabChange(() => { + const el = get_uiCurrentTabContent() + if (el?.id.includes('infinite-image-browsing')) { + try { + const iibTop = gradioApp().querySelector('#iib_top') + if (!iibTop) { + throw new Error('element \'#iib_top\' is not found') + } + const topRect = iibTop.getBoundingClientRect() + wrap.style = ` + top:${Math.max(isLobe() ? 32 : 128, topRect.top) - 10}px; + position: fixed; + left: 10px; + right: 10px; + z-index: 100; + width: unset; + bottom: 10px;` + iframe.style = 'width: 100%;height:100%' + } catch (error) { + console.error('Error mounting IIB. Running fallback.', error) + wrap.style = '' + iframe.style = 'width: 100%;height:100vh' + } + } + }) + } + + const IIB_container_id = [Date.now(), Math.random()].join() + window.IIB_container_id = IIB_container_id + const imgTransferBus = new BroadcastChannel('iib-image-transfer-bus') + imgTransferBus.addEventListener('message', async (ev) => { + const data = ev.data + if ( + typeof data !== 'object' || + (typeof data.IIB_container_id === 'string' && data.IIB_container_id !== IIB_container_id) + ) { + return + } + console.log('iib-message:', data) + const appDoc = gradioApp() + switch (data.event) { + case 'click_hidden_button': { + const btn = gradioApp().querySelector(`#${data.btnEleId}`) + btn.click() + break + } + case 'send_to_control_net': { + data.type === 'img2img' ? window.switch_to_img2img() : window.switch_to_txt2img() + await delay(100) + const cn = appDoc.querySelector(`#${data.type}_controlnet`) + const wrap = cn.querySelector('.label-wrap') + if (!wrap.className.includes('open')) { + wrap.click() + await delay(100) + } + wrap.scrollIntoView() + wrap.dispatchEvent(await createPasteEvent(data.url)) + break + } + case 'send_to_outpaint': { + switch2targetTab(getTabIdxById('openOutpaint')) + await delay(100) + const iframe = appDoc.querySelector('#openoutpaint-iframe') + openoutpaint_send_image(await imgUrl2DataUrl(data.url)) + iframe.contentWindow.postMessage({ + key: appDoc.querySelector('#openoutpaint-key').value, + type: 'openoutpaint/set-prompt', + prompt: data.prompt, + negPrompt: data.negPrompt + }) + break + } + } + + function imgUrl2DataUrl(imgUrl) { + return new Promise((resolve, reject) => { + fetch(imgUrl) + .then((response) => response.blob()) + .then((blob) => { + const reader = new FileReader() + reader.readAsDataURL(blob) + reader.onloadend = function () { + const dataURL = reader.result + resolve(dataURL) + } + }) + .catch((error) => reject(error)) + }) + } + + async function createPasteEvent(imgUrl) { + const response = await fetch(imgUrl) + const imageBlob = await response.blob() + const imageFile = new File([imageBlob], 'image.jpg', { + type: imageBlob.type, + lastModified: Date.now() + }) + const dataTransfer = new DataTransfer() + dataTransfer.items.add(imageFile) + const pasteEvent = new ClipboardEvent('paste', { + clipboardData: dataTransfer, + bubbles: true + }) + return pasteEvent + } + }) +}) diff --git a/extensions/sd-webui-infinite-image-browsing/log.log b/extensions/sd-webui-infinite-image-browsing/log.log new file mode 100644 index 0000000000000000000000000000000000000000..dd30655490a9ad126f5666c54fe71b0688943937 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/log.log @@ -0,0 +1 @@ +2024-08-25 15:57:17,431 - scripts.iib.logger - ERROR - img_update_func err 'str' object has no attribute 'read' diff --git a/extensions/sd-webui-infinite-image-browsing/migrate.py b/extensions/sd-webui-infinite-image-browsing/migrate.py new file mode 100644 index 0000000000000000000000000000000000000000..8bf092664d46023cc7ec0a039b6e34ba512d31a0 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/migrate.py @@ -0,0 +1,84 @@ +from contextlib import closing +import argparse +from scripts.iib.db.datamodel import DataBase +import os + +import shutil + + +def replace_path(old_base, new_base): + """ + Custom SQL function to replace part of a path. + + Args: + old_base (str): The base part of the path to be replaced. + new_base (str): The new base part of the path. + + Returns: + str: Updated path. + """ + + def replace_func(path): + if path.startswith(old_base): + return new_base + path[len(old_base) :] + else: + return path + + return replace_func + + +def update_paths(conn, table_name, old_base): + """ + Update paths in a specified SQLite table using a custom SQL function. + + Args: + db_path (str): Path to the SQLite database file. + table_name (str): Name of the table containing the paths. + + Returns: + None + """ + with closing(conn.cursor()) as cur: + # Use the custom function in an UPDATE statement + cur.execute( + f"UPDATE {table_name} SET path = replace_path(path) WHERE path LIKE ?", + (f"{old_base}%",), + ) + + # Commit the changes and close the connection + conn.commit() + + +def setup_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Script to migrate paths in an IIB SQLite database from an old directory structure to a new one." + ) + parser.add_argument( + "--db_path", type=str, help="Path to the input IIB QLite database file to be migrated. Default value is 'iib.db'.", default="iib.db" + ) + parser.add_argument( + "--old_dir", type=str, help="Old base directory to be replaced in the paths.", required=True + ) + parser.add_argument( + "--new_dir", type=str, help="New base directory to replace the old base directory in the paths.", required=True + ) + return parser + + +if __name__ == "__main__": + parser = setup_parser() + args = parser.parse_args() + old_base = args.old_dir + new_base = args.new_dir + db_path = args.db_path + db_temp_path = "db_migrate_temp.db" + shutil.copy2(db_path, db_temp_path) + DataBase.path = os.path.normpath(os.path.join(os.getcwd(), db_temp_path)) + conn = DataBase.get_conn() + conn.create_function("replace_path", 1, replace_path(old_base, new_base)) + update_paths(conn, "image", old_base) + update_paths(conn, "extra_path", old_base) + update_paths(conn, "folders", old_base) + shutil.copy(db_temp_path, "iib.db") + # os.remove(db_temp_path) + print("Database migration completed successfully.") diff --git a/extensions/sd-webui-infinite-image-browsing/plugins/.gitkeep b/extensions/sd-webui-infinite-image-browsing/plugins/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/extensions/sd-webui-infinite-image-browsing/requirements.txt b/extensions/sd-webui-infinite-image-browsing/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..ab7e64fa30ec44a209e81431a2be61a89de1fbc8 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/requirements.txt @@ -0,0 +1,9 @@ +fastapi +uvicorn +piexif +python-dotenv +Pillow +pillow-avif-plugin +imageio +av +lxml \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/__pycache__/iib_setup.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/__pycache__/iib_setup.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..370b771d59d7e28017328b4ff980ea2f7703b75a Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/__pycache__/iib_setup.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/api.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/api.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..170d5e93fd649df4e6e647a7b03383a1a819f1b7 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/api.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/dir_cover_cache.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/dir_cover_cache.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1423d624ef4d7515b4227b6c1e926d061bd59bdd Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/dir_cover_cache.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/fastapi_video.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/fastapi_video.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66d130bf297d6d4e82dd0548d21844e3294f367c Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/fastapi_video.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/logger.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/logger.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a6a39fbe256feb1b7bec64dc4d7f4ae9cb2fffc Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/logger.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/plugin.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/plugin.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb6d65424c6a3c59655a202ea60c21f8c52aea13 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/plugin.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/seq.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/seq.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d40e7188e920b2f39f706d9232ae2c5801a581b2 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/seq.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/tool.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/tool.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7a5b84f147239fa76a9235ff1259eec8ba3551f Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/__pycache__/tool.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/api.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/api.py new file mode 100644 index 0000000000000000000000000000000000000000..e909ae4f4a0bbe2774e05a496a3855350961d883 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/api.py @@ -0,0 +1,1183 @@ +import base64 +from datetime import datetime, timedelta +import io +import os +from pathlib import Path +import shutil +import sqlite3 + + +from scripts.iib.dir_cover_cache import get_top_4_media_info +from scripts.iib.tool import ( + get_created_date_by_stat, + get_video_type, + human_readable_size, + is_valid_media_path, + is_media_file, + get_cache_dir, + get_formatted_date, + is_win, + cwd, + locale, + enable_access_control, + get_windows_drives, + get_sd_webui_conf, + get_valid_img_dirs, + open_folder, + get_img_geninfo_txt_path, + unique_by, + create_zip_file, + normalize_paths, + to_abs_path, + is_secret_key_required, + open_file_with_default_app, + is_exe_ver, + backup_db_file, + get_current_commit_hash, + get_current_tag, + get_file_info_by_path, + get_frame_at_second +) +from fastapi import FastAPI, HTTPException, Header, Response +from fastapi.staticfiles import StaticFiles +import asyncio +from typing import List, Optional +from pydantic import BaseModel +from fastapi.responses import FileResponse, JSONResponse, StreamingResponse +from PIL import Image +from fastapi import Depends, FastAPI, HTTPException, Request +from fastapi.middleware.cors import CORSMiddleware +import hashlib +from scripts.iib.db.datamodel import ( + DataBase, + ExtraPathType, + Image as DbImg, + Tag, + Folder, + ImageTag, + ExtraPath, + FileInfoDict, + Cursor, + GlobalSetting +) +from scripts.iib.db.update_image_data import update_image_data, rebuild_image_index, add_image_data_single +from scripts.iib.logger import logger +from scripts.iib.seq import seq +import urllib.parse +from scripts.iib.fastapi_video import range_requests_response, close_video_file_reader +from scripts.iib.parsers.index import parse_image_info +import scripts.iib.plugin + +try: + import pillow_avif +except Exception as e: + logger.error(e) + +index_html_path = os.path.join(cwd, "vue/dist/index.html") # 在app.py也被使用 + + +send_img_path = {"value": ""} +mem = {"secret_key_hash": None, "extra_paths": [], "all_scanned_paths": []} +secret_key = os.getenv("IIB_SECRET_KEY") +if secret_key: + print("Secret key loaded successfully. ") + +WRITEABLE_PERMISSIONS = ["read-write", "write-only"] + +is_api_writeable = not (os.getenv("IIB_ACCESS_CONTROL_PERMISSION")) or ( + os.getenv("IIB_ACCESS_CONTROL_PERMISSION") in WRITEABLE_PERMISSIONS +) +IIB_DEBUG=False + + +async def write_permission_required(): + if not is_api_writeable: + error_msg = ( + "User is not authorized to perform this action. Required permission: " + + ", ".join(WRITEABLE_PERMISSIONS) + ) + raise HTTPException(status_code=403, detail=error_msg) + + +async def verify_secret(request: Request): + if not secret_key: + if is_secret_key_required: + raise HTTPException(status_code=400, detail={"type": "secret_key_required"}) + return + token = request.cookies.get("IIB_S") + if not token: + raise HTTPException(status_code=401, detail="Unauthorized") + if not mem["secret_key_hash"]: + mem["secret_key_hash"] = hashlib.sha256( + (secret_key + "_ciallo").encode("utf-8") + ).hexdigest() + if mem["secret_key_hash"] != token: + raise HTTPException(status_code=401, detail="Unauthorized") + +DEFAULT_BASE = "/infinite_image_browsing" +def infinite_image_browsing_api(app: FastAPI, **kwargs): + backup_db_file(DataBase.get_db_file_path()) + api_base = kwargs.get("base") if isinstance(kwargs.get("base"), str) else DEFAULT_BASE + fe_public_path = kwargs.get("fe_public_path") if isinstance(kwargs.get("fe_public_path"), str) else api_base + cache_base_dir = get_cache_dir() + + # print(f"IIB api_base:{api_base} fe_public_path:{fe_public_path}") + if IIB_DEBUG or is_exe_ver: + @app.exception_handler(Exception) + async def exception_handler(request: Request, exc: Exception): + error_msg = f"An exception occurred while processing {request.method} {request.url}: {exc}" + logger.error(error_msg) + + return JSONResponse( + status_code=500, content={"message": "Internal Server Error"} + ) + @app.middleware("http") + async def log_requests(request: Request, call_next): + path = request.url.path + if ( + path.find("infinite_image_browsing/image-thumbnail") == -1 + and path.find("infinite_image_browsing/file") == -1 + and path.find("infinite_image_browsing/fe-static") == -1 + ): + logger.info(f"Received request: {request.method} {request.url}") + if request.query_params: + logger.debug(f"Query Params: {request.query_params}") + if request.path_params: + logger.debug(f"Path Params: {request.path_params}") + + try: + return await call_next(request) + except HTTPException as http_exc: + logger.warning( + f"HTTPException occurred while processing {request.method} {request.url}: {http_exc}" + ) + raise http_exc + except Exception as exc: + logger.error( + f"An exception occurred while processing {request.method} {request.url}: {exc}" + ) + + + if kwargs.get("allow_cors"): + app.add_middleware( + CORSMiddleware, + allow_origin_regex="^[\w./:-]+$", + allow_methods=["*"], + allow_headers=["*"], + ) + + def get_img_search_dirs(): + try: + return get_valid_img_dirs(get_sd_webui_conf(**kwargs)) + except Exception as e: + print(e) + return [] + + def update_all_scanned_paths(): + allowed_paths = os.getenv("IIB_ACCESS_CONTROL_ALLOWED_PATHS") + if allowed_paths: + sd_webui_conf = get_sd_webui_conf(**kwargs) + path_config_key_map = { + "save": "outdir_save", + "extra": "outdir_extras_samples", + "txt2img": "outdir_txt2img_samples", + "img2img": "outdir_img2img_samples", + } + + def path_map(path: str): + path = path.strip() + if path in path_config_key_map: + return sd_webui_conf.get(path_config_key_map.get(path)) + return path + + paths = normalize_paths( + seq(allowed_paths.split(",")) + .map(path_map) + .filter(lambda x: x) + .to_list(), + os.getcwd() + ) + else: + paths = ( + get_img_search_dirs() + + mem["extra_paths"] + + kwargs.get("extra_paths_cli", []) + ) + mem["all_scanned_paths"] = unique_by(paths) + + update_all_scanned_paths() + + def update_extra_paths(conn: sqlite3.Connection): + r = ExtraPath.get_extra_paths(conn) + mem["extra_paths"] = [x.path for x in r] + update_all_scanned_paths() + + def safe_commonpath(seq): + try: + return os.path.commonpath(seq) + except Exception as e: + # logger.error(e) + return "" + + def is_path_under_parents(path, parent_paths: List[str] = []): + """ + Check if the given path is under one of the specified parent paths. + :param path: The path to check. + :param parent_paths: By default, all scanned paths are included in the list of parent paths + :return: True if the path is under one of the parent paths, False otherwise. + """ + try: + if not parent_paths: + parent_paths = mem["all_scanned_paths"] + path = to_abs_path(path) + for parent_path in parent_paths: + if safe_commonpath([path, parent_path]) == parent_path: + return True + except Exception as e: + logger.error(e) + return False + + def is_path_trusted(path: str): + if not enable_access_control: + return True + try: + parent_paths = mem["all_scanned_paths"] + path = to_abs_path(path) + for parent_path in parent_paths: + if len(path) <= len(parent_path): + if parent_path.startswith(path): + return True + else: + if path.startswith(parent_path): + return True + except: + pass + return False + + def check_path_trust(path: str): + if not is_path_trusted(path): + raise HTTPException(status_code=403) + + def filter_allowed_files(files: List[FileInfoDict]): + return [x for x in files if is_path_trusted(x["fullpath"])] + + + + class PathsReq(BaseModel): + paths: List[str] + + @app.get(f"{api_base}/hello") + async def greeting(): + return "hello" + + @app.get(f"{api_base}/global_setting", dependencies=[Depends(verify_secret)]) + async def global_setting(): + all_custom_tags = [] + + extra_paths = [] + app_fe_setting = {} + try: + conn = DataBase.get_conn() + all_custom_tags = Tag.get_all_custom_tag(conn) + extra_paths = ExtraPath.get_extra_paths(conn) + [ + ExtraPath(path, ExtraPathType.cli_only.value) + for path in kwargs.get("extra_paths_cli", []) + ] + update_extra_paths(conn) + app_fe_setting = GlobalSetting.get_all_settings(conn) + except Exception as e: + print(e) + return { + "global_setting": get_sd_webui_conf(**kwargs), + "cwd": cwd, + "is_win": is_win, + "home": os.environ.get("USERPROFILE") if is_win else os.environ.get("HOME"), + "sd_cwd": os.getcwd(), + "all_custom_tags": all_custom_tags, + "extra_paths": extra_paths, + "enable_access_control": enable_access_control, + "launch_mode": kwargs.get("launch_mode", "sd"), + "export_fe_fn": bool(kwargs.get("export_fe_fn")), + "app_fe_setting": app_fe_setting, + "is_readonly": not is_api_writeable, + } + + + class AppFeSettingReq(BaseModel): + name: str + value: str + + @app.post(f"{api_base}/app_fe_setting", dependencies=[Depends(verify_secret), Depends(write_permission_required)]) + async def app_fe_setting(req: AppFeSettingReq): + conn = DataBase.get_conn() + GlobalSetting.save_setting(conn, req.name, req.value) + + class AppFeSettingDelReq(BaseModel): + name: str + + @app.delete(f"{api_base}/app_fe_setting", dependencies=[Depends(verify_secret), Depends(write_permission_required)]) + async def remove_app_fe_setting(req: AppFeSettingDelReq): + conn = DataBase.get_conn() + GlobalSetting.remove_setting(conn, req.name) + + @app.get(f"{api_base}/version", dependencies=[Depends(verify_secret)]) + async def get_version(): + return { + "hash": get_current_commit_hash(), + "tag": get_current_tag(), + } + + class DeleteFilesReq(BaseModel): + file_paths: List[str] + + @app.post( + api_base + "/delete_files", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def delete_files(req: DeleteFilesReq): + conn = DataBase.get_conn() + for path in req.file_paths: + check_path_trust(path) + try: + if os.path.isdir(path): + if len(os.listdir(path)): + error_msg = ( + "When a folder is not empty, it is not allowed to be deleted." + if locale == "en" + else "文件夹不为空时不允许删除。" + ) + raise HTTPException(400, detail=error_msg) + shutil.rmtree(path) + else: + close_video_file_reader(path) + os.remove(path) + txt_path = get_img_geninfo_txt_path(path) + if txt_path: + os.remove(txt_path) + img = DbImg.get(conn, os.path.normpath(path)) + if img: + logger.info("delete file: %s", path) + ImageTag.remove(conn, img.id) + DbImg.remove(conn, img.id) + except OSError as e: + # 处理删除失败的情况 + logger.error("delete failed") + error_msg = ( + f"Error deleting file {path}: {e}" + if locale == "en" + else f"删除文件 {path} 时出错:{e}" + ) + raise HTTPException(400, detail=error_msg) + + class CreateFoldersReq(BaseModel): + dest_folder: str + + @app.post( + api_base + "/mkdirs", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def create_folders(req: CreateFoldersReq): + if enable_access_control: + if not is_path_under_parents(req.dest_folder): + raise HTTPException(status_code=403) + os.makedirs(req.dest_folder, exist_ok=True) + + class MoveFilesReq(BaseModel): + file_paths: List[str] + dest: str + create_dest_folder: Optional[bool] = False + + @app.post( + api_base + "/copy_files", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def copy_files(req: MoveFilesReq): + for path in req.file_paths: + check_path_trust(path) + try: + shutil.copy(path, req.dest) + txt_path = get_img_geninfo_txt_path(path) + if txt_path: + shutil.copy(txt_path, req.dest) + except OSError as e: + error_msg = ( + f"Error copying file {path} to {req.dest}: {e}" + if locale == "en" + else f"复制文件 {path} 到 {req.dest} 时出错:{e}" + ) + raise HTTPException(400, detail=error_msg) + + @app.post( + api_base + "/move_files", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def move_files(req: MoveFilesReq): + if req.create_dest_folder: + os.makedirs(req.dest, exist_ok=True) + elif not os.path.isdir(req.dest): + error_msg = ( + f"Destination folder {req.dest} does not exist." + if locale == "en" + else f"目标文件夹 {req.dest} 不存在。" + ) + raise HTTPException(400, detail=error_msg) + + conn = DataBase.get_conn() + + def move_file_with_geninfo(path: str, dest: str): + path = os.path.normpath(path) + txt_path = get_img_geninfo_txt_path(path) + if txt_path: + shutil.move(txt_path, dest) + img = DbImg.get(conn, path) + new_path = os.path.normpath(os.path.join(dest, os.path.basename(path))) + if img: + logger.info(f"update file path: {path} -> {new_path} in db") + img.update_path(conn, new_path, force=True) + + for path in req.file_paths: + check_path_trust(path) + path = os.path.normpath(path) + base_dir = os.path.dirname(path) + try: + files = list(os.walk(path)) + is_dir = os.path.isdir(path) + shutil.move(path, req.dest) + if is_dir: + for root, _, files in files: + relative_path = root[len(base_dir) + 1 :] + dest = os.path.join(req.dest, relative_path) + for file in files: + is_valid = is_media_file(file) + if is_valid: + move_file_with_geninfo(os.path.join(root, file), dest) + else: + move_file_with_geninfo(path, req.dest) + + conn.commit() + except OSError as e: + + conn.rollback() + error_msg = ( + f"Error moving file {path} to {req.dest}: {e}" + if locale == "en" + else f"移动文件 {path} 到 {req.dest} 时出错:{e}" + ) + raise HTTPException(400, detail=error_msg) + + @app.get(api_base + "/files", dependencies=[Depends(verify_secret)]) + async def get_target_folder_files(folder_path: str): + files: List[FileInfoDict] = [] + try: + if is_win and folder_path == "/": + for item in get_windows_drives(): + files.append( + {"type": "dir", "size": "-", "name": item, "fullpath": item} + ) + else: + if not os.path.exists(folder_path): + return {"files": []} + folder_path = to_abs_path(folder_path) + check_path_trust(folder_path) + folder_listing: List[os.DirEntry] = os.scandir(folder_path) + is_under_scanned_path = is_path_under_parents(folder_path) + for item in folder_listing: + if not os.path.exists(item.path): + continue + fullpath = os.path.normpath(item.path) + name = os.path.basename(item.path) + stat = item.stat() + date = get_formatted_date(stat.st_mtime) + created_time = get_created_date_by_stat(stat) + if item.is_file(): + bytes = stat.st_size + size = human_readable_size(bytes) + files.append( + { + "type": "file", + "date": date, + "size": size, + "name": name, + "bytes": bytes, + "created_time": created_time, + "fullpath": fullpath, + "is_under_scanned_path": is_under_scanned_path, + } + ) + elif item.is_dir(): + files.append( + { + "type": "dir", + "date": date, + "created_time": created_time, + "size": "-", + "name": name, + "is_under_scanned_path": is_under_scanned_path, + "fullpath": fullpath, + } + ) + except Exception as e: + # logger.error(e) + raise HTTPException(status_code=400, detail=str(e)) + + return {"files": filter_allowed_files(files)} + + + @app.post(api_base + "/batch_get_files_info", dependencies=[Depends(verify_secret)]) + async def batch_get_files_info(req: PathsReq): + res = {} + for path in req.paths: + check_path_trust(path) + res[path] = get_file_info_by_path(path) + return res + + @app.get(api_base + "/image-thumbnail", dependencies=[Depends(verify_secret)]) + async def thumbnail(path: str, t: str, size: str = "256x256"): + check_path_trust(path) + if not cache_base_dir: + return + # 生成缓存文件的路径 + hash_dir = hashlib.md5((path + t).encode("utf-8")).hexdigest() + hash = hash_dir + size + cache_dir = os.path.join(cache_base_dir, "iib_cache", hash_dir) + cache_path = os.path.join(cache_dir, f"{size}.webp") + + # 如果缓存文件存在,则直接返回该文件 + if os.path.exists(cache_path): + return FileResponse( + cache_path, + media_type="image/webp", + headers={"Cache-Control": "max-age=31536000", "ETag": hash}, + ) + + + # 如果小于64KB,直接返回原图 + if os.path.getsize(path) < 64 * 1024: + return FileResponse( + path, + media_type="image/" + path.split(".")[-1], + headers={"Cache-Control": "max-age=31536000", "ETag": hash}, + ) + + + # 如果缓存文件不存在,则生成缩略图并保存 + with Image.open(path) as img: + w, h = size.split("x") + img.thumbnail((int(w), int(h))) + os.makedirs(cache_dir, exist_ok=True) + img.save(cache_path, "webp") + + # 返回缓存文件 + return FileResponse( + cache_path, + media_type="image/webp", + headers={"Cache-Control": "max-age=31536000", "ETag": hash}, + ) + + @app.get(api_base + "/file", dependencies=[Depends(verify_secret)]) + async def get_file(path: str, t: str, disposition: Optional[str] = None): + filename = path + import mimetypes + + check_path_trust(path) + if not os.path.exists(filename): + raise HTTPException(status_code=404) + if not os.path.isfile(filename): + raise HTTPException(status_code=400, detail=f"{filename} is not a file") + # 根据文件后缀名获取媒体类型 + media_type, _ = mimetypes.guess_type(filename) + headers = {} + if disposition: + encoded_filename = urllib.parse.quote(disposition.encode('utf-8')) + headers['Content-Disposition'] = f"attachment; filename*=UTF-8''{encoded_filename}" + + if is_path_under_parents(filename) and is_valid_media_path( + filename + ): # 认为永远不变,不要协商缓存了试试 + headers[ + "Cache-Control" + ] = "public, max-age=31536000" # 针对同样名字文件但实际上不同内容的文件要求必须传入创建时间来避免浏览器缓存 + headers["Expires"] = (datetime.now() + timedelta(days=365)).strftime( + "%a, %d %b %Y %H:%M:%S GMT" + ) + + return FileResponse( + filename, + media_type=media_type, + headers=headers, + ) + + @app.get(api_base + "/stream_video", dependencies=[Depends(verify_secret)]) + async def stream_video(path: str, request: Request): + check_path_trust(path) + import mimetypes + media_type, _ = mimetypes.guess_type(path) + return range_requests_response( + request, file_path=path, content_type=media_type + ) + + @app.get(api_base + "/video_cover", dependencies=[Depends(verify_secret)]) + async def video_cover(path: str, mt: str): + check_path_trust(path) + if not cache_base_dir: + return + + if not os.path.exists(path): + raise HTTPException(status_code=404) + if not os.path.isfile(path) and get_video_type(path): + raise HTTPException(status_code=400, detail=f"{path} is not a video file") + # 生成缓存文件的路径 + hash_dir = hashlib.md5((path + mt).encode("utf-8")).hexdigest() + hash = hash_dir + cache_dir = os.path.join(cache_base_dir, "iib_cache", "video_cover", hash_dir) + cache_path = os.path.join(cache_dir, "cover.webp") + # 如果缓存文件存在,则直接返回该文件 + if os.path.exists(cache_path): + return FileResponse( + cache_path, + media_type="image/webp", + headers={ + "Cache-Control": "no-store", + }, + ) + # 如果缓存文件不存在,则生成缩略图并保存 + + import imageio.v3 as iio + frame = iio.imread( + path, + index=16, + plugin="pyav", + ) + + os.makedirs(cache_dir, exist_ok=True) + iio.imwrite(cache_path,frame, extension=".webp") + + # 返回缓存文件 + return FileResponse( + cache_path, + media_type="image/webp", + headers={ + "Cache-Control": "no-store", + }, + ) + + class SetTargetFrameAsCoverReq(BaseModel): + base64_img: str + path: str + updated_time: str + + def save_base64_image(base64_str, file_path): + if base64_str.startswith('data:image'): + base64_str = base64_str.split(',')[1] + image_data = base64.b64decode(base64_str) + with open(file_path, 'wb') as file: + file.write(image_data) + + @app.post(api_base+ "/set_target_frame_as_video_cover", dependencies=[Depends(verify_secret), Depends(write_permission_required)]) + async def set_target_frame_as_video_cover(req: SetTargetFrameAsCoverReq): + hash_dir = hashlib.md5((req.path + req.updated_time).encode("utf-8")).hexdigest() + hash = hash_dir + cache_dir = os.path.join(cache_base_dir, "iib_cache", "video_cover", hash_dir) + cache_path = os.path.join(cache_dir, "cover.webp") + + os.makedirs(cache_dir, exist_ok=True) + + save_base64_image(req.base64_img, cache_path) + return FileResponse( + cache_path, + media_type="image/webp", + headers={"ETag": hash}, + ) + + @app.post(api_base + "/send_img_path", dependencies=[Depends(verify_secret)]) + async def api_set_send_img_path(path: str): + send_img_path["value"] = path + + # 等待图片信息生成完成 + @app.get(api_base + "/gen_info_completed", dependencies=[Depends(verify_secret)]) + async def api_set_send_img_path(): + for _ in range(30): # timeout 3s + if send_img_path["value"] == "": # 等待setup里面生成完成 + return True + v = send_img_path["value"] + # is_dev and logger.info("gen_info_completed %s %s", _, v) + await asyncio.sleep(0.1) + return send_img_path["value"] == "" + + @app.get(api_base + "/image_geninfo", dependencies=[Depends(verify_secret)]) + async def image_geninfo(path: str): + return parse_image_info(path).raw_info + + class GeninfoBatchReq(BaseModel): + paths: List[str] + + @app.post(api_base + "/image_geninfo_batch", dependencies=[Depends(verify_secret)]) + async def image_geninfo_batch(req: GeninfoBatchReq): + res = {} + conn = DataBase.get_conn() + for path in req.paths: + try: + img = DbImg.get(conn, path) + if img: + res[path] = img.exif + else: + res[path] = parse_image_info(path).raw_info + except Exception as e: + logger.error(e, stack_info=True) + return res + + + class CheckPathExistsReq(BaseModel): + paths: List[str] + + @app.post(api_base + "/check_path_exists", dependencies=[Depends(verify_secret)]) + async def check_path_exists(req: CheckPathExistsReq): + update_all_scanned_paths() + res = {} + for path in req.paths: + res[path] = os.path.exists(path) and is_path_trusted(path) + return res + + @app.get(api_base) + def index_bd(): + if fe_public_path: + with open(index_html_path, "r", encoding="utf-8") as file: + content = file.read().replace(DEFAULT_BASE, fe_public_path) + return Response(content=content, media_type="text/html") + return FileResponse(index_html_path) + + static_dir = f"{cwd}/vue/dist" + @app.get(api_base + "/fe-static/{file_path:path}") + async def serve_static_file(file_path: str): + file_full_path = f"{static_dir}/{file_path}" + if file_path.endswith(".js"): + with open(file_full_path, "r", encoding="utf-8") as file: + content = file.read().replace(DEFAULT_BASE, fe_public_path) + return Response(content=content, media_type="text/javascript") + else: + return FileResponse(file_full_path) + + class OpenFolderReq(BaseModel): + path: str + + @app.post( + api_base + "/open_folder", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + def open_folder_using_explore(req: OpenFolderReq): + if not is_path_trusted(req.path): + raise HTTPException(status_code=403) + open_folder(*os.path.split(req.path)) + + @app.post(api_base + "/shutdown") + async def shutdown_app(): + # This API endpoint is mainly used as a sidecar in Tauri applications to shut down the application + if not kwargs.get("enable_shutdown"): + raise HTTPException(status_code=403, detail="Shutdown is disabled.") + os.kill(os.getpid(), 9) + return {"message": "Application is shutting down."} + + @app.post( + api_base + "/zip", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + def zip_files(req: PathsReq): + for path in req.paths: + check_path_trust(path) + if not os.path.isfile(path): + raise HTTPException(400, "The corresponding path must be a file.") + now = datetime.now() + timestamp = now.strftime("%Y-%m-%d-%H-%M-%S") + zip_temp_dir = os.path.join(cwd, "zip_temp") + os.makedirs(zip_temp_dir, exist_ok=True) + file_path = os.path.join(zip_temp_dir, f"iib_batch_download_{timestamp}.zip") + create_zip_file(req.paths, file_path) + return FileResponse(file_path, media_type="application/zip") + + @app.post( + api_base + "/open_with_default_app", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + def open_target_file_withDefault_app(req: OpenFolderReq): + check_path_trust(req.path) + open_file_with_default_app(req.path) + + + @app.post( + api_base + "/batch_top_4_media_info", + dependencies=[Depends(verify_secret)], + ) + def batch_get_top_4_media_cover_info(req: PathsReq): + for path in req.paths: + check_path_trust(path) + res = {} + for path in req.paths: + res[path] = get_top_4_media_info(path) + return res + + db_api_base = api_base + "/db" + + @app.get(db_api_base + "/basic_info", dependencies=[Depends(verify_secret)]) + async def get_db_basic_info(): + conn = DataBase.get_conn() + img_count = DbImg.count(conn) + tags = Tag.get_all(conn) + expired_dirs = Folder.get_expired_dirs(conn) + return { + "img_count": img_count, + "tags": tags, + "expired": len(expired_dirs) != 0, + "expired_dirs": expired_dirs, + } + + @app.get(db_api_base + "/expired_dirs", dependencies=[Depends(verify_secret)]) + async def get_db_expired(): + conn = DataBase.get_conn() + expired_dirs = Folder.get_expired_dirs(conn) + return { + "expired": len(expired_dirs) != 0, + "expired_dirs": expired_dirs, + } + + @app.post( + db_api_base + "/update_image_data", + dependencies=[Depends(verify_secret)], + ) + async def update_image_db_data(): + try: + DataBase._initing = True + conn = DataBase.get_conn() + img_count = DbImg.count(conn) + update_extra_paths(conn) + dirs = ( + get_img_search_dirs() + if img_count == 0 + else Folder.get_expired_dirs(conn) + ) + mem["extra_paths"] + + update_image_data(dirs) + finally: + DataBase._initing = False + + class SearchBySubstrReq(BaseModel): + surstr: str + cursor: str + regexp: str + folder_paths: List[str] = None + size: Optional[int] = 200 + + @app.post(db_api_base + "/search_by_substr", dependencies=[Depends(verify_secret)]) + async def search_by_substr(req: SearchBySubstrReq): + if IIB_DEBUG: + logger.info(req) + conn = DataBase.get_conn() + folder_paths=normalize_paths(req.folder_paths, os.getcwd()) + if(not folder_paths and req.folder_paths): + return { "files": [], "cursor": Cursor(has_next=False) } + imgs, next_cursor = DbImg.find_by_substring( + conn=conn, + substring=req.surstr, + cursor=req.cursor, + limit=req.size, + regexp=req.regexp, + folder_paths=folder_paths + ) + return { + "files": filter_allowed_files([x.to_file_info() for x in imgs]), + "cursor": next_cursor + } + + class MatchImagesByTagsReq(BaseModel): + and_tags: List[int] + or_tags: List[int] + not_tags: List[int] + cursor: str + folder_paths: List[str] = None + size: Optional[int] = 200 + + @app.post(db_api_base + "/match_images_by_tags", dependencies=[Depends(verify_secret)]) + async def match_image_by_tags(req: MatchImagesByTagsReq): + if IIB_DEBUG: + logger.info(req) + conn = DataBase.get_conn() + folder_paths=normalize_paths(req.folder_paths, os.getcwd()) + if(not folder_paths and req.folder_paths): + return { "files": [], "cursor": Cursor(has_next=False) } + imgs, next_cursor = ImageTag.get_images_by_tags( + conn=conn, + tag_dict={"and": req.and_tags, "or": req.or_tags, "not": req.not_tags}, + cursor=req.cursor, + folder_paths=folder_paths, + limit=req.size + ) + return { + "files": filter_allowed_files([x.to_file_info() for x in imgs]), + "cursor": next_cursor + } + + @app.get(db_api_base + "/img_selected_custom_tag", dependencies=[Depends(verify_secret)]) + async def get_img_selected_custom_tag(path: str): + path = os.path.normpath(path) + if not is_valid_media_path(path): + return [] + conn = DataBase.get_conn() + update_extra_paths(conn) + if not is_path_under_parents(path): + return [] + img = DbImg.get(conn, path) + if not img: + if DbImg.count(conn) == 0: + return [] + update_image_data([os.path.dirname(path)]) + img = DbImg.get(conn, path) + assert img + # tags = Tag.get_all_custom_tag() + return ImageTag.get_tags_for_image(conn, img.id, type="custom") + + @app.post(db_api_base + "/get_image_tags", dependencies=[Depends(verify_secret)]) + async def get_img_tags(req: PathsReq): + conn = DataBase.get_conn() + return ImageTag.batch_get_tags_by_path(conn, req.paths) + + class ToggleCustomTagToImgReq(BaseModel): + img_path: str + tag_id: int + + @app.post( + db_api_base + "/toggle_custom_tag_to_img", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def toggle_custom_tag_to_img(req: ToggleCustomTagToImgReq): + conn = DataBase.get_conn() + path = os.path.normpath(req.img_path) + update_extra_paths(conn) + if not is_path_under_parents(path): + raise HTTPException( + 400, + '当前文件不在搜索路径内,你可以将它添加到扫描路径再尝试。在右上角的"更多"里面' + if locale == "zh" + else 'The current file is not within the scan path. You can add it to the scan path and try again. In the top right corner, click on "More".', + ) + img = DbImg.get(conn, path) + if not img: + if DbImg.count(conn): + # update_image_data([os.path.dirname(path)]) + add_image_data_single(path) + img = DbImg.get(conn, path) + else: + raise HTTPException( + 400, + "你需要先通过图像搜索页生成索引" + if locale == "zh" + else "You need to generate an index through the image search page first.", + ) + tags = ImageTag.get_tags_for_image( + conn=conn, image_id=img.id, type="custom", tag_id=req.tag_id + ) + is_remove = len(tags) + if is_remove: + ImageTag.remove(conn, img.id, tags[0].id) + else: + ImageTag(img.id, req.tag_id).save(conn) + conn.commit() + return {"is_remove": is_remove} + + class BatchUpdateImageReq(BaseModel): + img_paths: List[str] + action: str + tag_id: int + + @app.post( + db_api_base + "/batch_update_image_tag", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def batch_update_image_tag(req: BatchUpdateImageReq): + assert req.action in ["add", "remove"] + conn = DataBase.get_conn() + paths: List[str] = seq(req.img_paths).map(os.path.normpath).to_list() + update_extra_paths(conn) + for path in paths: + if not is_path_under_parents(path): + raise HTTPException( + 400, + '当前文件不在搜索路径内,你可以将它添加到扫描路径再尝试。在右上角的"更多"里面' + if locale == "zh" + else 'The current file is not within the scan path. You can add it to the scan path and try again. In the top right corner, click on "More".', + ) + img = DbImg.get(conn, path) + if not img: + if DbImg.count(conn): + add_image_data_single(path) + img = DbImg.get(conn, path) + else: + raise HTTPException( + 400, + "你需要先通过图像搜索页生成索引" + if locale == "zh" + else "You need to generate an index through the image search page first.", + ) + try: + for path in paths: + img = DbImg.get(conn, path) + if req.action == "add": + ImageTag(img.id, req.tag_id).save_or_ignore(conn) + else: + ImageTag.remove(conn, img.id, req.tag_id) + finally: + conn.commit() + + class AddCustomTagReq(BaseModel): + tag_name: str + + @app.post( + db_api_base + "/add_custom_tag", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def add_custom_tag(req: AddCustomTagReq): + conn = DataBase.get_conn() + tag = Tag.get_or_create(conn, name=req.tag_name, type="custom") + conn.commit() + return tag + + class RenameFileReq(BaseModel): + path: str + name: str + + @app.post( + db_api_base + "/rename", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def rename_file(req: RenameFileReq): + conn = DataBase.get_conn() + try: + # Normalize the paths + + path = os.path.normpath(req.path) + new_path = os.path.join(os.path.dirname(path), req.name) + + # Check if the file exists + if not os.path.exists(path): + raise HTTPException(status_code=404, detail="File not found") + + # Check if a file with the new name already exists + if os.path.exists(new_path): + raise HTTPException(status_code=400, detail="A file with the new name already exists") + close_video_file_reader(path) + img = DbImg.get(conn, path) + if img: + img.update_path(conn, new_path) + conn.commit() + + # Perform the file rename operation + os.rename(path, new_path) + + + return {"detail": "File renamed successfully", "new_path": new_path} + + except PermissionError: + raise HTTPException(status_code=403, detail="Permission denied") + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + class RemoveCustomTagReq(BaseModel): + tag_id: int + + @app.post( + db_api_base + "/remove_custom_tag", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def remove_custom_tag(req: RemoveCustomTagReq): + conn = DataBase.get_conn() + ImageTag.remove(conn, tag_id=req.tag_id) + Tag.remove(conn, req.tag_id) + + class RemoveCustomTagFromReq(BaseModel): + img_id: int + tag_id: str + + @app.post( + db_api_base + "/remove_custom_tag_from_img", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def remove_custom_tag_from_img(req: RemoveCustomTagFromReq): + conn = DataBase.get_conn() + ImageTag.remove(conn, image_id=req.img_id, tag_id=req.tag_id) + + + + + class ExtraPathModel(BaseModel): + path: str + types: List[str] + + @app.post( + f"{db_api_base}/extra_paths", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def create_extra_path(extra_path: ExtraPathModel): + if enable_access_control: + if not is_path_under_parents(extra_path.path): + raise HTTPException(status_code=403) + conn = DataBase.get_conn() + path = ExtraPath.get_target_path(conn, extra_path.path) + if path: + for t in extra_path.types: + path.types.append(t) + path.types = unique_by(path.types) + else: + path = ExtraPath(extra_path.path, extra_path.types) + try: + path.save(conn) + finally: + conn.commit() + + class ExtraPathAliasModel(BaseModel): + path: str + alias: str + + + @app.post( + f"{db_api_base}/alias_extra_path", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def alias_extra_path(req: ExtraPathAliasModel): + conn = DataBase.get_conn() + path = ExtraPath.get_target_path(conn, req.path) + if not path: + raise HTTPException(400) + path.alias = req.alias + try: + path.save(conn) + finally: + conn.commit() + return path + + + @app.get( + f"{db_api_base}/extra_paths", + dependencies=[Depends(verify_secret)], + ) + async def read_extra_paths(): + conn = DataBase.get_conn() + return ExtraPath.get_extra_paths(conn) + + + + @app.delete( + f"{db_api_base}/extra_paths", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def delete_extra_path(extra_path: ExtraPathModel): + path = to_abs_path(extra_path.path) + conn = DataBase.get_conn() + ExtraPath.remove(conn, path, extra_path.types, img_search_dirs=get_img_search_dirs()) + + + @app.post( + f"{db_api_base}/rebuild_index", + dependencies=[Depends(verify_secret), Depends(write_permission_required)], + ) + async def rebuild_index(): + update_extra_paths(conn = DataBase.get_conn()) + rebuild_image_index(search_dirs = get_img_search_dirs() + mem["extra_paths"]) + diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/__pycache__/datamodel.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/__pycache__/datamodel.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9104c488b9b35abb89ed2e5d993ed38efef654f7 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/__pycache__/datamodel.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/__pycache__/update_image_data.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/__pycache__/update_image_data.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a4dae7c34579ca522fd409747c7250488ee7251 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/__pycache__/update_image_data.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/datamodel.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/datamodel.py new file mode 100644 index 0000000000000000000000000000000000000000..818a96508a34a5d11b20dada49e35ea352e0509b --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/datamodel.py @@ -0,0 +1,840 @@ +from datetime import datetime +import json +from sqlite3 import Connection, connect +from enum import Enum +import sqlite3 +from typing import Dict, List, Optional, TypedDict, Union +from scripts.iib.tool import ( + cwd, + get_modified_date, + human_readable_size, + tags_translate, + is_dev, + find, + unique_by, +) +from contextlib import closing +import os +import threading +import re + + +class FileInfoDict(TypedDict): + type: str + date: float + size: int + name: str + bytes: bytes + created_time: float + fullpath: str + + +class Cursor: + def __init__(self, has_next=True, next=""): + self.has_next = has_next + self.next = next + + +class DataBase: + local = threading.local() + + _initing = False + + num = 0 + + path = "iib.db" + + @classmethod + def get_conn(clz) -> Connection: + # for : sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread + if hasattr(clz.local, "conn"): + return clz.local.conn + else: + conn = clz.init() + clz.local.conn = conn + + return conn + + @classmethod + def get_db_file_path(clz): + return clz.path if os.path.isabs(clz.path) else os.path.join(cwd, clz.path) + + @classmethod + def init(clz): + # 创建连接并打开数据库 + conn = connect(clz.get_db_file_path()) + + def regexp(expr, item): + if not isinstance(item, str): + return False + reg = re.compile(expr, flags=re.IGNORECASE | re.MULTILINE | re.DOTALL) + return reg.search(item) is not None + + conn.create_function("regexp", 2, regexp) + try: + Folder.create_table(conn) + ImageTag.create_table(conn) + Tag.create_table(conn) + Image.create_table(conn) + ExtraPath.create_table(conn) + DirCoverCache.create_table(conn) + GlobalSetting.create_table(conn) + finally: + conn.commit() + clz.num += 1 + if is_dev: + print(f"当前连接数{clz.num}") + return conn + + +class Image: + def __init__(self, path, exif=None, size=0, date="", id=None): + self.path = path + self.exif = exif + self.id = id + self.size = size + self.date = date + + def to_file_info(self) -> FileInfoDict: + return { + "type": "file", + "id": self.id, + "date": self.date, + "created_date": self.date, + "size": human_readable_size(self.size), + "is_under_scanned_path": True, + "bytes": self.size, + "name": os.path.basename(self.path), + "fullpath": self.path, + } + + def save(self, conn): + with closing(conn.cursor()) as cur: + cur.execute( + "INSERT OR REPLACE INTO image (path, exif, size, date) VALUES (?, ?, ?, ?)", + (self.path, self.exif, self.size, self.date), + ) + self.id = cur.lastrowid + + def update_path(self, conn: Connection, new_path: str, force=False): + self.path = os.path.normpath(new_path) + with closing(conn.cursor()) as cur: + if force: # force update path + cur.execute("DELETE FROM image WHERE path = ?", (self.path,)) + cur.execute("UPDATE image SET path = ? WHERE id = ?", (self.path, self.id)) + + @classmethod + def get(cls, conn: Connection, id_or_path): + with closing(conn.cursor()) as cur: + cur.execute( + "SELECT * FROM image WHERE id = ? OR path = ?", (id_or_path, id_or_path) + ) + row = cur.fetchone() + if row is None: + return None + else: + return cls.from_row(row) + + @classmethod + def get_by_ids(cls, conn: Connection, ids: List[int]) -> List["Image"]: + if not ids: + return [] + + query = """ + SELECT * FROM image + WHERE id IN ({}) + """.format( + ",".join("?" * len(ids)) + ) + + with closing(conn.cursor()) as cur: + cur.execute(query, ids) + rows = cur.fetchall() + + images = [] + for row in rows: + images.append(cls.from_row(row)) + return images + + @classmethod + def create_table(cls, conn): + with closing(conn.cursor()) as cur: + cur.execute( + """CREATE TABLE IF NOT EXISTS image ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + path TEXT UNIQUE, + exif TEXT, + size INTEGER, + date TEXT + )""" + ) + cur.execute("CREATE INDEX IF NOT EXISTS image_idx_path ON image(path)") + + @classmethod + def count(cls, conn): + with closing(conn.cursor()) as cur: + cur.execute("SELECT COUNT(*) FROM image") + count = cur.fetchone()[0] + return count + + @classmethod + def from_row(cls, row: tuple): + image = cls(path=row[1], exif=row[2], size=row[3], date=row[4]) + image.id = row[0] + return image + + @classmethod + def remove(cls, conn: Connection, image_id: int) -> None: + with closing(conn.cursor()) as cur: + cur.execute("DELETE FROM image WHERE id = ?", (image_id,)) + conn.commit() + + @classmethod + def safe_batch_remove(cls, conn: Connection, image_ids: List[int]) -> None: + if not (image_ids): + return + with closing(conn.cursor()) as cur: + try: + placeholders = ",".join("?" * len(image_ids)) + cur.execute( + f"DELETE FROM image_tag WHERE image_id IN ({placeholders})", + image_ids, + ) + cur.execute( + f"DELETE FROM image WHERE id IN ({placeholders})", image_ids + ) + except BaseException as e: + print(e) + finally: + conn.commit() + + @classmethod + def find_by_substring( + cls, conn: Connection, substring: str, limit: int = 500, cursor="", regexp="", + folder_paths: List[str] = [] + ) -> tuple[List["Image"], Cursor]: + api_cur = Cursor() + with closing(conn.cursor()) as cur: + params = [] + where_clauses = [] + if regexp: + where_clauses.append("(exif REGEXP ?)") + params.append(regexp) + else: + where_clauses.append("(path LIKE ? OR exif LIKE ?)") + params.extend((f"%{substring}%", f"%{substring}%")) + if cursor: + where_clauses.append("(date < ?)") + params.append(cursor) + if folder_paths: + folder_clauses = [] + for folder_path in folder_paths: + folder_clauses.append("(image.path LIKE ?)") + params.append(os.path.join(folder_path, "%")) + where_clauses.append("(" + " OR ".join(folder_clauses) + ")") + sql = "SELECT * FROM image" + if where_clauses: + sql += " WHERE " + sql += " AND ".join(where_clauses) + sql += " ORDER BY date DESC LIMIT ? " + params.append(limit) + cur.execute(sql, params) + rows = cur.fetchall() + + api_cur.has_next = len(rows) >= limit + images = [] + deleted_ids = [] + for row in rows: + img = cls.from_row(row) + if os.path.exists(img.path): + images.append(img) + else: + deleted_ids.append(img.id) + cls.safe_batch_remove(conn, deleted_ids) + if images: + api_cur.next = str(images[-1].date) + return images, api_cur + + +class Tag: + def __init__(self, name: str, score: int, type: str, count=0): + self.name = name + self.score = score + self.type = type + self.count = count + self.id = None + self.display_name = tags_translate.get(name) + + def save(self, conn): + with closing(conn.cursor()) as cur: + cur.execute( + "INSERT OR REPLACE INTO tag (id, name, score, type, count) VALUES (?, ?, ?, ?, ?)", + (self.id, self.name, self.score, self.type, self.count), + ) + self.id = cur.lastrowid + + @classmethod + def remove(cls, conn, tag_id): + with closing(conn.cursor()) as cur: + cur.execute("DELETE FROM tag WHERE id = ?", (tag_id,)) + conn.commit() + + @classmethod + def get(cls, conn: Connection, id): + with closing(conn.cursor()) as cur: + cur.execute("SELECT * FROM tag WHERE id = ?", (id,)) + row = cur.fetchone() + if row is None: + return None + else: + return cls.from_row(row) + + @classmethod + def get_all_custom_tag(cls, conn): + with closing(conn.cursor()) as cur: + cur.execute("SELECT * FROM tag where type = 'custom'") + rows = cur.fetchall() + tags: list[Tag] = [] + for row in rows: + tags.append(cls.from_row(row)) + return tags + + @classmethod + def get_all(cls, conn): + with closing(conn.cursor()) as cur: + cur.execute("SELECT * FROM tag") + rows = cur.fetchall() + tags: list[Tag] = [] + for row in rows: + tags.append(cls.from_row(row)) + return tags + + @classmethod + def get_or_create(cls, conn: Connection, name: str, type: str): + assert name and type + with closing(conn.cursor()) as cur: + cur.execute( + "SELECT tag.* FROM tag WHERE name = ? and type = ?", (name, type) + ) + row = cur.fetchone() + if row is None: + tag = cls(name=name, score=0, type=type) + tag.save(conn) + return tag + else: + return cls.from_row(row) + + @classmethod + def from_row(cls, row: tuple): + tag = cls(name=row[1], score=row[2], type=row[3], count=row[4]) + tag.id = row[0] + return tag + + @classmethod + def create_table(cls, conn): + with closing(conn.cursor()) as cur: + cur.execute( + """CREATE TABLE IF NOT EXISTS tag ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + score INTEGER, + type TEXT, + count INTEGER, + UNIQUE(name, type) ON CONFLICT REPLACE + ); + """ + ) + cur.execute("CREATE INDEX IF NOT EXISTS tag_idx_name ON tag(name)") + cur.execute( + """INSERT OR IGNORE INTO tag(name, score, type, count) + VALUES ("like", 0, "custom", 0); + """ + ) + + +class ImageTag: + def __init__(self, image_id: int, tag_id: int): + assert tag_id and image_id + self.image_id = image_id + self.tag_id = tag_id + + def save(self, conn): + with closing(conn.cursor()) as cur: + cur.execute( + "INSERT INTO image_tag (image_id, tag_id) VALUES (?, ?)", + (self.image_id, self.tag_id), + ) + + def save_or_ignore(self, conn): + with closing(conn.cursor()) as cur: + cur.execute( + "INSERT OR IGNORE INTO image_tag (image_id, tag_id) VALUES (?, ?)", + (self.image_id, self.tag_id), + ) + + @classmethod + def get_tags_for_image( + cls, + conn: Connection, + image_id: int, + tag_id: Optional[int] = None, + type: Optional[str] = None, + ): + with closing(conn.cursor()) as cur: + query = "SELECT tag.* FROM tag INNER JOIN image_tag ON tag.id = image_tag.tag_id WHERE image_tag.image_id = ?" + params = [image_id] + if tag_id: + query += " AND image_tag.tag_id = ?" + params.append(tag_id) + if type: + query += " AND tag.type = ?" + params.append(type) + cur.execute(query, tuple(params)) + rows = cur.fetchall() + return [Tag.from_row(x) for x in rows] + + @classmethod + def get_images_for_tag(cls, conn: Connection, tag_id): + with closing(conn.cursor()) as cur: + cur.execute( + "SELECT image.* FROM image INNER JOIN image_tag ON image.id = image_tag.image_id WHERE image_tag.tag_id = ?", + (tag_id,), + ) + rows = cur.fetchall() + images = [] + for row in rows: + images.append(Image.from_row(row)) + return images + + @classmethod + def create_table(cls, conn): + with closing(conn.cursor()) as cur: + cur.execute( + """CREATE TABLE IF NOT EXISTS image_tag ( + image_id INTEGER, + tag_id INTEGER, + FOREIGN KEY (image_id) REFERENCES image(id), + FOREIGN KEY (tag_id) REFERENCES tag(id), + PRIMARY KEY (image_id, tag_id) + )""" + ) + + @classmethod + def get_images_by_tags( + cls, + conn: Connection, + tag_dict: Dict[str, List[int]], + limit: int = 500, + cursor="", + folder_paths: List[str] = None, + ) -> tuple[List[Image], Cursor]: + query = """ + SELECT image.id, image.path, image.size,image.date + FROM image + INNER JOIN image_tag ON image.id = image_tag.image_id + """ + + where_clauses = [] + params = [] + + for operator, tag_ids in tag_dict.items(): + if operator == "and" and tag_dict["and"]: + where_clauses.append( + "tag_id IN ({})".format(",".join("?" * len(tag_ids))) + ) + params.extend(tag_ids) + elif operator == "not" and tag_dict["not"]: + where_clauses.append( + """(image_id NOT IN ( + SELECT image_id + FROM image_tag + WHERE tag_id IN ({}) +))""".format( + ",".join("?" * len(tag_ids)) + ) + ) + params.extend(tag_ids) + elif operator == "or" and tag_dict["or"]: + where_clauses.append( + """(image_id IN ( + SELECT image_id + FROM image_tag + WHERE tag_id IN ({}) + GROUP BY image_id + HAVING COUNT(DISTINCT tag_id) >= 1 +) +)""".format( + ",".join("?" * len(tag_ids)) + ) + ) + params.extend(tag_ids) + + if folder_paths: + folder_clauses = [] + for folder_path in folder_paths: + folder_clauses.append("(image.path LIKE ?)") + params.append(os.path.join(folder_path, "%")) + print(folder_path) + where_clauses.append("(" + " OR ".join(folder_clauses) + ")") + + if cursor: + where_clauses.append("(image.date < ?)") + params.append(cursor) + if where_clauses: + query += " WHERE " + " AND ".join(where_clauses) + query += " GROUP BY image.id" + if "and" in tag_dict and tag_dict['and']: + query += " HAVING COUNT(DISTINCT tag_id) = ?" + params.append(len(tag_dict["and"])) + + query += " ORDER BY date DESC LIMIT ?" + params.append(limit) + api_cur = Cursor() + with closing(conn.cursor()) as cur: + cur.execute(query, params) + rows = cur.fetchall() + images = [] + deleted_ids = [] + for row in rows: + img = Image(id=row[0], path=row[1], size=row[2], date=row[3]) + if os.path.exists(img.path): + images.append(img) + else: + deleted_ids.append(img.id) + Image.safe_batch_remove(conn, deleted_ids) + api_cur.has_next = len(rows) >= limit + if images: + api_cur.next = str(images[-1].date) + return images, api_cur + + @classmethod + def batch_get_tags_by_path( + cls, conn: Connection, paths: List[str], type="custom" + ) -> Dict[str, List[Tag]]: + if not paths: + return {} + tag_dict = {} + with closing(conn.cursor()) as cur: + placeholders = ",".join("?" * len(paths)) + query = f""" + SELECT image.path, tag.* FROM image_tag + INNER JOIN image ON image_tag.image_id = image.id + INNER JOIN tag ON image_tag.tag_id = tag.id + WHERE tag.type = '{type}' AND image.path IN ({placeholders}) + """ + cur.execute(query, paths) + rows = cur.fetchall() + for row in rows: + path = row[0] + tag = Tag.from_row(row[1:]) + if path in tag_dict: + tag_dict[path].append(tag) + else: + tag_dict[path] = [tag] + return tag_dict + + @classmethod + def remove( + cls, + conn: Connection, + image_id: Optional[int] = None, + tag_id: Optional[int] = None, + ) -> None: + assert image_id or tag_id + with closing(conn.cursor()) as cur: + if tag_id and image_id: + cur.execute( + "DELETE FROM image_tag WHERE image_id = ? and tag_id = ?", + (image_id, tag_id), + ) + elif tag_id: + cur.execute("DELETE FROM image_tag WHERE tag_id = ?", (tag_id,)) + else: + cur.execute("DELETE FROM image_tag WHERE image_id = ?", (image_id,)) + conn.commit() + + +class Folder: + def __init__(self, id: int, path: str, modified_date: str): + self.id = id + self.path = path + self.modified_date = modified_date + + @classmethod + def create_table(cls, conn): + with closing(conn.cursor()) as cur: + cur.execute( + """CREATE TABLE IF NOT EXISTS folders + (id INTEGER PRIMARY KEY AUTOINCREMENT, + path TEXT, + modified_date TEXT)""" + ) + cur.execute("CREATE INDEX IF NOT EXISTS folders_idx_path ON folders(path)") + + @classmethod + def check_need_update(cls, conn: Connection, folder_path: str): + folder_path = os.path.normpath(folder_path) + with closing(conn.cursor()) as cur: + if not os.path.exists(folder_path): + return False + cur.execute("SELECT * FROM folders WHERE path=?", (folder_path,)) + folder_record = cur.fetchone() # 如果这个文件夹没有记录,或者修改时间与数据库不同,则需要修改 + return not folder_record or ( + folder_record[2] != get_modified_date(folder_path) + ) + + @classmethod + def update_modified_date_or_create(cls, conn: Connection, folder_path: str): + folder_path = os.path.normpath(folder_path) + with closing(conn.cursor()) as cur: + cur.execute("SELECT * FROM folders WHERE path = ?", (folder_path,)) + row = cur.fetchone() + if row: + cur.execute( + "UPDATE folders SET modified_date = ? WHERE path = ?", + (get_modified_date(folder_path), folder_path), + ) + else: + cur.execute( + "INSERT INTO folders (path, modified_date) VALUES (?, ?)", + (folder_path, get_modified_date(folder_path)), + ) + + @classmethod + def get_expired_dirs(cls, conn: Connection): + dirs: List[str] = [] + with closing(conn.cursor()) as cur: + cur.execute("SELECT * FROM folders") + result_set = cur.fetchall() + extra_paths = ExtraPath.get_extra_paths(conn) + for ep in extra_paths: + if not find(result_set, lambda x: x[1] == ep.path): + dirs.append(ep.path) + for row in result_set: + folder_path = row[1] + if ( + os.path.exists(folder_path) + and get_modified_date(folder_path) != row[2] + ): + dirs.append(folder_path) + return unique_by(dirs, os.path.normpath) + + @classmethod + def remove_folder(cls, conn: Connection, folder_path: str): + folder_path = os.path.normpath(folder_path) + with closing(conn.cursor()) as cur: + cur.execute("DELETE FROM folders WHERE path = ?", (folder_path,)) + + @classmethod + def remove_all(cls, conn: Connection): + with closing(conn.cursor()) as cur: + cur.execute("DELETE FROM folders") + conn.commit() + + +class ExtraPathType(Enum): + scanned = "scanned" + scanned_fixed = "scanned-fixed" + walk = "walk" + cli_only = "cli_access_only" + + +class ExtraPath: + def __init__(self, path: str, types: Union[str, List[str]], alias = ''): + self.path = os.path.normpath(path) + self.types = types.split('+') if isinstance(types, str) else types + self.alias = alias + + def save(self, conn): + type_str = '+'.join(self.types) + for type in self.types: + assert type in [ExtraPathType.walk.value, ExtraPathType.scanned.value, ExtraPathType.scanned_fixed.value] + with closing(conn.cursor()) as cur: + cur.execute( + "INSERT INTO extra_path (path, type, alias) VALUES (?, ?, ?) " + "ON CONFLICT (path) DO UPDATE SET type = excluded.type, alias = excluded.alias", + (self.path, type_str, self.alias), + ) + + @classmethod + def get_target_path(cls, conn, path) -> Optional['ExtraPath']: + path = os.path.normpath(path) + query = f"SELECT * FROM extra_path where path = ?" + params = (path,) + with closing(conn.cursor()) as cur: + cur.execute(query, params) + rows = cur.fetchall() + paths: List[ExtraPath] = [] + for row in rows: + path = row[0] + if os.path.exists(path): + paths.append(ExtraPath(*row)) + else: + sql = "DELETE FROM extra_path WHERE path = ?" + cur.execute(sql, (path,)) + conn.commit() + return paths[0] if paths else None + + @classmethod + def get_extra_paths(cls, conn) -> List["ExtraPath"]: + query = "SELECT * FROM extra_path" + with closing(conn.cursor()) as cur: + cur.execute(query) + rows = cur.fetchall() + paths: List[ExtraPath] = [] + for row in rows: + path = row[0] + if os.path.exists(path): + paths.append(ExtraPath(*row)) + else: + cls.remove(conn, path) + return paths + + @classmethod + def remove( + cls, + conn, + path: str, + types: List[str] = None, + img_search_dirs: Optional[List[str]] = [], + ): + with closing(conn.cursor()) as cur: + path = os.path.normpath(path) + + target = cls.get_target_path(conn, path) + if not target: + return + new_types = [] + for type in target.types: + if type not in types: + new_types.append(type) + if new_types: + target.types = new_types + target.save(conn) + else: + sql = "DELETE FROM extra_path WHERE path = ?" + cur.execute(sql, (path,)) + + if path not in img_search_dirs: + Folder.remove_folder(conn, path) + conn.commit() + + @classmethod + def create_table(cls, conn): + with closing(conn.cursor()) as cur: + cur.execute( + """CREATE TABLE IF NOT EXISTS extra_path ( + path TEXT PRIMARY KEY, + type TEXT NOT NULL, + alias TEXT DEFAULT '' + )""" + ) + try: + cur.execute( + """ALTER TABLE extra_path + ADD COLUMN alias TEXT DEFAULT ''""" + ) + except sqlite3.OperationalError: + pass + +class DirCoverCache: + @classmethod + def create_table(cls, conn): + with closing(conn.cursor()) as cur: + cur.execute(""" + CREATE TABLE IF NOT EXISTS dir_cover_cache ( + folder_path TEXT PRIMARY KEY, + modified_time TEXT, + media_files TEXT + ) + """) + + @classmethod + def is_cache_expired(cls, conn, folder_path): + with closing(conn.cursor()) as cur: + cur.execute("SELECT modified_time FROM dir_cover_cache WHERE folder_path = ?", (folder_path,)) + result = cur.fetchone() + + if not result: + return True + + cached_time = datetime.fromisoformat(result[0]) + folder_modified_time = os.path.getmtime(folder_path) + return datetime.fromtimestamp(folder_modified_time) > cached_time + + @classmethod + def cache_media_files(cls, conn, folder_path, media_files): + media_files_json = json.dumps(media_files) + with closing(conn.cursor()) as cur: + cur.execute(""" + INSERT INTO dir_cover_cache (folder_path, modified_time, media_files) + VALUES (?, ?, ?) + ON CONFLICT(folder_path) DO UPDATE SET modified_time = excluded.modified_time, media_files = excluded.media_files + """, (folder_path, datetime.now().isoformat(), media_files_json)) + conn.commit() + + @classmethod + def get_cached_media_files(cls, conn, folder_path): + with closing(conn.cursor()) as cur: + cur.execute("SELECT media_files FROM dir_cover_cache WHERE folder_path = ?", (folder_path,)) + result = cur.fetchone() + + if result: + media_files_json = result[0] + return json.loads(media_files_json) + else: + return [] + + +class GlobalSetting: + @classmethod + def create_table(cls, conn): + with closing(conn.cursor()) as cur: + cur.execute( + """CREATE TABLE IF NOT EXISTS global_setting ( + setting_json TEXT, + name TEXT PRIMARY KEY, + created_time TEXT, + modified_time TEXT + )""" + ) + + @classmethod + def get_setting(cls, conn, name): + with closing(conn.cursor()) as cur: + cur.execute("SELECT setting_json FROM global_setting WHERE name = ?", (name,)) + result = cur.fetchone() + if result: + return json.loads(result[0]) + else: + return None + + @classmethod + def save_setting(cls, conn, name: str, setting: str): + json.loads(setting) # check if it is valid json + with closing(conn.cursor()) as cur: + cur.execute( + """INSERT INTO global_setting (setting_json, name, created_time, modified_time) + VALUES (?, ?, ?, ?) + ON CONFLICT(name) DO UPDATE SET setting_json = excluded.setting_json, modified_time = excluded.modified_time + """, + (setting, name, datetime.now().isoformat(), datetime.now().isoformat()), + ) + conn.commit() + + + @classmethod + def remove_setting(cls, conn, name: str): + with closing(conn.cursor()) as cur: + cur.execute("DELETE FROM global_setting WHERE name = ?", (name,)) + conn.commit() + + @classmethod + def get_all_settings(cls, conn): + with closing(conn.cursor()) as cur: + cur.execute("SELECT * FROM global_setting") + rows = cur.fetchall() + settings = {} + for row in rows: + settings[row[1]] = json.loads(row[0]) + return settings \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/update_image_data.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/update_image_data.py new file mode 100644 index 0000000000000000000000000000000000000000..c9b5a88d0cc16c21442c0b4d5dbf807d3abd5d50 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/db/update_image_data.py @@ -0,0 +1,199 @@ +from contextlib import closing +from typing import Dict, List +from scripts.iib.db.datamodel import Image as DbImg, Tag, ImageTag, DataBase, Folder +import os +from scripts.iib.tool import ( + is_valid_media_path, + get_modified_date, + get_video_type, + is_dev, + get_modified_date, + is_image_file, + case_insensitive_get +) +from scripts.iib.parsers.model import ImageGenerationInfo, ImageGenerationParams +from scripts.iib.logger import logger +from scripts.iib.parsers.index import parse_image_info +from scripts.iib.plugin import plugin_inst_map + +# 定义一个函数来获取图片文件的EXIF数据 +def get_exif_data(file_path): + if get_video_type(file_path): + return ImageGenerationInfo() + try: + return parse_image_info(file_path) + except Exception as e: + if is_dev: + logger.error("get_exif_data %s", e) + return ImageGenerationInfo() + + +def update_image_data(search_dirs: List[str], is_rebuild = False): + conn = DataBase.get_conn() + tag_incr_count_rec: Dict[int, int] = {} + + if is_rebuild: + Folder.remove_all(conn) + + def safe_save_img_tag(img_tag: ImageTag): + tag_incr_count_rec[img_tag.tag_id] = ( + tag_incr_count_rec.get(img_tag.tag_id, 0) + 1 + ) + img_tag.save_or_ignore(conn) # 原先用来处理一些意外,但是写的正确完全没问题,去掉了try catch + + # 递归处理每个文件夹 + def process_folder(folder_path: str): + if not Folder.check_need_update(conn, folder_path): + return + print(f"Processing folder: {folder_path}") + for filename in os.listdir(folder_path): + file_path = os.path.normpath(os.path.join(folder_path, filename)) + try: + + if os.path.isdir(file_path): + process_folder(file_path) + elif is_valid_media_path(file_path): + build_single_img_idx(conn, file_path, is_rebuild, safe_save_img_tag) + # neg暂时跳过感觉个没人会搜索这个 + except Exception as e: + logger.error("Tag generation failed. Skipping this file. file:%s error: %s", file_path, e) + # 提交对数据库的更改 + Folder.update_modified_date_or_create(conn, folder_path) + conn.commit() + + for dir in search_dirs: + process_folder(dir) + conn.commit() + for tag_id in tag_incr_count_rec: + tag = Tag.get(conn, tag_id) + tag.count += tag_incr_count_rec[tag_id] + tag.save(conn) + conn.commit() + +def add_image_data_single(file_path): + conn = DataBase.get_conn() + tag_incr_count_rec: Dict[int, int] = {} + + def safe_save_img_tag(img_tag: ImageTag): + tag_incr_count_rec[img_tag.tag_id] = ( + tag_incr_count_rec.get(img_tag.tag_id, 0) + 1 + ) + img_tag.save_or_ignore(conn) + + file_path = os.path.normpath(file_path) + try: + if not is_valid_media_path(file_path): + return + build_single_img_idx(conn, file_path, False, safe_save_img_tag) + # neg暂时跳过感觉个没人会搜索这个 + except Exception as e: + logger.error("Tag generation failed. Skipping this file. file:%s error: %s", file_path, e) + conn.commit() + + for tag_id in tag_incr_count_rec: + tag = Tag.get(conn, tag_id) + tag.count += tag_incr_count_rec[tag_id] + tag.save(conn) + conn.commit() + +def rebuild_image_index(search_dirs: List[str]): + conn = DataBase.get_conn() + with closing(conn.cursor()) as cur: + cur.execute( + """DELETE FROM image_tag + WHERE image_tag.tag_id IN ( + SELECT tag.id FROM tag WHERE tag.type <> 'custom' + ) + """ + ) + cur.execute("""DELETE FROM tag WHERE tag.type <> 'custom'""") + conn.commit() + update_image_data(search_dirs=search_dirs, is_rebuild=True) + + +def get_extra_meta_keys_from_plugins(source_identifier: str): + try: + plugin = plugin_inst_map.get(source_identifier) + if plugin: + return plugin.extra_convert_to_tag_meta_keys + except Exception as e: + logger.error("get_extra_meta_keys_from_plugins %s", e) + return [] + +def build_single_img_idx(conn, file_path, is_rebuild, safe_save_img_tag): + img = DbImg.get(conn, file_path) + parsed_params = None + if is_rebuild: + info = get_exif_data(file_path) + parsed_params = info.params + if not img: + img = DbImg( + file_path, + info.raw_info, + os.path.getsize(file_path), + get_modified_date(file_path), + ) + img.save(conn) + else: + if img: # 已存在的跳过 + if img.date == get_modified_date(img.path): + return + else: + DbImg.safe_batch_remove(conn=conn, image_ids=[img.id]) + info = get_exif_data(file_path) + parsed_params = info.params + img = DbImg( + file_path, + info.raw_info, + os.path.getsize(file_path), + get_modified_date(file_path), + ) + img.save(conn) + + if not parsed_params: + return + meta = parsed_params.meta + lora = parsed_params.extra.get("lora", []) + lyco = parsed_params.extra.get("lyco", []) + pos = parsed_params.pos_prompt + size_tag = Tag.get_or_create( + conn, + str(meta.get("Size-1", 0)) + " * " + str(meta.get("Size-2", 0)), + type="size", + ) + safe_save_img_tag(ImageTag(img.id, size_tag.id)) + media_type_tag = Tag.get_or_create(conn, "Image" if is_image_file(file_path) else "Video", 'Media Type') + safe_save_img_tag(ImageTag(img.id, media_type_tag.id)) + keys = [ + "Model", + "Sampler", + "Source Identifier", + "Postprocess upscale by", + "Postprocess upscaler", + "Size", + "Refiner", + "Hires upscaler" + ] + keys += get_extra_meta_keys_from_plugins(meta.get("Source Identifier", "")) + for k in keys: + v = case_insensitive_get(meta, k) + if not v: + continue + + tag = Tag.get_or_create(conn, str(v), k) + safe_save_img_tag(ImageTag(img.id, tag.id)) + if "Hires upscaler" == k: + tag = Tag.get_or_create(conn, 'Hires All', k) + safe_save_img_tag(ImageTag(img.id, tag.id)) + elif "Refiner" == k: + tag = Tag.get_or_create(conn, 'Refiner All', k) + safe_save_img_tag(ImageTag(img.id, tag.id)) + for i in lora: + tag = Tag.get_or_create(conn, i["name"], "lora") + safe_save_img_tag(ImageTag(img.id, tag.id)) + for i in lyco: + tag = Tag.get_or_create(conn, i["name"], "lyco") + safe_save_img_tag(ImageTag(img.id, tag.id)) + for k in pos: + tag = Tag.get_or_create(conn, k, "pos") + safe_save_img_tag(ImageTag(img.id, tag.id)) \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/dir_cover_cache.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/dir_cover_cache.py new file mode 100644 index 0000000000000000000000000000000000000000..7b850d3af75b370f58e2787e1eb29eafaa6e1be9 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/dir_cover_cache.py @@ -0,0 +1,53 @@ +import os +from scripts.iib.db.datamodel import DirCoverCache, DataBase +from scripts.iib.tool import get_created_date_by_stat, get_formatted_date, is_valid_media_path, get_video_type, birthtime_sort_key_fn + +def get_top_4_media_info(folder_path): + """ + 获取给定文件夹路径下的前4个媒体文件的完整路径。 + + 参数: + folder_path (str): 文件夹的路径。 + + 返回值: + list: 包含前4个媒体文件完整路径的列表。 + """ + conn = DataBase.get_conn() + if DirCoverCache.is_cache_expired(conn, folder_path): + media_files = get_media_files_from_folder(folder_path) + DirCoverCache.cache_media_files(conn, folder_path, media_files) + else: + media_files = DirCoverCache.get_cached_media_files(conn, folder_path) + + return media_files[:4] + +def get_media_files_from_folder(folder_path): + """ + 从文件夹中获取媒体文件的完整路径。 + + 参数: + folder_path (str): 文件夹的路径。 + + 返回值: + list: 包含媒体文件完整路径的列表。 + """ + media_files = [] + with os.scandir(folder_path) as entries: + for entry in sorted(entries, key=birthtime_sort_key_fn, reverse=True): + if entry.is_file() and is_valid_media_path(entry.path): + name = os.path.basename(entry.path) + stat = entry.stat() + date = get_formatted_date(stat.st_mtime) + created_time = get_created_date_by_stat(stat) + media_files.append({ + "fullpath": entry.path, + "media_type": "video" if get_video_type(entry.path) else "image", + "type": "file", + "date": date, + "created_time": created_time, + "name": name, + }) + if len(media_files) > 3: + return media_files + + return media_files diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/fastapi_video.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/fastapi_video.py new file mode 100644 index 0000000000000000000000000000000000000000..bd2ade1ca72869d74582e118af40b7502f685680 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/fastapi_video.py @@ -0,0 +1,86 @@ +import os +from typing import BinaryIO + +from fastapi import FastAPI, HTTPException, Request, status +from fastapi.responses import StreamingResponse +from scripts.iib.tool import get_video_type + +video_file_handler = {} + +def close_video_file_reader(path): + if not get_video_type(path): + return + try: + video_file_handler[path].close() + except Exception as e: + print(f"close file error: {e}") + + +def send_bytes_range_requests( + file_path, start: int, end: int, chunk_size: int = 10_000 +): + """Send a file in chunks using Range Requests specification RFC7233 + + `start` and `end` parameters are inclusive due to specification + """ + with open(file_path, mode="rb") as f: + video_file_handler[file_path] = f + f.seek(start) + while (pos := f.tell()) <= end: + read_size = min(chunk_size, end + 1 - pos) + yield f.read(read_size) + + +def _get_range_header(range_header: str, file_size: int) -> tuple[int, int]: + def _invalid_range(): + return HTTPException( + status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE, + detail=f"Invalid request range (Range:{range_header!r})", + ) + + try: + h = range_header.replace("bytes=", "").split("-") + start = int(h[0]) if h[0] != "" else 0 + end = int(h[1]) if h[1] != "" else file_size - 1 + except ValueError: + raise _invalid_range() + + if start > end or start < 0 or end > file_size - 1: + raise _invalid_range() + return start, end + + +def range_requests_response( + request: Request, file_path: str, content_type: str +): + """Returns StreamingResponse using Range Requests of a given file""" + + file_size = os.stat(file_path).st_size + range_header = request.headers.get("range") + + headers = { + "content-type": content_type, + "accept-ranges": "bytes", + "content-encoding": "identity", + "content-length": str(file_size), + "access-control-expose-headers": ( + "content-type, accept-ranges, content-length, " + "content-range, content-encoding" + ), + } + start = 0 + end = file_size - 1 + status_code = status.HTTP_200_OK + + if range_header is not None: + start, end = _get_range_header(range_header, file_size) + size = end - start + 1 + headers["content-length"] = str(size) + headers["content-range"] = f"bytes {start}-{end}/{file_size}" + status_code = status.HTTP_206_PARTIAL_CONTENT + + return StreamingResponse( + send_bytes_range_requests(file_path, start, end), + headers=headers, + status_code=status_code, + ) diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/img_cache_gen.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/img_cache_gen.py new file mode 100644 index 0000000000000000000000000000000000000000..be8d5e6821e94f7de49dcc5b37e8d3701eed0bd2 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/img_cache_gen.py @@ -0,0 +1,58 @@ +import hashlib +import os +from typing import List +from scripts.iib.tool import get_formatted_date, get_cache_dir, is_image_file +from concurrent.futures import ThreadPoolExecutor +import time +from PIL import Image + +def generate_image_cache(dirs, size:str, verbose=False): + start_time = time.time() + cache_base_dir = get_cache_dir() + + def process_image(item): + if item.is_dir(): + verbose and print(f"Processing directory: {item.path}") + for sub_item in os.scandir(item.path): + process_image(sub_item) + return + if not os.path.exists(item.path) or not is_image_file(item.path): + return + + try: + path = os.path.normpath(item.path) + stat = item.stat() + t = get_formatted_date(stat.st_mtime) + hash_dir = hashlib.md5((path + t).encode("utf-8")).hexdigest() + cache_dir = os.path.join(cache_base_dir, "iib_cache", hash_dir) + cache_path = os.path.join(cache_dir, f"{size}.webp") + + if os.path.exists(cache_path): + verbose and print(f"Image cache already exists: {path}") + return + + if os.path.getsize(path) < 64 * 1024: + verbose and print(f"Image size less than 64KB: {path}", "skip") + return + + with Image.open(path) as img: + w, h = size.split("x") + img.thumbnail((int(w), int(h))) + os.makedirs(cache_dir, exist_ok=True) + img.save(cache_path, "webp") + + verbose and print(f"Image cache generated: {path}") + except Exception as e: + print(f"Error generating image cache: {path}") + print(e) + + with ThreadPoolExecutor() as executor: + for dir_path in dirs: + folder_listing: List[os.DirEntry] = os.scandir(dir_path) + for item in folder_listing: + executor.submit(process_image, item) + + print("Image cache generation completed. ✨") + end_time = time.time() + execution_time = end_time - start_time + print(f"Execution time: {execution_time} seconds") \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/logger.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..3d795279695ff805e6076982e02794c5c6912581 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/logger.py @@ -0,0 +1,20 @@ + +from scripts.iib.tool import is_dev,cwd + +import logging +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.INFO) + +file_handler = logging.FileHandler(f"{cwd}/log.log") +file_handler.setLevel(logging.DEBUG) + +formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") +console_handler.setFormatter(formatter) +file_handler.setFormatter(formatter) + +logger.addHandler(file_handler) +if is_dev: + logger.addHandler(console_handler) diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/comfyui.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/comfyui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05f023fe496faef0b25ed3b48fd5b80fb9c11edb Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/comfyui.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/fooocus.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/fooocus.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a47c461e9fe7b3729ff56a9ebb603f39a33ad4c Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/fooocus.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/index.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/index.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de5baeb8f2a96e16b407165d6dee8aa2c91028d2 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/index.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/model.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/model.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed6a6c1e8bf54749d87776008b6eee60812c5bd2 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/model.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/novelai.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/novelai.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0eb18dc58eaed27208b386b5b2dca89d0941ed5c Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/novelai.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/sd_webui.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/sd_webui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe58b8cdebd101d6148350fd7859e948db0b3cb7 Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/sd_webui.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/stable_swarm_ui.cpython-310.pyc b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/stable_swarm_ui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5076289116d5df23a6cbb8421e0abb05c4f128ef Binary files /dev/null and b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/__pycache__/stable_swarm_ui.cpython-310.pyc differ diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/comfyui.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/comfyui.py new file mode 100644 index 0000000000000000000000000000000000000000..4a48eba786a7c6e32a256f6307cfb53ea4de52b2 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/comfyui.py @@ -0,0 +1,51 @@ +from PIL import Image + +from scripts.iib.tool import ( + comfyui_exif_data_to_str, + is_img_created_by_comfyui, + is_img_created_by_comfyui_with_webui_gen_info, + get_comfyui_exif_data, + parse_generation_parameters, + read_sd_webui_gen_info_from_image, +) +from scripts.iib.parsers.model import ImageGenerationInfo, ImageGenerationParams +from scripts.iib.logger import logger + + +class ComfyUIParser: + def __init__(self): + pass + + @classmethod + def parse(clz, img, file_path): + info = "" + params = None + if not clz.test(img, file_path): + raise Exception("The input image does not match the current parser.") + try: + if is_img_created_by_comfyui_with_webui_gen_info(img): + info = read_sd_webui_gen_info_from_image(img, file_path) + info += ", Source Identifier: ComfyUI" + params = parse_generation_parameters(info) + else: + params = get_comfyui_exif_data(img) + info = comfyui_exif_data_to_str(params) + except Exception: + logger.error('parse comfyui image failed. prompt:') + logger.error(img.info.get('prompt')) + return ImageGenerationInfo() + return ImageGenerationInfo( + info, + ImageGenerationParams( + meta=params["meta"], pos_prompt=params["pos_prompt"], extra=params + ), + ) + + @classmethod + def test(clz, img: Image, file_path: str) -> bool: + try: + return is_img_created_by_comfyui( + img + ) or is_img_created_by_comfyui_with_webui_gen_info(img) + except Exception: + return False diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/fooocus.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/fooocus.py new file mode 100644 index 0000000000000000000000000000000000000000..7c1df198e5ba93365935d8da12cff27353314f9e --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/fooocus.py @@ -0,0 +1,77 @@ +import os +import re +from PIL import Image + +from scripts.iib.parsers.model import ImageGenerationInfo, ImageGenerationParams +from scripts.iib.tool import omit, parse_generation_parameters, unique_by + + +def remove_extra_spaces(text): + return re.sub(r"\s+", " ", text) + + +def get_log_file(file_path: str): + dir = os.path.dirname(file_path) + with open(os.path.join(dir, "log.html")) as f: + return f.read() + +lora_re = re.compile("LoRA \d+", re.IGNORECASE) + +class FooocusParser: + def __init__(self): + pass + + @classmethod + def parse(clz, img: Image, file_path): + if not clz.test(img, file_path): + raise Exception("The input image does not match the current parser.") + from lxml import etree + + log = get_log_file(file_path) + root = etree.HTML(log) + id = str(os.path.basename(file_path)).replace(".", "_") + metadata = root.xpath(f'//div[@id="{id}"]/descendant::table[@class="metadata"]') + tr_elements = metadata[0].xpath(".//tr") + lora_list = [] + # As a workaround to bypass parsing errors in the parser. + # https://github.com/jiw0220/stable-diffusion-image-metadata/blob/00b8d42d4d1a536862bba0b07c332bdebb2a0ce5/src/index.ts#L130 + metadata_list_str = "Steps: Unknown , Source Identifier: Fooocus ," + params = {"meta": {"Source Identifier": "Fooocus"}} + for tr in tr_elements: + label = tr.xpath('.//td[@class="label" or @class="key"]/text()') + value = tr.xpath('.//td[@class="value"]/text()') + if label: + k = label[0] + v = value[0] if value else "None" + if k == "Fooocus V2 Expansion": + continue + if k == "Prompt" or k == "Negative Prompt": + params[k] = remove_extra_spaces(v.replace("\n", "").strip()) + else: + v = v.replace(",", ",") + params["meta"][k] = v + metadata_list_str += f" {k}: {v}," + if lora_re.search(k): + lora_list.append({ "name": v.strip(), "value": 1 }) + params["meta"]["Model"] = params["meta"]["Base Model"] + params["meta"]["Size"] = str(params["meta"]["Resolution"]).replace("(", "").replace(")", "").replace(",", " * ") + metadata_list_str = metadata_list_str.strip() + info = f"""{params['Prompt']}\nNegative prompt: {params['Negative Prompt']}\n{metadata_list_str}""".strip() + return ImageGenerationInfo( + info, + ImageGenerationParams( + meta=params["meta"], + pos_prompt=parse_generation_parameters(info)["pos_prompt"], + extra={ + "lora": unique_by(lora_list, lambda x: x["name"].lower()) + } + ), + ) + + @classmethod + def test(clz, img: Image, file_path: str): + filename = os.path.basename(file_path) + try: + return get_log_file(file_path).find(filename) != -1 + except Exception as e: + return False diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/index.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/index.py new file mode 100644 index 0000000000000000000000000000000000000000..93dcdd0b9cc608357d354229e096647a4c5cce89 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/index.py @@ -0,0 +1,31 @@ +from scripts.iib.parsers.comfyui import ComfyUIParser +from scripts.iib.parsers.sd_webui import SdWebUIParser +from scripts.iib.parsers.fooocus import FooocusParser +from scripts.iib.parsers.novelai import NovelAIParser +from scripts.iib.parsers.model import ImageGenerationInfo +from scripts.iib.parsers.stable_swarm_ui import StableSwarmUIParser +from scripts.iib.logger import logger +from PIL import Image +from scripts.iib.plugin import plugin_insts +import traceback + + +def parse_image_info(image_path: str) -> ImageGenerationInfo: + parsers = plugin_insts + [ + ComfyUIParser, + FooocusParser, + NovelAIParser, + StableSwarmUIParser, + SdWebUIParser, + ] + with Image.open(image_path) as img: + for parser in parsers: + if parser.test(img, image_path): + try: + return parser.parse(img, image_path) + except Exception as e: + logger.error(e, stack_info=True) + print(e) + print(traceback.format_exc()) + return ImageGenerationInfo() + raise Exception("matched parser is not found") diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/model.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/model.py new file mode 100644 index 0000000000000000000000000000000000000000..282048d8e4df6a38fb6b20b3ab235656a876229c --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/model.py @@ -0,0 +1,18 @@ +from scripts.iib.tool import omit + + +class ImageGenerationParams: + def __init__(self, meta: dict = {}, pos_prompt: list = [], extra: dict = {}) -> None: + self.meta = meta + self.pos_prompt = pos_prompt + self.extra = omit(extra, ["meta", "pos_prompt"]) + + +class ImageGenerationInfo: + def __init__( + self, + raw_info: str = "", + params: ImageGenerationParams = ImageGenerationParams(), + ): + self.raw_info = raw_info + self.params = params diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/novelai.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/novelai.py new file mode 100644 index 0000000000000000000000000000000000000000..680e1cd03417f5e2e55d110533ec620e0cd8bb0b --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/novelai.py @@ -0,0 +1,44 @@ +import json +from PIL import Image + +from scripts.iib.tool import ( + parse_generation_parameters, + replace_punctuation +) +from scripts.iib.parsers.model import ImageGenerationInfo, ImageGenerationParams + + +class NovelAIParser: + def __init__(self): + pass + + @classmethod + def parse(clz, img, file_path): + info = "" + params = None + if not clz.test(img, file_path): + raise Exception("The input image does not match the current parser.") + data = json.loads(img.info.get('Comment')) + meta_kv = [f"""Steps: {data["steps"]}, Source Identifier: NovelAI"""] + for key, value in data.items(): + if key not in ["prompt"]: + value = replace_punctuation(str(value)) + meta_kv.append(f"{key}: {value}") + meta = ', '.join(meta_kv) + info = data["prompt"] + '\n' + meta + + params = parse_generation_parameters(info) + + return ImageGenerationInfo( + info, + ImageGenerationParams( + meta=params["meta"], pos_prompt=params["pos_prompt"] + ), + ) + + @classmethod + def test(clz, img: Image, file_path: str) -> bool: + try: + return img.info.get('Software') == 'NovelAI' and isinstance(img.info.get('Comment'), str) + except Exception: + return False diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/sd_webui.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/sd_webui.py new file mode 100644 index 0000000000000000000000000000000000000000..88c11a51a3dc0509da33a572ffd427c4e271cd0c --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/sd_webui.py @@ -0,0 +1,35 @@ +from PIL import Image + +from scripts.iib.tool import ( + parse_generation_parameters, + read_sd_webui_gen_info_from_image, +) +from scripts.iib.parsers.model import ImageGenerationInfo, ImageGenerationParams + + +class SdWebUIParser: + def __init__(self): + pass + + @classmethod + def parse(clz, img: Image, file_path): + if not clz.test(img, file_path): + raise Exception("The input image does not match the current parser.") + info = read_sd_webui_gen_info_from_image(img, file_path) + if not info: + return ImageGenerationInfo() + info += ", Source Identifier: Stable Diffusion web UI" + params = parse_generation_parameters(info) + return ImageGenerationInfo( + info, + ImageGenerationParams( + meta=params["meta"], pos_prompt=params["pos_prompt"], extra=params + ), + ) + + @classmethod + def test(clz, img: Image, file_path: str): + try: + return True + except Exception as e: + return False diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/stable_swarm_ui.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/stable_swarm_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..8e41e043371f20b9c8844f3c9f143d0944c25a43 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/parsers/stable_swarm_ui.py @@ -0,0 +1,61 @@ +from PIL import Image + +import piexif +import piexif.helper +from scripts.iib.tool import parse_generation_parameters, replace_punctuation +from scripts.iib.parsers.model import ImageGenerationInfo, ImageGenerationParams +from PIL.ExifTags import TAGS +import json + + +class StableSwarmUIParser: + def __init__(self): + pass + + @classmethod + def get_exif_data(clz, image: Image) -> str: + items = image.info or {} + + if "exif" in items: + exif = piexif.load(items["exif"]) + exif_bytes = ( + (exif or {}).get("Exif", {}).get(piexif.ExifIFD.UserComment, b"") + ) + + unicode_start = exif_bytes.find(b"UNICODE") + if unicode_start == -1: + raise ValueError("'UNICODE' markup isn't found") + + unicode_data = exif_bytes[unicode_start + len("UNICODE") + 1 :] + geninfo = unicode_data.decode("utf-16") + return geninfo + + @classmethod + def parse(clz, img: Image, file_path): + if not clz.test(img, file_path): + raise Exception("The input image does not match the current parser.") + exif_data = json.loads(clz.get_exif_data(img))["sui_image_params"] + prompt = exif_data.pop("prompt") + negativeprompt = exif_data.pop("negativeprompt") + steps = exif_data.pop("steps") + meta_kv = [f"Steps: {steps}", "Source Identifier: StableSwarmUI"] + for key, value in exif_data.items(): + value = replace_punctuation(str(value)) + meta_kv.append(f"{key}: {value}") + meta = ", ".join(meta_kv) + info = "\n".join([prompt, f"Negative prompt: {negativeprompt}", meta]) + params = parse_generation_parameters(info) + return ImageGenerationInfo( + info, + ImageGenerationParams( + meta=params["meta"], pos_prompt=params["pos_prompt"], extra=params + ), + ) + + @classmethod + def test(clz, img: Image, file_path: str): + try: + exif = clz.get_exif_data(img) + return exif.find("sui_image_params") != -1 + except Exception as e: + return False diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/plugin.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/plugin.py new file mode 100644 index 0000000000000000000000000000000000000000..f37ce548828286e402db6911f7887f316f0f5484 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/plugin.py @@ -0,0 +1,34 @@ +import os +import importlib.util +import sys +from scripts.iib.tool import cwd + +def load_plugins(plugin_dir): + plugins = [] + for filename in os.listdir(plugin_dir): + main_module_path = os.path.join(plugin_dir, filename, 'main.py') + if not os.path.exists(main_module_path): + continue + spec = importlib.util.spec_from_file_location('main', main_module_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + plugins.append(module) + return plugins + +plugin_insts = [] +plugin_inst_map = {} +# 使用插件 +try: + plugin_dir = os.path.normpath(os.path.join(cwd, 'plugins')) + plugins = load_plugins(plugin_dir) + sys.path.append(plugin_dir) + for plugin in plugins: + try: + res = plugin.Main() + plugin_insts.append(res) + plugin_inst_map[res.source_identifier] = res + print(f'IIB loaded plugin: {res.name}') + except Exception as e: + print(f'Error running plugin {plugin.__class__.__name__}: {e}') +except Exception as e: + print(f'Error loading plugins: {e}') \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/seq.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/seq.py new file mode 100644 index 0000000000000000000000000000000000000000..a7c609c81c87b1bfa1f374ffb56aa49052cdb813 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/seq.py @@ -0,0 +1,21 @@ +class Seq: + def __init__(self, iterable): + self.iterable = iterable + + def map(self, func): + return Seq(func(item) for item in self.iterable) + + def filter(self, predicate): + return Seq(item for item in self.iterable if predicate(item)) + + def to_list(self): + return list(self.iterable) + + def __iter__(self): + return iter(self.iterable) + + def __repr__(self): + return f"Seq({repr(self.iterable)})" + +def seq(iterable): + return Seq(iterable) \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/tool.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/tool.py new file mode 100644 index 0000000000000000000000000000000000000000..c632e4e010181ab93f20771a877d2733c153d158 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/tool.py @@ -0,0 +1,726 @@ +import ctypes +from datetime import datetime +import os +import platform +import re +import tempfile +import subprocess +from typing import Dict, List +import sys +import piexif +import piexif.helper +import json +import zipfile +from PIL import Image +import shutil + + +sd_img_dirs = [ + "outdir_txt2img_samples", + "outdir_img2img_samples", + "outdir_save", + "outdir_extras_samples", + "outdir_grids", + "outdir_img2img_grids", + "outdir_samples", + "outdir_txt2img_grids", +] + + +is_dev = os.getenv("APP_ENV") == "dev" +is_nuitka = "__compiled__" in globals() +is_pyinstaller_bundle = bool(getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS')) +is_exe_ver = is_nuitka or is_pyinstaller_bundle + +cwd = os.getcwd() if is_exe_ver else os.path.normpath(os.path.join(__file__, "../../../")) +is_win = platform.system().lower().find("windows") != -1 + + + + +try: + from dotenv import load_dotenv + + load_dotenv(os.path.join(cwd, ".env")) +except Exception as e: + print(e) + + + +def backup_db_file(db_file_path): + + if not os.path.exists(db_file_path): + return + max_backup_count = int(os.environ.get('IIB_DB_FILE_BACKUP_MAX', '8')) + if max_backup_count < 1: + return + backup_folder = os.path.join(cwd,'iib_db_backup') + current_time = datetime.now() + timestamp = current_time.strftime('%Y-%m-%d %H-%M-%S') + backup_filename = f"iib.db_{timestamp}" + os.makedirs(backup_folder, exist_ok=True) + backup_filepath = os.path.join(backup_folder, backup_filename) + shutil.copy2(db_file_path, backup_filepath) + backup_files = os.listdir(backup_folder) + pattern = r"iib\.db_(\d{4}-\d{2}-\d{2} \d{2}-\d{2}-\d{2})" + backup_files_with_time = [(f, re.search(pattern, f).group(1)) for f in backup_files if re.search(pattern, f)] + sorted_backup_files = sorted(backup_files_with_time, key=lambda x: datetime.strptime(x[1], '%Y-%m-%d %H-%M-%S')) + + if len(sorted_backup_files) > max_backup_count: + files_to_remove_count = len(sorted_backup_files) - max_backup_count + for i in range(files_to_remove_count): + file_to_remove = os.path.join(backup_folder, sorted_backup_files[i][0]) + os.remove(file_to_remove) + + print(f"\033[92mIIB Database file has been successfully backed up to the backup folder.\033[0m") + +def get_sd_webui_conf(**kwargs): + try: + from modules.shared import opts + + return opts.data + except: + pass + try: + sd_conf_path = kwargs.get("sd_webui_config") + with codecs.open(sd_conf_path, "r", "utf-8") as f: + obj = json.loads(f.read()) + if kwargs.get("sd_webui_path_relative_to_config"): + for dir in sd_img_dirs: + if obj[dir] and not os.path.isabs(obj[dir]): + obj[dir] = os.path.normpath( + os.path.join(sd_conf_path, "../", obj[dir]) + ) + return obj + except: + pass + return {} + +def normalize_paths(paths: List[str], base = cwd): + """ + Normalize a list of paths, ensuring that each path is an absolute path with no redundant components. + + Args: + paths (List[str]): A list of paths to be normalized. + + Returns: + List[str]: A list of normalized paths. + """ + res: List[str] = [] + for path in paths: + # Skip empty or blank paths + if not path or len(path.strip()) == 0: + continue + # If the path is already an absolute path, use it as is + if os.path.isabs(path): + abs_path = path + # Otherwise, make the path absolute by joining it with the current working directory + else: + abs_path = os.path.join(base, path) + # If the absolute path exists, add it to the result after normalizing it + if os.path.exists(abs_path): + res.append(os.path.normpath(abs_path)) + return res + +def to_abs_path(path): + if not os.path.isabs(path): + path = os.path.join(os.getcwd(), path) + return os.path.normpath(path) + + +def get_valid_img_dirs( + conf, + keys=sd_img_dirs, +): + # 获取配置项 + paths = [conf.get(key) for key in keys] + + # 判断路径是否有效并转为绝对路径 + abs_paths = [] + for path in paths: + if not path or len(path.strip()) == 0: + continue + if os.path.isabs(path): # 已经是绝对路径 + abs_path = path + else: # 转为绝对路径 + abs_path = os.path.join(os.getcwd(), path) + if os.path.exists(abs_path): # 判断路径是否存在 + abs_paths.append(os.path.normpath(abs_path)) + + return abs_paths + + +def human_readable_size(size_bytes): + """ + Converts bytes to a human-readable format. + """ + # define the size units + units = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") + # calculate the logarithm of the input value with base 1024 + size = int(size_bytes) + if size == 0: + return "0B" + i = 0 + while size >= 1024 and i < len(units) - 1: + size /= 1024 + i += 1 + # round the result to two decimal points and return as a string + return "{:.2f} {}".format(size, units[i]) + + +def get_windows_drives(): + drives = [] + bitmask = ctypes.windll.kernel32.GetLogicalDrives() + for letter in range(65, 91): + if bitmask & 1: + drive_name = chr(letter) + ":/" + drives.append(drive_name) + bitmask >>= 1 + return drives + + +pattern = re.compile(r"(\d+\.?\d*)([KMGT]?B)", re.IGNORECASE) + + +def convert_to_bytes(file_size_str): + match = re.match(pattern, file_size_str) + if match: + size_str, unit_str = match.groups() + size = float(size_str) + unit = unit_str.upper() + if unit == "KB": + size *= 1024 + elif unit == "MB": + size *= 1024**2 + elif unit == "GB": + size *= 1024**3 + elif unit == "TB": + size *= 1024**4 + return int(size) + else: + raise ValueError(f"Invalid file size string '{file_size_str}'") + +def get_video_type(file_path): + video_extensions = ['.mp4', '.m4v', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.ts'] + file_extension = file_path[file_path.rfind('.'):].lower() + + if file_extension in video_extensions: + return file_extension[1:] + else: + return None + +def is_image_file(filename: str) -> bool: + if not isinstance(filename, str): + return False + + extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.avif'] + extension = filename.split('.')[-1].lower() + return f".{extension}" in extensions + +def is_video_file(filename: str) -> bool: + return isinstance(get_video_type(filename), str) + +def is_valid_media_path(path): + """ + 判断给定的路径是否是图像文件 + """ + abs_path = os.path.abspath(path) # 转为绝对路径 + if not os.path.exists(abs_path): # 判断路径是否存在 + return False + if not os.path.isfile(abs_path): # 判断是否是文件 + return False + return is_image_file(abs_path) or is_video_file(abs_path) + +def is_media_file(file_path): + return is_image_file(file_path) or is_video_file(file_path) + +def create_zip_file(file_paths: List[str], zip_file_name: str): + """ + 将文件打包成一个压缩包 + + Args: + file_paths: 文件路径的列表 + zip_file_name: 压缩包的文件名 + + Returns: + 无返回值 + """ + with zipfile.ZipFile(zip_file_name, 'w', zipfile.ZIP_DEFLATED) as zip_file: + for file_path in file_paths: + if os.path.isfile(file_path): + zip_file.write(file_path, os.path.basename(file_path)) + elif os.path.isdir(file_path): + for root, _, files in os.walk(file_path): + for file in files: + full_path = os.path.join(root, file) + zip_file.write(full_path, os.path.relpath(full_path, file_path)) + +def get_temp_path(): + """获取跨平台的临时文件目录路径""" + temp_path = None + try: + # 尝试获取系统环境变量中的临时文件目录路径 + temp_path = ( + os.environ.get("TMPDIR") or os.environ.get("TMP") or os.environ.get("TEMP") + ) + except Exception as e: + print("获取系统环境变量临时文件目录路径失败,错误信息:", e) + + # 如果系统环境变量中没有设置临时文件目录路径,则使用 Python 的 tempfile 模块创建临时文件目录 + if not temp_path: + try: + temp_path = tempfile.gettempdir() + except Exception as e: + print("使用 Python 的 tempfile 模块创建临时文件目录失败,错误信息:", e) + + # 确保临时文件目录存在 + if not os.path.exists(temp_path): + try: + os.makedirs(temp_path) + except Exception as e: + print("创建临时文件目录失败,错误信息:", e) + + return temp_path + + +_temp_path = get_temp_path() + + +def get_cache_dir(): + return os.getenv("IIB_CACHE_DIR") or _temp_path + +def get_secret_key_required(): + try: + from modules.shared import cmd_opts + return bool(cmd_opts.gradio_auth) + except: + return False + +is_secret_key_required = get_secret_key_required() + +def get_enable_access_control(): + ctrl = os.getenv("IIB_ACCESS_CONTROL") + if ctrl == "enable": + return True + if ctrl == "disable": + return False + try: + from modules.shared import cmd_opts + + return ( + cmd_opts.share or cmd_opts.ngrok or cmd_opts.listen or cmd_opts.server_name + ) + except: + pass + return False + + +enable_access_control = get_enable_access_control() + + +def get_locale(): + import locale + + env_lang = os.getenv("IIB_SERVER_LANG") + if env_lang in ["zh", "en"]: + return env_lang + lang, _ = locale.getdefaultlocale() + return "zh" if lang and lang.startswith("zh") else "en" + + +locale = get_locale() + + +def get_formatted_date(timestamp: float) -> str: + return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") + + +def get_modified_date(folder_path: str): + return get_formatted_date(os.path.getmtime(folder_path)) + + +def get_created_date(folder_path: str): + return get_formatted_date(os.path.getctime(folder_path)) + +is_st_birthtime_available = True + +def get_created_date_by_stat(stat: os.stat_result): + global is_st_birthtime_available + try: + if is_st_birthtime_available: + return get_formatted_date(stat.st_birthtime) + else: + return get_formatted_date(stat.st_ctime) + except Exception as e: + is_st_birthtime_available = False + return get_formatted_date(stat.st_ctime) + +def birthtime_sort_key_fn(x): + stat = x.stat() + global is_st_birthtime_available + try: + if is_st_birthtime_available and hasattr(stat, "st_birthtime"): + return stat.st_birthtime + else: + return stat.st_ctime + except: + is_st_birthtime_available = False + return stat.st_ctime + +def unique_by(seq, key_func=lambda x: x): + seen = set() + return [x for x in seq if not (key := key_func(x)) in seen and not seen.add(key)] + + +def find(lst, comparator): + return next((item for item in lst if comparator(item)), None) + + +def findIndex(lst, comparator): + return next((i for i, item in enumerate(lst) if comparator(item)), -1) + +def unquote(text): + if len(text) == 0 or text[0] != '"' or text[-1] != '"': + return text + + try: + return json.loads(text) + except Exception: + return text + +def get_img_geninfo_txt_path(path: str): + txt_path = re.sub(r"\.\w+$", ".txt", path) + if os.path.exists(txt_path): + return txt_path + +def is_img_created_by_comfyui(img: Image): + return img.info.get('prompt') and img.info.get('workflow') + +def is_img_created_by_comfyui_with_webui_gen_info(img: Image): + return is_img_created_by_comfyui(img) and img.info.get('parameters') + +def get_comfyui_exif_data(img: Image): + prompt = img.info.get('prompt') + if not prompt: + return {} + meta_key = '3' + data: Dict[str, any] = json.loads(prompt) + for i in range(3, 32): + try: + i = str(i) + if data[i]["class_type"].startswith("KSampler"): + meta_key = i + break + except: + pass + meta = {} + KSampler_entry = data[meta_key]["inputs"] + # As a workaround to bypass parsing errors in the parser. + # https://github.com/jiw0220/stable-diffusion-image-metadata/blob/00b8d42d4d1a536862bba0b07c332bdebb2a0ce5/src/index.ts#L130 + meta["Steps"] = "Unknown" + meta["Sampler"] = KSampler_entry["sampler_name"] + meta["Model"] = data[KSampler_entry["model"][0]]["inputs"]["ckpt_name"] + meta["Source Identifier"] = "ComfyUI" + def get_text_from_clip(idx: str) : + text = data[idx]["inputs"]["text"] + if isinstance(text, list): # type:CLIPTextEncode (NSP) mode:Wildcards + text = data[text[0]]["inputs"]["text"] + return text.strip() + pos_prompt = get_text_from_clip(KSampler_entry["positive"][0]) + neg_prompt = get_text_from_clip(KSampler_entry["negative"][0]) + pos_prompt_arr = unique_by(parse_prompt(pos_prompt)["pos_prompt"]) + return { + "meta": meta, + "pos_prompt": pos_prompt_arr, + "pos_prompt_raw": pos_prompt, + "neg_prompt_raw" : neg_prompt + } + +def comfyui_exif_data_to_str(data): + res = data["pos_prompt_raw"] + "\nNegative prompt: " + data["neg_prompt_raw"] + "\n" + meta_arr = [] + for k,v in data["meta"].items(): + meta_arr.append(f'{k}: {v}') + return res + ", ".join(meta_arr) + +def read_sd_webui_gen_info_from_image(image: Image, path="") -> str: + """ + Reads metadata from an image file. + + Args: + image (PIL.Image.Image): The image object to read metadata from. + path (str): Optional. The path to the image file. Used to look for a .txt file with additional metadata. + + Returns: + str: The metadata as a string. + """ + items = image.info or {} + geninfo = items.pop("parameters", None) + if "exif" in items: + exif = piexif.load(items["exif"]) + exif_comment = (exif or {}).get("Exif", {}).get(piexif.ExifIFD.UserComment, b"") + + try: + exif_comment = piexif.helper.UserComment.load(exif_comment) + except ValueError: + exif_comment = exif_comment.decode("utf8", errors="ignore") + + if exif_comment: + items["exif comment"] = exif_comment + geninfo = exif_comment + + if not geninfo and path: + try: + txt_path = get_img_geninfo_txt_path(path) + if txt_path: + with open(txt_path) as f: + geninfo = f.read() + except Exception as e: + pass + + return geninfo + + +re_param_code = r'\s*([\w ]+):\s*("(?:\\"[^,]|\\"|\\|[^\"])+"|[^,]*)(?:,|$)' +re_param = re.compile(re_param_code) +re_imagesize = re.compile(r"^(\d+)x(\d+)$") +re_lora_prompt = re.compile("", re.IGNORECASE) +re_lora_extract = re.compile(r"([\w_\s.]+)(?:\d+)?") +re_lyco_prompt = re.compile("", re.IGNORECASE) +re_parens = re.compile(r"[\\/\[\](){}]+") + + +def lora_extract(lora: str): + """ + 提取yoshino yoshino(2a79aa5adc4a) + """ + res = re_lora_extract.match(lora) + return res.group(1) if res else lora + + +def parse_prompt(x: str): + x = re.sub(r'\sBREAK\s', ' , BREAK , ', x) + x = re.sub( + re_parens, "", x.replace(",", ",").replace("-", " ").replace("_", " ") + ) + tag_list = [x.strip() for x in x.split(",")] + res = [] + lora_list = [] + lyco_list = [] + for tag in tag_list: + if len(tag) == 0: + continue + idx_colon = tag.find(":") + if idx_colon != -1: + if re.search(re_lora_prompt, tag): + lora_res = re.search(re_lora_prompt, tag) + lora_list.append( + {"name": lora_res.group(1), "value": float(lora_res.group(2))} + ) + elif re.search(re_lyco_prompt, tag): + lyco_res = re.search(re_lyco_prompt, tag) + lyco_list.append( + {"name": lyco_res.group(1), "value": float(lyco_res.group(2))} + ) + else: + tag = tag[0:idx_colon] + if len(tag): + res.append(tag.lower()) + else: + res.append(tag.lower()) + return {"pos_prompt": res, "lora": lora_list, "lyco": lyco_list} + + +def parse_generation_parameters(x: str): + res = {} + prompt = "" + negative_prompt = "" + done_with_prompt = False + if not x: + return {"meta": {}, "pos_prompt": [], "lora": [], "lyco": []} + + *lines, lastline = x.strip().split("\n") + if len(re_param.findall(lastline)) < 3: + lines.append(lastline) + lastline = "" + if len(lines) == 1 and lines[0].startswith("Postprocess"): # 把上面改成<2应该也可以,当时不敢动 + lastline = lines[ + 0 + ] # 把Postprocess upscale by: 4, Postprocess upscaler: R-ESRGAN 4x+ Anime6B 推到res解析 + lines = [] + for i, line in enumerate(lines): + line = line.strip() + if line.startswith("Negative prompt:"): + done_with_prompt = True + line = line[16:].strip() + + if done_with_prompt: + negative_prompt += ("" if negative_prompt == "" else "\n") + line + else: + prompt += ("" if prompt == "" else "\n") + line + + for k, v in re_param.findall(lastline): + try: + if len(v) == 0: + res[k] = v + continue + if v[0] == '"' and v[-1] == '"': + v = unquote(v) + + m = re_imagesize.match(v) + if m is not None: + res[f"{k}-1"] = m.group(1) + res[f"{k}-2"] = m.group(2) + else: + res[k] = v + except Exception: + print(f"Error parsing \"{k}: {v}\"") + + prompt_parse_res = parse_prompt(prompt) + lora = prompt_parse_res["lora"] + for k in res: + k_s = str(k) + if k_s.startswith("AddNet Module") and str(res[k]).lower() == "lora": + model = res[k_s.replace("Module", "Model")] + value = res.get(k_s.replace("Module", "Weight A"), "1") + lora.append({"name": lora_extract(model), "value": float(value)}) + return { + "meta": res, + "pos_prompt": unique_by(prompt_parse_res["pos_prompt"]), + "lora": unique_by(lora, lambda x: x["name"].lower()), + "lyco": unique_by(prompt_parse_res["lyco"], lambda x: x["name"].lower()), + } + + +tags_translate: Dict[str, str] = {} +try: + import codecs + + with codecs.open(os.path.join(cwd, "tags-translate.csv"), "r", "utf-8") as tag: + tags_translate_str = tag.read() + for line in tags_translate_str.splitlines(): + en, mapping = line.split(",") + tags_translate[en.strip()] = mapping.strip() +except Exception as e: + pass + + +def open_folder(folder_path, file_path=None): + folder = os.path.realpath(folder_path) + if file_path: + file = os.path.join(folder, file_path) + if os.name == "nt": + subprocess.run(["explorer", "/select,", file]) + elif sys.platform == "darwin": + subprocess.run(["open", "-R", file]) + elif os.name == "posix": + subprocess.run(["xdg-open", file]) + else: + if os.name == "nt": + subprocess.run(["explorer", folder]) + elif sys.platform == "darwin": + subprocess.run(["open", folder]) + elif os.name == "posix": + subprocess.run(["xdg-open", folder]) + + +def open_file_with_default_app(file_path): + system = platform.system() + if system == 'Darwin': # macOS + subprocess.call(['open', file_path]) + elif system == 'Windows': # Windows + subprocess.call(file_path, shell=True) + elif system == 'Linux': # Linux + subprocess.call(['xdg-open', file_path]) + else: + raise OSError(f'Unsupported operating system: {system}') + +def omit(d, keys): + return {k: v for k, v in d.items() if k not in keys} + + +def get_current_commit_hash(): + try: + result = subprocess.run(['git', 'rev-parse', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, cwd=cwd) + if result.returncode == 0: + return result.stdout.strip() + else: + return None + except Exception: + return None + +def get_current_tag(): + try: + result = subprocess.run(['git', 'describe', '--tags', '--abbrev=0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, cwd=cwd) + if result.returncode == 0: + return result.stdout.strip() + else: + return None + except Exception: + return None + + +def replace_punctuation(input_string): + return input_string.replace(',', ' ').replace('\n', ' ') + + +def case_insensitive_get(d, key, default=None): + for k, v in d.items(): + if k.lower() == key.lower(): + return v + return default + +def build_sd_webui_style_img_gen_info(prompt, negative_prompt = 'None', meta = {}): + res = f"{prompt}\nNegative prompt: {negative_prompt}\n" + for k, v in meta.items(): + res += f"{k}: {v}, " + return res + +def map_dict_keys(value_dict, map_dict=None): + if map_dict is None: + return value_dict + else: + return {map_dict.get(key, key): value for key, value in value_dict.items()} + + +def get_file_info_by_path(fullpath: str, is_under_scanned_path = True): + stat = os.stat(fullpath) + date = get_formatted_date(stat.st_mtime) + name = os.path.basename(fullpath) + created_time = get_created_date_by_stat(stat) + if os.path.isfile(fullpath): + bytes = stat.st_size + size = human_readable_size(bytes) + return { + "type": "file", + "date": date, + "size": size, + "name": name, + "bytes": bytes, + "created_time": created_time, + "fullpath": fullpath, + "is_under_scanned_path": is_under_scanned_path, + } + + elif os.path.isdir(fullpath): + return { + "type": "dir", + "date": date, + "created_time": created_time, + "size": "-", + "name": name, + "is_under_scanned_path": is_under_scanned_path, + "fullpath": fullpath, + } + return {} + + +def get_frame_at_second(video_path, second): + import av + with av.open(video_path) as container: + time_base = container.streams.video[0].time_base + frame_container_pts = round( second / time_base) + + container.seek(frame_container_pts, backward=True, stream=container.streams.video[0]) + frame = next(container.decode(video=0)) + return frame \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib/video_cover_gen.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib/video_cover_gen.py new file mode 100644 index 0000000000000000000000000000000000000000..bc2fd8e8cd72cf0da18030d4497caee5ef8d5597 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib/video_cover_gen.py @@ -0,0 +1,59 @@ +import hashlib +import os +from typing import List +from scripts.iib.tool import get_formatted_date, get_cache_dir, is_video_file +from concurrent.futures import ThreadPoolExecutor +import time + + +def generate_video_covers(dirs,verbose=False): + start_time = time.time() + import imageio.v3 as iio + + cache_base_dir = get_cache_dir() + + def process_video(item): + if item.is_dir(): + verbose and print(f"Processing directory: {item.path}") + for sub_item in os.scandir(item.path): + process_video(sub_item) + return + if not os.path.exists(item.path) or not is_video_file(item.path): + return + + try: + path = os.path.normpath(item.path) + stat = item.stat() + t = get_formatted_date(stat.st_mtime) + hash_dir = hashlib.md5((path + t).encode("utf-8")).hexdigest() + cache_dir = os.path.join(cache_base_dir, "iib_cache", "video_cover", hash_dir) + cache_path = os.path.join(cache_dir, "cover.webp") + + # 如果缓存文件存在,则直接返回该文件 + if os.path.exists(cache_path): + print(f"Video cover already exists: {path}") + return + + frame = iio.imread( + path, + index=16, + plugin="pyav", + ) + + os.makedirs(cache_dir, exist_ok=True) + iio.imwrite(cache_path, frame, extension=".webp") + verbose and print(f"Video cover generated: {path}") + except Exception as e: + print(f"Error generating video cover: {path}") + print(e) + + with ThreadPoolExecutor() as executor: + for dir_path in dirs: + folder_listing: List[os.DirEntry] = os.scandir(dir_path) + for item in folder_listing: + executor.submit(process_video, item) + + print("Video covers generated successfully.") + end_time = time.time() + execution_time = end_time - start_time + print(f"Execution time: {execution_time} seconds") \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/scripts/iib_setup.py b/extensions/sd-webui-infinite-image-browsing/scripts/iib_setup.py new file mode 100644 index 0000000000000000000000000000000000000000..930aaef034f7540be26d4c7e66fd30bbffa9c77e --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/scripts/iib_setup.py @@ -0,0 +1,86 @@ +from scripts.iib.api import infinite_image_browsing_api, send_img_path +from modules import script_callbacks, generation_parameters_copypaste as send +from scripts.iib.tool import locale +from scripts.iib.tool import read_sd_webui_gen_info_from_image +from PIL import Image +from scripts.iib.logger import logger + +from fastapi import FastAPI +import gradio as gr +from modules.shared import cmd_opts + +""" +api函数声明和启动分离方便另外一边被外部调用 +""" + + +def on_ui_tabs(): + + with gr.Blocks(analytics_enabled=False) as view: + with gr.Row(): + with gr.Column(): + gr.HTML("", elem_id="iib_top") + gr.HTML("error", elem_id="infinite_image_browsing_container_wrapper") + # 以下是使用2个组件模拟粘贴过程 + img = gr.Image( + type="pil", + elem_id="iib_hidden_img", + ) + + def on_img_change(): + send_img_path["value"] = "" # 真正收到图片改变才允许放行 + + img.change(on_img_change) + + img_update_trigger = gr.Button( + "button", elem_id="iib_hidden_img_update_trigger" + ) + + # 修改文本和图像,等待修改完成后前端触发粘贴按钮 + # 有时在触发后收不到回调,可能是在解析params。txt时除了问题删除掉就行了 + def img_update_func(): + try: + path = send_img_path.get("value") + # logger.info("img_update_func %s", path) + img = Image.open(path) + info = read_sd_webui_gen_info_from_image(img, path) + return img, info + except Exception as e: + logger.error("img_update_func err %s",e) + + img_file_info = gr.Textbox(elem_id="iib_hidden_img_file_info") + img_update_trigger.click(img_update_func, outputs=[img, img_file_info]) + for tab in ["txt2img", "img2img", "inpaint", "extras"]: + btn = gr.Button(f"Send to {tab}", elem_id=f"iib_hidden_tab_{tab}") + # 注册粘贴 + send.register_paste_params_button( + send.ParamBinding( + paste_button=btn, + tabname=tab, + source_image_component=img, + source_text_component=img_file_info, + ) + ) + + return ( + ( + view, + "无边图像浏览" if locale == "zh" else "Infinite image browsing", + "infinite-image-browsing", + ), + ) + +def on_app_started(_: gr.Blocks, app: FastAPI) -> None: + # 第一个参数是SD-WebUI传进来的gr.Blocks,但是不需要使用 + DEFAULT_BASE = "/infinite_image_browsing" + fe_public_path = None + subpath = vars(cmd_opts).get("subpath") + + if subpath: + base = "/" + subpath + "/" + DEFAULT_BASE + fe_public_path = base.replace('//', '/') + infinite_image_browsing_api(app, fe_public_path = fe_public_path) + + +script_callbacks.on_ui_tabs(on_ui_tabs) +script_callbacks.on_app_started(on_app_started) diff --git a/extensions/sd-webui-infinite-image-browsing/style.css b/extensions/sd-webui-infinite-image-browsing/style.css new file mode 100644 index 0000000000000000000000000000000000000000..3a937d686bd254d8c2ae8733515db7dfe1f8b008 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/style.css @@ -0,0 +1,3 @@ +[id^="iib_hidden_"] { + display: none !important; +} \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/vue/.eslintrc.cjs b/extensions/sd-webui-infinite-image-browsing/vue/.eslintrc.cjs new file mode 100644 index 0000000000000000000000000000000000000000..89d44a57eeb9a34541cea9f4f469657222c64ed1 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/.eslintrc.cjs @@ -0,0 +1,31 @@ +/* eslint-disable no-undef */ + +// require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + ignorePatterns: [ + 'dist/', + // 其他忽略规则 + ], + root: true, + extends: [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-typescript', + ], + parserOptions: { + ecmaVersion: 'latest' + }, + rules: { + 'vue/multi-word-component-names': ['error', { ignores: ['index', 'index.vue'] }], + 'no-console': 'off', + 'quote-props': ['error', 'as-needed'], + quotes: ['error', 'single'], + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-explicit-any': 'off', + indent: 'off', + '@typescript-eslint/indent': ['error', 2] + } +} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/.gitignore b/extensions/sd-webui-infinite-image-browsing/vue/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..234f2f1beb6f684af9f47861d3a04652e31f23e4 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/.gitignore @@ -0,0 +1,22 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/extensions/sd-webui-infinite-image-browsing/vue/.prettierrc.json b/extensions/sd-webui-infinite-image-browsing/vue/.prettierrc.json new file mode 100644 index 0000000000000000000000000000000000000000..66e23359c3dabfe3929b4e2fa049c41037afb15f --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "tabWidth": 2, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none" +} \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/vue/.vscode/extensions.json b/extensions/sd-webui-infinite-image-browsing/vue/.vscode/extensions.json new file mode 100644 index 0000000000000000000000000000000000000000..c0a6e5a48110e472b09d68afa2a030af6ab3208b --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] +} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/README.md b/extensions/sd-webui-infinite-image-browsing/vue/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4b8e0d35c8d9f86718ce422ed43a8729586ead2b --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/README.md @@ -0,0 +1,47 @@ +# vue + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. + +If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: + +1. Disable the built-in TypeScript Extension + 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette + 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` +2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. + +## Customize configuration + +See [Vite Configuration Reference](https://vitejs.dev/config/). + +## Project Setup + +```sh +yarn +``` + +### Compile and Hot-Reload for Development + +```sh +yarn dev +``` + + +### Compile and Minify for Production, Deliver to Production Mode Resources + +```sh +yarn build +``` + +### Lint with [ESLint](https://eslint.org/) + +```sh +yarn lint +``` diff --git a/extensions/sd-webui-infinite-image-browsing/vue/build.ts b/extensions/sd-webui-infinite-image-browsing/vue/build.ts new file mode 100644 index 0000000000000000000000000000000000000000..7854c3acff230297b3eba6d1eb86957badd4b883 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/build.ts @@ -0,0 +1,27 @@ +import { execSync } from 'child_process' +import { readFile, rm, writeFile } from 'fs/promises' +import { exit } from 'process' + +const main = async () => { + try { + console.log(execSync('vue-tsc && vite build').toString('utf8')) + } catch (error: any) { + if (error.stdout && error.stderr) { + console.log(error.stdout.toString('utf8')) + console.error(error.stderr.toString('utf8')) + exit(2) + } else { + throw error + } + } + try { + await rm('../javascript/index.js') + // eslint-disable-next-line no-empty + } catch (error) { + + } + const html = (await readFile('dist/index.html')).toString() + const js = (await readFile('index.tpl.js')).toString().replace('__built_html__', html) + await writeFile('../javascript/index.js', js) +} +main() diff --git a/extensions/sd-webui-infinite-image-browsing/vue/components.d.ts b/extensions/sd-webui-infinite-image-browsing/vue/components.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..dbf049d408e5a0d58f5d4846315dd5dbf81e441d --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/components.d.ts @@ -0,0 +1,57 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +import '@vue/runtime-core' + +export {} + +declare module '@vue/runtime-core' { + export interface GlobalComponents { + AAlert: typeof import('ant-design-vue/es')['Alert'] + ABadge: typeof import('ant-design-vue/es')['Badge'] + ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb'] + ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem'] + AButton: typeof import('ant-design-vue/es')['Button'] + ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] + ACol: typeof import('ant-design-vue/es')['Col'] + ACollapse: typeof import('ant-design-vue/es')['Collapse'] + ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel'] + ADrawer: typeof import('ant-design-vue/es')['Drawer'] + ADropdown: typeof import('ant-design-vue/es')['Dropdown'] + AForm: typeof import('ant-design-vue/es')['Form'] + AFormItem: typeof import('ant-design-vue/es')['FormItem'] + AImage: typeof import('ant-design-vue/es')['Image'] + AInput: typeof import('ant-design-vue/es')['Input'] + AInputGroup: typeof import('ant-design-vue/es')['InputGroup'] + AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] + AMenu: typeof import('ant-design-vue/es')['Menu'] + AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] + AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] + AModal: typeof import('ant-design-vue/es')['Modal'] + ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] + ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] + ARow: typeof import('ant-design-vue/es')['Row'] + ASelect: typeof import('ant-design-vue/es')['Select'] + ASkeleton: typeof import('ant-design-vue/es')['Skeleton'] + ASlider: typeof import('ant-design-vue/es')['Slider'] + ASpin: typeof import('ant-design-vue/es')['Spin'] + ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] + ASwitch: typeof import('ant-design-vue/es')['Switch'] + ATabPane: typeof import('ant-design-vue/es')['TabPane'] + ATabs: typeof import('ant-design-vue/es')['Tabs'] + ATag: typeof import('ant-design-vue/es')['Tag'] + ATextarea: typeof import('ant-design-vue/es')['Textarea'] + ATooltip: typeof import('ant-design-vue/es')['Tooltip'] + BaseFileListInfo: typeof import('./src/components/BaseFileListInfo.vue')['default'] + ChangeIndicator: typeof import('./src/components/ChangeIndicator.vue')['default'] + ContextMenu: typeof import('./src/components/ContextMenu.vue')['default'] + FileItem: typeof import('./src/components/FileItem.vue')['default'] + HistoryRecord: typeof import('./src/components/HistoryRecord.vue')['default'] + MultiSelectKeep: typeof import('./src/components/MultiSelectKeep.vue')['default'] + NumInput: typeof import('./src/components/numInput.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + } +} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/FileItem-17626ff1.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/FileItem-17626ff1.css new file mode 100644 index 0000000000000000000000000000000000000000..2b69cb065358e60016b83d3ac8ec302b498c0872 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/FileItem-17626ff1.css @@ -0,0 +1 @@ +.vue-recycle-scroller{position:relative}.vue-recycle-scroller.direction-vertical:not(.page-mode){overflow-y:auto}.vue-recycle-scroller.direction-horizontal:not(.page-mode){overflow-x:auto}.vue-recycle-scroller.direction-horizontal{display:flex}.vue-recycle-scroller__slot{flex:auto 0 0}.vue-recycle-scroller__item-wrapper{flex:1;box-sizing:border-box;overflow:hidden;position:relative}.vue-recycle-scroller.ready .vue-recycle-scroller__item-view{position:absolute;top:0;left:0;will-change:transform}.vue-recycle-scroller.direction-vertical .vue-recycle-scroller__item-wrapper{width:100%}.vue-recycle-scroller.direction-horizontal .vue-recycle-scroller__item-wrapper{height:100%}.vue-recycle-scroller.ready.direction-vertical .vue-recycle-scroller__item-view{width:100%}.vue-recycle-scroller.ready.direction-horizontal .vue-recycle-scroller__item-view{height:100%}.resize-observer[data-v-b329ee4c]{position:absolute;top:0;left:0;z-index:-1;width:100%;height:100%;border:none;background-color:transparent;pointer-events:none;display:block;overflow:hidden;opacity:0}.resize-observer[data-v-b329ee4c] object{display:block;position:absolute;top:0;left:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1}.ant-tag{box-sizing:border-box;margin:0 8px 0 0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block;height:auto;padding:0 7px;font-size:12px;line-height:20px;white-space:nowrap;background:#fafafa;border:1px solid #d9d9d9;border-radius:2px;opacity:1;transition:all .3s}.ant-tag,.ant-tag a,.ant-tag a:hover{color:#000000d9}.ant-tag>a:first-child:last-child{display:inline-block;margin:0 -8px;padding:0 8px}.ant-tag-close-icon{margin-left:3px;color:#00000073;font-size:10px;cursor:pointer;transition:all .3s}.ant-tag-close-icon:hover{color:#000000d9}.ant-tag-has-color{border-color:transparent}.ant-tag-has-color,.ant-tag-has-color a,.ant-tag-has-color a:hover,.ant-tag-has-color .anticon-close,.ant-tag-has-color .anticon-close:hover{color:#fff}.ant-tag-checkable{background-color:transparent;border-color:transparent;cursor:pointer}.ant-tag-checkable:not(.ant-tag-checkable-checked):hover{color:#d03f0a}.ant-tag-checkable:active,.ant-tag-checkable-checked{color:#fff}.ant-tag-checkable-checked{background-color:#d03f0a}.ant-tag-checkable:active{background-color:#ab2800}.ant-tag-hidden{display:none}.ant-tag-pink{color:#c41d7f;background:#fff0f6;border-color:#ffadd2}.ant-tag-pink-inverse{color:#fff;background:#eb2f96;border-color:#eb2f96}.ant-tag-magenta{color:#c41d7f;background:#fff0f6;border-color:#ffadd2}.ant-tag-magenta-inverse{color:#fff;background:#eb2f96;border-color:#eb2f96}.ant-tag-red{color:#cf1322;background:#fff1f0;border-color:#ffa39e}.ant-tag-red-inverse{color:#fff;background:#f5222d;border-color:#f5222d}.ant-tag-volcano{color:#d4380d;background:#fff2e8;border-color:#ffbb96}.ant-tag-volcano-inverse{color:#fff;background:#fa541c;border-color:#fa541c}.ant-tag-orange{color:#d46b08;background:#fff7e6;border-color:#ffd591}.ant-tag-orange-inverse{color:#fff;background:#fa8c16;border-color:#fa8c16}.ant-tag-yellow{color:#d4b106;background:#feffe6;border-color:#fffb8f}.ant-tag-yellow-inverse{color:#fff;background:#fadb14;border-color:#fadb14}.ant-tag-gold{color:#d48806;background:#fffbe6;border-color:#ffe58f}.ant-tag-gold-inverse{color:#fff;background:#faad14;border-color:#faad14}.ant-tag-cyan{color:#08979c;background:#e6fffb;border-color:#87e8de}.ant-tag-cyan-inverse{color:#fff;background:#13c2c2;border-color:#13c2c2}.ant-tag-lime{color:#7cb305;background:#fcffe6;border-color:#eaff8f}.ant-tag-lime-inverse{color:#fff;background:#a0d911;border-color:#a0d911}.ant-tag-green{color:#389e0d;background:#f6ffed;border-color:#b7eb8f}.ant-tag-green-inverse{color:#fff;background:#52c41a;border-color:#52c41a}.ant-tag-blue{color:#096dd9;background:#e6f7ff;border-color:#91d5ff}.ant-tag-blue-inverse{color:#fff;background:#1890ff;border-color:#1890ff}.ant-tag-geekblue{color:#1d39c4;background:#f0f5ff;border-color:#adc6ff}.ant-tag-geekblue-inverse{color:#fff;background:#2f54eb;border-color:#2f54eb}.ant-tag-purple{color:#531dab;background:#f9f0ff;border-color:#d3adf7}.ant-tag-purple-inverse{color:#fff;background:#722ed1;border-color:#722ed1}.ant-tag-success{color:#52c41a;background:#f6ffed;border-color:#b7eb8f}.ant-tag-processing{color:#d03f0a;background:#fff1e6;border-color:#f7ae83}.ant-tag-error{color:#ff4d4f;background:#fff2f0;border-color:#ffccc7}.ant-tag-warning{color:#faad14;background:#fffbe6;border-color:#ffe58f}.ant-tag>.anticon+span,.ant-tag>span+.anticon{margin-left:7px}.ant-tag.ant-tag-rtl{margin-right:0;margin-left:8px;direction:rtl;text-align:right}.ant-tag-rtl .ant-tag-close-icon{margin-right:3px;margin-left:0}.ant-tag-rtl.ant-tag>.anticon+span,.ant-tag-rtl.ant-tag>span+.anticon{margin-right:7px;margin-left:0}.changeIndicators[data-v-78cd67a3]{position:absolute;display:flex;flex-direction:column;height:100%;align-items:center;justify-content:center;opacity:.6}.changeIndicatorsRight[data-v-78cd67a3]{position:absolute;right:0}.changeIndicator[data-v-78cd67a3]{margin-left:-4px;width:16px;height:16px;border-radius:2px;border:1px solid rgba(255,255,255,.2);background-color:gray;line-height:16px;margin-bottom:2px;text-align:center;font-size:6pt;font-weight:600;color:#000;z-index:9999;pointer-events:auto;box-shadow:0 0 4px #00000080}.changeIndicatorsRight .changeIndicator[data-v-78cd67a3]{margin-right:-4px;border-top-right-radius:8px;border-bottom-right-radius:8px;text-align:left;padding-left:2px}.changeIndicatorsLeft .changeIndicator[data-v-78cd67a3]{border-top-left-radius:8px;border-bottom-left-radius:8px;text-align:right;padding-right:2px}.changeIndicatorWrapper[data-v-78cd67a3]{top:0;position:absolute;user-select:none;width:100%;height:100%;z-index:999999;pointer-events:none}.hoverOverlay[data-v-78cd67a3]{display:none;background-color:#000c;color:#fff;border:1px solid gray;padding:10px 20px;border-radius:5px;z-index:100;opacity:1;font-size:8pt;line-height:1.2;overflow:hidden}.hoverOverlay ul[data-v-78cd67a3]{list-style:none;padding:0}.hoverOverlay ul li[data-v-78cd67a3]{display:inline-block;padding-left:4px;padding-right:4px;border:1px solid gray;border-radius:2px;margin:1px;font-weight:200}.changeIndicators[data-v-78cd67a3]:hover{opacity:1}.changeIndicators:hover+div.hoverOverlay[data-v-78cd67a3]{display:block;position:absolute;top:0;left:0;width:100%;height:100%}table tr td:first-child span[data-v-78cd67a3]{padding:1px 3px;display:inline-block;width:100%}table tr td[data-v-78cd67a3]:first-child{padding-right:10px;vertical-align:top}.otherChangeIndicator[data-v-78cd67a3]{background-color:#8b5b8e;color:#efefef}.stepsChangeIndicator[data-v-78cd67a3]{background-color:#577ab8;color:#efefef}.seedChangeIndicator[data-v-78cd67a3]{background-color:#649da3;color:#121}.negpromptChangeIndicator[data-v-78cd67a3]{background-color:#d8a390;color:#2f2f2f}.modelChangeIndicator[data-v-78cd67a3]{background-color:#d68679;color:#efefef}.promptChangeIndicator[data-v-78cd67a3]{background-color:#8fba99;color:#121}.cfgChangeIndicator[data-v-78cd67a3]{background-color:#d4c98f;color:#121}.sizeChangeIndicator[data-v-78cd67a3]{background-color:#678a6c;color:#efefef}.center[data-v-9a0c2829]{display:flex;justify-content:center;align-items:center}.item-content[data-v-9a0c2829]{position:relative}.item-content.video[data-v-9a0c2829]{background-color:var(--zp-border);border-radius:8px;overflow:hidden;width:var(--6b392466);height:var(--6b392466);background-size:cover;background-position:center;cursor:pointer}.item-content .play-icon[data-v-9a0c2829]{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);border-radius:100%;display:flex}.item-content .tags-container[data-v-9a0c2829]{position:absolute;right:8px;bottom:8px;display:flex;width:calc(100% - 16px);flex-wrap:wrap-reverse;flex-direction:row-reverse}.item-content .tags-container>*[data-v-9a0c2829]{margin:0 0 4px 4px;font-size:14px;line-height:1.6}.close-icon[data-v-9a0c2829]{position:absolute;top:0;right:0;transform:translate(50%,-50%) scale(1.5);cursor:pointer;z-index:100;border-radius:100%;overflow:hidden;line-height:1;background-color:var(--zp-primary-background)}.file[data-v-9a0c2829]{padding:8px 16px;margin:8px;display:flex;align-items:center;background:var(--zp-primary-background);border-radius:8px;box-shadow:0 0 4px var(--zp-secondary-variant-background);position:relative}.file:hover .more[data-v-9a0c2829]{opacity:1}.file .more[data-v-9a0c2829]{opacity:0;transition:all .3s ease;position:absolute;top:4px;right:4px;z-index:100;display:flex;align-items:center;justify-content:center;flex-direction:column;line-height:1em}.file .more .float-btn-wrap[data-v-9a0c2829]{font-size:1.5em;cursor:pointer;font-size:500;padding:4px;border-radius:100vh;color:#fff;background:var(--zp-icon-bg);margin-bottom:4px}.file .more .float-btn-wrap.like-selected[data-v-9a0c2829]{color:#df0505}.file.grid[data-v-9a0c2829]{padding:0;display:inline-block;box-sizing:content-box;box-shadow:unset;background-color:var(--zp-secondary-background)}.file.grid[data-v-9a0c2829] .icon{font-size:8em}.file.grid[data-v-9a0c2829] .profile{padding:0 4px}.file.grid[data-v-9a0c2829] .profile .name{font-weight:500;padding:0}.file.grid[data-v-9a0c2829] .profile .basic-info{display:flex;justify-content:space-between;flex-direction:row;margin:0;font-size:.7em}.file.grid[data-v-9a0c2829] .profile .basic-info *{white-space:nowrap;overflow:hidden}.file.grid[data-v-9a0c2829] .ant-image,.file.grid[data-v-9a0c2829] .preview-icon-wrap{border:1px solid var(--zp-secondary);background-color:var(--zp-secondary-variant-background);border-radius:8px;overflow:hidden}.file.grid[data-v-9a0c2829] img:not(.dir-cover-item),.file.grid[data-v-9a0c2829] .dir-cover-container,.file.grid[data-v-9a0c2829] .preview-icon-wrap>[role=img]{height:var(--6b392466);width:var(--6b392466);object-fit:contain}.file.clickable[data-v-9a0c2829]{cursor:pointer}.file.selected[data-v-9a0c2829]{outline:#0084ff solid 2px}.file .name[data-v-9a0c2829]{flex:1;padding:8px;word-break:break-all}.file .basic-info[data-v-9a0c2829]{overflow:hidden;display:flex;flex-direction:column;align-items:flex-end}.file .dir-cover-container[data-v-9a0c2829]{top:0;display:flex;flex-wrap:wrap;padding:4px}.file .dir-cover-container>img[data-v-9a0c2829]{width:calc(50% - 8px);height:calc(50% - 8px);margin:4px;object-fit:cover;border-radius:8px;overflow:hidden} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/FileItem-9a94f7dd.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/FileItem-9a94f7dd.js new file mode 100644 index 0000000000000000000000000000000000000000..499d472b3853a8465206d11a115acdd355f8acb2 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/FileItem-9a94f7dd.js @@ -0,0 +1,3 @@ +var vt=Object.defineProperty;var mt=(i,t,e)=>t in i?vt(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var ee=(i,t,e)=>(mt(i,typeof t!="symbol"?t+"":t,e),e);import{d as X,u as Ye,E as V,al as qe,h as K,c as S,ao as yt,cB as bt,r as j,bc as At,X as H,cp as St,P as ze,bM as kt,A as W,bY as It,aY as _t,cC as Ct,cD as wt,cE as ce,L as ge,ak as ke,bf as Et,cc as Tt,cF as Pt,cG as Ot,cA as Dt,cH as Nt,cI as Ge,bA as zt,cJ as ae,cK as $t,aF as Mt,cL as Qt,B as Bt,cM as Ft,n as pe,m as se,aP as Rt,q as Ze,$ as Ie,cf as Xe,aH as Lt,cN as et,cO as jt,N as Ht,v as Vt,aN as tt,aO as nt,ax as it,S as a,a0 as R,cP as xt,cQ as Ut,cR as Jt,cS as Wt,cT as Kt,ar as Yt,T as h,aE as te,Y as A,a1 as I,a6 as J,cU as $e,cV as qt,cW as Gt,a5 as rt,ae as Y,V as _,W as y,a2 as Q,aj as st,cj as Zt,ci as Xt,M as ot,U as u,Z as lt,ce as en,cX as Me,ad as tn,cY as nn,cg as rn,bZ as sn,cZ as on,c_ as de,c$ as ln}from"./index-93a218ca.js";import{f as Qe,g as an,h as un}from"./functionalCallableComp-685da399.js";import{D as G,a as ve}from"./index-f60e0de3.js";/* empty css */var cn=function(){return{prefixCls:String,checked:{type:Boolean,default:void 0},onChange:{type:Function},onClick:{type:Function},"onUpdate:checked":Function}},dn=X({compatConfig:{MODE:3},name:"ACheckableTag",props:cn(),setup:function(t,e){var n=e.slots,r=e.emit,o=Ye("tag",t),p=o.prefixCls,d=function(E){var w=t.checked;r("update:checked",!w),r("change",!w),r("click",E)},v=V(function(){var b;return qe(p.value,(b={},K(b,"".concat(p.value,"-checkable"),!0),K(b,"".concat(p.value,"-checkable-checked"),t.checked),b))});return function(){var b;return S("span",{class:v.value,onClick:d},[(b=n.default)===null||b===void 0?void 0:b.call(n)])}}});const me=dn;var fn=new RegExp("^(".concat(yt.join("|"),")(-inverse)?$")),hn=new RegExp("^(".concat(bt.join("|"),")$")),gn=function(){return{prefixCls:String,color:{type:String},closable:{type:Boolean,default:!1},closeIcon:ze.any,visible:{type:Boolean,default:void 0},onClose:{type:Function},"onUpdate:visible":Function,icon:ze.any}},Z=X({compatConfig:{MODE:3},name:"ATag",props:gn(),slots:["closeIcon","icon"],setup:function(t,e){var n=e.slots,r=e.emit,o=e.attrs,p=Ye("tag",t),d=p.prefixCls,v=p.direction,b=j(!0);At(function(){t.visible!==void 0&&(b.value=t.visible)});var E=function(s){s.stopPropagation(),r("update:visible",!1),r("close",s),!s.defaultPrevented&&t.visible===void 0&&(b.value=!1)},w=V(function(){var l=t.color;return l?fn.test(l)||hn.test(l):!1}),T=V(function(){var l;return qe(d.value,(l={},K(l,"".concat(d.value,"-").concat(t.color),w.value),K(l,"".concat(d.value,"-has-color"),t.color&&!w.value),K(l,"".concat(d.value,"-hidden"),!b.value),K(l,"".concat(d.value,"-rtl"),v.value==="rtl"),l))});return function(){var l,s,c,m=t.icon,N=m===void 0?(l=n.icon)===null||l===void 0?void 0:l.call(n):m,O=t.color,z=t.closeIcon,g=z===void 0?(s=n.closeIcon)===null||s===void 0?void 0:s.call(n):z,f=t.closable,C=f===void 0?!1:f,M=function(){return C?g?S("span",{class:"".concat(d.value,"-close-icon"),onClick:E},[g]):S(kt,{class:"".concat(d.value,"-close-icon"),onClick:E},null):null},F={backgroundColor:O&&!w.value?O:void 0},B=N||null,k=(c=n.default)===null||c===void 0?void 0:c.call(n),L=B?S(H,null,[B,S("span",null,[k])]):k,P="onClick"in o,$=S("span",{class:T.value,style:F},[L,M()]);return P?S(St,null,{default:function(){return[$]}}):$}}});Z.CheckableTag=me;Z.install=function(i){return i.component(Z.name,Z),i.component(me.name,me),i};const pn=Z;G.Button=ve;G.install=function(i){return i.component(G.name,G),i.component(ve.name,ve),i};var vn={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 00.6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0046.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z"}}]},name:"star",theme:"filled"};const mn=vn;function Be(i){for(var t=1;t{const r=Pt();ge(r),ne.has(r)||(ne.set(r,ke(i(r,n??(t==null?void 0:t())))),Et(()=>{ne.delete(r)}));const o=ne.get(r);return ge(o),{state:o,toRefs(){return Tt(o)}}}}}var Bn={icon:{tag:"svg",attrs:{viewBox:"0 0 1024 1024",focusable:"false"},children:[{tag:"path",attrs:{d:"M715.8 493.5L335 165.1c-14.2-12.2-35-1.2-35 18.5v656.8c0 19.7 20.8 30.7 35 18.5l380.8-328.4c10.9-9.4 10.9-27.6 0-37z"}}]},name:"caret-right",theme:"outlined"};const Fn=Bn;function Le(i){for(var t=1;t{const i=j([]);return{selectdFiles:i,addFiles:e=>{i.value=Dt([...i.value,...e])}}});class oe{constructor(t,e=Nt.CREATED_TIME_DESC){ee(this,"root");ee(this,"execQueue",[]);ee(this,"walkerInitPromsie");this.entryPath=t,this.sortMethod=e,this.root={children:[],info:{name:this.entryPath,size:"-",bytes:0,created_time:"",is_under_scanned_path:!0,date:"",type:"dir",fullpath:this.entryPath}},this.walkerInitPromsie=new Promise(n=>{Qe([this.entryPath]).then(async r=>{this.root.info=r[this.entryPath],await this.fetchChildren(this.root),n()})})}reset(){return this.root.children=[],this.fetchChildren(this.root)}get images(){const t=e=>e.children.map(n=>{if(n.info.type==="dir")return t(n);if(ae(n.info.name))return n.info}).filter(n=>n).flat(1);return t(this.root)}get isCompleted(){return this.execQueue.length===0}async fetchChildren(t){const{files:e}=await an(t.info.fullpath);return t.children=Ge(e,this.sortMethod).map(n=>({info:n,children:[]})),this.execQueue.shift(),this.execQueue.unshift(...t.children.filter(n=>n.info.type==="dir").map(n=>({fn:()=>this.fetchChildren(n),...n}))),t}async next(){await this.walkerInitPromsie;const t=Tn(this.execQueue);if(!t)return null;const e=await t.fn();return this.execQueue=this.execQueue.slice(),this.root={...this.root},e}async isExpired(){const t=[this.root.info],e=r=>{for(const o of r.children)o.info.type==="dir"&&(t.push(o.info),e(o))};e(this.root);const n=await Qe(t.map(r=>r.fullpath));for(const r of t)if(!zt(r,n[r.fullpath]))return!0;return!1}async seamlessRefresh(t,e=j(!1)){const n=performance.now(),r=new oe(this.entryPath,this.sortMethod);for(await r.walkerInitPromsie;!r.isCompleted&&r.images.length
'};e.configure=function(s){var c,m;for(c in s)m=s[c],m!==void 0&&s.hasOwnProperty(c)&&(n[c]=m);return this},e.status=null,e.set=function(s){var c=e.isStarted();s=r(s,n.minimum,1),e.status=s===1?null:s;var m=e.render(!c),N=m.querySelector(n.barSelector),O=n.speed,z=n.easing;return m.offsetWidth,d(function(g){n.positionUsing===""&&(n.positionUsing=e.getPositioningCSS()),v(N,p(s,O,z)),s===1?(v(m,{transition:"none",opacity:1}),m.offsetWidth,setTimeout(function(){v(m,{transition:"all "+O+"ms linear",opacity:0}),setTimeout(function(){e.remove(),g()},O)},O)):setTimeout(g,O)}),this},e.isStarted=function(){return typeof e.status=="number"},e.start=function(){e.status||e.set(0);var s=function(){setTimeout(function(){e.status&&(e.trickle(),s())},n.trickleSpeed)};return n.trickle&&s(),this},e.done=function(s){return!s&&!e.status?this:e.inc(.3+.5*Math.random()).set(1)},e.inc=function(s){var c=e.status;return c?c>1?void 0:(typeof s!="number"&&(c>=0&&c<.2?s=.1:c>=.2&&c<.5?s=.04:c>=.5&&c<.8?s=.02:c>=.8&&c<.99?s=.005:s=0),c=r(c+s,0,.994),e.set(c)):e.start()},e.trickle=function(){return e.inc()},function(){var s=0,c=0;e.promise=function(m){return!m||m.state()==="resolved"?this:(c===0&&e.start(),s++,c++,m.always(function(){c--,c===0?(s=0,e.done()):e.set((s-c)/s)}),this)}}(),e.getElement=function(){var s=e.getParent();if(s){var c=Array.prototype.slice.call(s.querySelectorAll(".nprogress")).filter(function(m){return m.parentElement===s});if(c.length>0)return c[0]}return null},e.getParent=function(){if(n.parent instanceof HTMLElement)return n.parent;if(typeof n.parent=="string")return document.querySelector(n.parent)},e.render=function(s){if(e.isRendered())return e.getElement();E(document.documentElement,"nprogress-busy");var c=document.createElement("div");c.id="nprogress",c.className="nprogress",c.innerHTML=n.template;var m=c.querySelector(n.barSelector),N=s?"-100":o(e.status||0),O=e.getParent(),z;return v(m,{transition:"all 0 linear",transform:"translate3d("+N+"%,0,0)"}),n.showSpinner||(z=c.querySelector(n.spinnerSelector),z&&l(z)),O!=document.body&&E(O,"nprogress-custom-parent"),O.appendChild(c),c},e.remove=function(){e.status=null,w(document.documentElement,"nprogress-busy"),w(e.getParent(),"nprogress-custom-parent");var s=e.getElement();s&&l(s)},e.isRendered=function(){return!!e.getElement()},e.getPositioningCSS=function(){var s=document.body.style,c="WebkitTransform"in s?"Webkit":"MozTransform"in s?"Moz":"msTransform"in s?"ms":"OTransform"in s?"O":"";return c+"Perspective"in s?"translate3d":c+"Transform"in s?"translate":"margin"};function r(s,c,m){return sm?m:s}function o(s){return(-1+s)*100}function p(s,c,m){var N;return n.positionUsing==="translate3d"?N={transform:"translate3d("+o(s)+"%,0,0)"}:n.positionUsing==="translate"?N={transform:"translate("+o(s)+"%,0)"}:N={"margin-left":o(s)+"%"},N.transition="all "+c+"ms "+m,N}var d=function(){var s=[];function c(){var m=s.shift();m&&m(c)}return function(m){s.push(m),s.length==1&&c()}}(),v=function(){var s=["Webkit","O","Moz","ms"],c={};function m(g){return g.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(f,C){return C.toUpperCase()})}function N(g){var f=document.body.style;if(g in f)return g;for(var C=s.length,M=g.charAt(0).toUpperCase()+g.slice(1),F;C--;)if(F=s[C]+M,F in f)return F;return g}function O(g){return g=m(g),c[g]||(c[g]=N(g))}function z(g,f,C){f=O(f),g.style[f]=C}return function(g,f){var C=arguments,M,F;if(C.length==2)for(M in f)F=f[M],F!==void 0&&f.hasOwnProperty(M)&&z(g,M,F);else z(g,C[1],C[2])}}();function b(s,c){var m=typeof s=="string"?s:T(s);return m.indexOf(" "+c+" ")>=0}function E(s,c){var m=T(s),N=m+c;b(m,c)||(s.className=N.substring(1))}function w(s,c){var m=T(s),N;b(s,c)&&(N=m.replace(" "+c+" "," "),s.className=N.substring(1,N.length-1))}function T(s){return(" "+(s&&s.className||"")+" ").replace(/\s+/gi," ")}function l(s){s&&s.parentNode&&s.parentNode.removeChild(s)}return e})})(ct);var Xn=ct.exports;const ms=Mt(Xn);function ys({fetchNext:i}={}){const{scroller:t,sortedFiles:e,sortMethod:n,currLocation:r,stackViewEl:o,canLoadNext:p,previewIdx:d,props:v,walker:b,getViewableAreaFiles:E}=le().toRefs(),{state:w}=le(),T=j(!1),l=j(q.defaultGridCellWidth),s=V(()=>l.value+16),c=44,{width:m}=Qt(o),N=V(()=>~~(m.value/s.value)),O=ke(new Map),z=V(()=>{const B=s.value;return{first:B+(l.value<=160?0:c),second:B}}),g=j(!1),f=async()=>{var B;if(!(g.value||v.value.mode!=="walk"||!p.value))try{g.value=!0,await((B=b.value)==null?void 0:B.next())}finally{g.value=!1}},C=async(B=!1)=>{const k=t.value,L=()=>B?d.value:(k==null?void 0:k.$_endIndex)??0,P=()=>{const $=e.value.length,x=50;return $?i?L()>$-x:L()>$-x&&p.value:!0};for(;P();){await Ze(30);const $=await(i??f)();if(typeof $=="boolean"&&!$)return}};w.useEventListen("loadNextDir",Bt(async(B=!1)=>{await C(B),v.value.mode==="walk"&&M()})),w.useEventListen("viewableAreaFilesChange",()=>{const B=E.value(),k=B.filter(P=>P.is_under_scanned_path&&ae(P.name)).map(P=>P.fullpath);ei.fetchImageTags(k);const L=B.filter(P=>P.is_under_scanned_path&&P.type==="dir"&&!O.has(P.fullpath)).map(P=>P.fullpath);L.length&&Ft(L).then(P=>{for(const $ in P)if(Object.prototype.hasOwnProperty.call(P,$)){const x=P[$];O.set($,x)}})}),w.useEventListen("refresh",async()=>{w.eventEmitter.emit("viewableAreaFilesChange")});const M=pe(()=>w.eventEmitter.emit("viewableAreaFilesChange"),300);se(r,M);const F=pe(async()=>{await C(),M()},150);return{gridItems:N,sortedFiles:e,sortMethodConv:Rt,moreActionsDropdownShow:T,gridSize:s,sortMethod:n,onScroll:F,loadNextDir:f,loadNextDirLoading:g,canLoadNext:p,itemSize:z,cellWidth:l,dirCoverCache:O}}const bs=new Map,q=Ie(),As=Zn(),ei=Xe(),Ss=Lt(),ks=new BroadcastChannel("iib-image-transfer-bus"),{eventEmitter:Is,useEventListen:_s}=et(),{useHookShareState:le}=Qn((i,{images:t})=>{const e=j({tabIdx:-1,paneIdx:-1}),n=V(()=>Ht(r.value)),r=j([]),o=V(()=>{var f;return r.value.map(C=>C.curr).slice((f=q.conf)!=null&&f.is_win&&e.value.mode!=="scanned-fixed"?1:0)}),p=V(()=>Vt(...o.value)),d=V(()=>{var f,C;return e.value.mode==="scanned-fixed"?((C=(f=r.value)==null?void 0:f[0])==null?void 0:C.curr)??"":e.value.mode==="walk"?e.value.path??"":r.value.length===1?"/":p.value}),v=j(q.defaultSortingMethod),b=j(e.value.mode=="walk"?new oe(e.value.path,v.value):void 0);se([()=>e.value.mode,()=>e.value.path,v],async([f,C,M])=>{var F;f==="walk"?(b.value=new oe(C,M),r.value=[{files:[],curr:C}],await Ze(),await((F=b.value)==null?void 0:F.reset()),z.eventEmitter.emit("loadNextDir")):b.value=void 0});const E=ke(new Set);se(n,()=>E.clear());const w=V(()=>{var F;if(t.value)return t.value;if(b.value)return b.value.images.filter(B=>!E.has(B.fullpath));if(!n.value)return[];const f=((F=n.value)==null?void 0:F.files)??[],C=v.value;return Ge((B=>q.onlyFoldersAndImages?B.filter(k=>k.type==="dir"||ae(k.name)):B)(f),C).filter(B=>!E.has(B.fullpath))}),T=j([]),l=j(-1),s=V(()=>b.value?!b.value.isCompleted:!1),c=j(!1),m=j(!1),N=j(),O=()=>{var f,C,M;return(M=(C=(f=q.tabList)==null?void 0:f[e.value.tabIdx])==null?void 0:C.panes)==null?void 0:M[e.value.paneIdx]},z=et();z.useEventListen("selectAll",()=>{console.log(`select all 0 -> ${w.value.length}`),T.value=$n(0,w.value.length)});const g=()=>{const f=N.value;if(f){const C=Math.max(f.$_startIndex-10,0);return w.value.slice(C,f.$_endIndex+10)}return[]};return{previewing:m,spinning:c,canLoadNext:s,multiSelectedIdxs:T,previewIdx:l,basePath:o,currLocation:d,currPage:n,stack:r,sortMethod:v,sortedFiles:w,scroller:N,stackViewEl:j(),props:e,getPane:O,walker:b,deletedFiles:E,getViewableAreaFiles:g,...z}},()=>({images:j()}));function Cs(){const{eventEmitter:i,multiSelectedIdxs:t,sortedFiles:e}=le().toRefs();return{onSelectAll:()=>i.value.emit("selectAll"),onReverseSelect:()=>{t.value=e.value.map((p,d)=>d).filter(p=>!t.value.includes(p))},onClearAllSelected:()=>{t.value=[]}}}const ws=()=>{const{stackViewEl:i}=le().toRefs(),t=j(-1);return jt(i,e=>{var r;let n=e.target;for(;n.parentElement;)if(n=n.parentElement,n.tagName.toLowerCase()==="li"&&n.classList.contains("file-item-trigger")){const o=(r=n.dataset)==null?void 0:r.idx;o&&Number.isSafeInteger(+o)&&(t.value=+o);return}}),{showMenuIdx:t}};function ti(){var i=window.navigator.userAgent,t=i.indexOf("MSIE ");if(t>0)return parseInt(i.substring(t+5,i.indexOf(".",t)),10);var e=i.indexOf("Trident/");if(e>0){var n=i.indexOf("rv:");return parseInt(i.substring(n+3,i.indexOf(".",n)),10)}var r=i.indexOf("Edge/");return r>0?parseInt(i.substring(r+5,i.indexOf(".",r)),10):-1}let ie;function ye(){ye.init||(ye.init=!0,ie=ti()!==-1)}var ue={name:"ResizeObserver",props:{emitOnMount:{type:Boolean,default:!1},ignoreWidth:{type:Boolean,default:!1},ignoreHeight:{type:Boolean,default:!1}},emits:["notify"],mounted(){ye(),it(()=>{this._w=this.$el.offsetWidth,this._h=this.$el.offsetHeight,this.emitOnMount&&this.emitSize()});const i=document.createElement("object");this._resizeObject=i,i.setAttribute("aria-hidden","true"),i.setAttribute("tabindex",-1),i.onload=this.addResizeHandlers,i.type="text/html",ie&&this.$el.appendChild(i),i.data="about:blank",ie||this.$el.appendChild(i)},beforeUnmount(){this.removeResizeHandlers()},methods:{compareAndNotify(){(!this.ignoreWidth&&this._w!==this.$el.offsetWidth||!this.ignoreHeight&&this._h!==this.$el.offsetHeight)&&(this._w=this.$el.offsetWidth,this._h=this.$el.offsetHeight,this.emitSize())},emitSize(){this.$emit("notify",{width:this._w,height:this._h})},addResizeHandlers(){this._resizeObject.contentDocument.defaultView.addEventListener("resize",this.compareAndNotify),this.compareAndNotify()},removeResizeHandlers(){this._resizeObject&&this._resizeObject.onload&&(!ie&&this._resizeObject.contentDocument&&this._resizeObject.contentDocument.defaultView.removeEventListener("resize",this.compareAndNotify),this.$el.removeChild(this._resizeObject),this._resizeObject.onload=null,this._resizeObject=null)}}};const ni=xt();tt("data-v-b329ee4c");const ii={class:"resize-observer",tabindex:"-1"};nt();const ri=ni((i,t,e,n,r,o)=>(a(),R("div",ii)));ue.render=ri;ue.__scopeId="data-v-b329ee4c";ue.__file="src/components/ResizeObserver.vue";function re(i){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?re=function(t){return typeof t}:re=function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},re(i)}function si(i,t){if(!(i instanceof t))throw new TypeError("Cannot call a class as a function")}function Ue(i,t){for(var e=0;ei.length)&&(t=i.length);for(var e=0,n=new Array(t);e2&&arguments[2]!==void 0?arguments[2]:{},n,r,o,p=function(v){for(var b=arguments.length,E=new Array(b>1?b-1:0),w=1;w1){var b=d.find(function(w){return w.isIntersecting});b&&(v=b)}if(r.callback){var E=v.isIntersecting&&v.intersectionRatio>=r.threshold;if(E===r.oldResult)return;r.oldResult=E,r.callback(E,v)}},this.options.intersection),it(function(){r.observer&&r.observer.observe(r.el)})}}},{key:"destroyObserver",value:function(){this.observer&&(this.observer.disconnect(),this.observer=null),this.callback&&this.callback._clear&&(this.callback._clear(),this.callback=null)}},{key:"threshold",get:function(){return this.options.intersection&&typeof this.options.intersection.threshold=="number"?this.options.intersection.threshold:0}}]),i}();function ft(i,t,e){var n=t.value;if(n)if(typeof IntersectionObserver>"u")console.warn("[vue-observe-visibility] IntersectionObserver API is not available in your browser. Please install this polyfill: https://github.com/w3c/IntersectionObserver/tree/master/polyfill");else{var r=new hi(i,n,e);i._vue_visibilityState=r}}function gi(i,t,e){var n=t.value,r=t.oldValue;if(!dt(n,r)){var o=i._vue_visibilityState;if(!n){ht(i);return}o?o.createObserver(n,e):ft(i,{value:n},e)}}function ht(i){var t=i._vue_visibilityState;t&&(t.destroyObserver(),delete i._vue_visibilityState)}var pi={beforeMount:ft,updated:gi,unmounted:ht},vi={itemsLimit:1e3},mi=/(auto|scroll)/;function gt(i,t){return i.parentNode===null?t:gt(i.parentNode,t.concat([i]))}var fe=function(t,e){return getComputedStyle(t,null).getPropertyValue(e)},yi=function(t){return fe(t,"overflow")+fe(t,"overflow-y")+fe(t,"overflow-x")},bi=function(t){return mi.test(yi(t))};function We(i){if(i instanceof HTMLElement||i instanceof SVGElement){for(var t=gt(i.parentNode,[]),e=0;e{this.$_prerender=!1,this.updateVisibleItems(!0),this.ready=!0})},activated(){const i=this.$_lastUpdateScrollPosition;typeof i=="number"&&this.$nextTick(()=>{this.scrollToPosition(i)})},beforeUnmount(){this.removeListeners()},methods:{addView(i,t,e,n,r){const o=Ut({id:Ii++,index:t,used:!0,key:n,type:r}),p=Jt({item:e,position:0,nr:o});return i.push(p),p},unuseView(i,t=!1){const e=this.$_unusedViews,n=i.nr.type;let r=e.get(n);r||(r=[],e.set(n,r)),r.push(i),t||(i.nr.used=!1,i.position=-9999)},handleResize(){this.$emit("resize"),this.ready&&this.updateVisibleItems(!1)},handleScroll(i){if(!this.$_scrollDirty){if(this.$_scrollDirty=!0,this.$_updateTimeout)return;const t=()=>requestAnimationFrame(()=>{this.$_scrollDirty=!1;const{continuous:e}=this.updateVisibleItems(!1,!0);e||(clearTimeout(this.$_refreshTimout),this.$_refreshTimout=setTimeout(this.handleScroll,this.updateInterval+100))});t(),this.updateInterval&&(this.$_updateTimeout=setTimeout(()=>{this.$_updateTimeout=0,this.$_scrollDirty&&t()},this.updateInterval))}},handleVisibilityChange(i,t){this.ready&&(i||t.boundingClientRect.width!==0||t.boundingClientRect.height!==0?(this.$emit("visible"),requestAnimationFrame(()=>{this.updateVisibleItems(!1)})):this.$emit("hidden"))},updateVisibleItems(i,t=!1){const e=this.itemSize,n=this.gridItems||1,r=this.itemSecondarySize||e,o=this.$_computedMinItemSize,p=this.typeField,d=this.simpleArray?null:this.keyField,v=this.items,b=v.length,E=this.sizes,w=this.$_views,T=this.$_unusedViews,l=this.pool,s=this.itemIndexByKey;let c,m,N,O,z;if(!b)c=m=O=z=N=0;else if(this.$_prerender)c=O=0,m=z=Math.min(this.prerender,v.length),N=null;else{const k=this.getScroll();if(t){let $=k.start-this.$_lastUpdateScrollPosition;if($<0&&($=-$),e===null&&$k.start&&(De=U),U=~~((x+De)/2);while(U!==Ne);for(U<0&&(U=0),c=U,N=E[b-1].accumulator,m=U;mb&&(m=b)),O=c;Ob&&(m=b),O<0&&(O=0),z>b&&(z=b),N=Math.ceil(b/n)*e}}m-c>vi.itemsLimit&&this.itemsLimitError(),this.totalSize=N;let g;const f=c<=this.$_endIndex&&m>=this.$_startIndex;if(f)for(let k=0,L=l.length;k=m)&&this.unuseView(g));const C=f?null:new Map;let M,F,B;for(let k=c;k=P.length)&&(g=this.addView(l,k,M,L,F),this.unuseView(g,!0),P=T.get(F)),g=P[B],C.set(F,B+1)),w.delete(g.nr.key),g.nr.used=!0,g.nr.index=k,g.nr.key=L,g.nr.type=F,w.set(L,g),$=!0;else if(!g.nr.used&&(g.nr.used=!0,g.nr.index=k,$=!0,P)){const x=P.indexOf(g);x!==-1&&P.splice(x,1)}g.item=M,$&&(k===v.length-1&&this.$emit("scroll-end"),k===0&&this.$emit("scroll-start")),e===null?(g.position=E[k-1].accumulator,g.offset=0):(g.position=Math.floor(k/n)*e,g.offset=k%n*r)}return this.$_startIndex=c,this.$_endIndex=m,this.emitUpdate&&this.$emit("update",c,m,O,z),clearTimeout(this.$_sortTimer),this.$_sortTimer=setTimeout(this.sortViews,this.updateInterval+300),{continuous:f}},getListenerTarget(){let i=We(this.$el);return window.document&&(i===window.document.documentElement||i===window.document.body)&&(i=window),i},getScroll(){const{$el:i,direction:t}=this,e=t==="vertical";let n;if(this.pageMode){const r=i.getBoundingClientRect(),o=e?r.height:r.width;let p=-(e?r.top:r.left),d=e?window.innerHeight:window.innerWidth;p<0&&(d+=p,p=0),p+d>o&&(d=o-p),n={start:p,end:p+d}}else e?n={start:i.scrollTop,end:i.scrollTop+i.clientHeight}:n={start:i.scrollLeft,end:i.scrollLeft+i.clientWidth};return n},applyPageMode(){this.pageMode?this.addListeners():this.removeListeners()},addListeners(){this.listenerTarget=this.getListenerTarget(),this.listenerTarget.addEventListener("scroll",this.handleScroll,Se?{passive:!0}:!1),this.listenerTarget.addEventListener("resize",this.handleResize)},removeListeners(){this.listenerTarget&&(this.listenerTarget.removeEventListener("scroll",this.handleScroll),this.listenerTarget.removeEventListener("resize",this.handleResize),this.listenerTarget=null)},scrollToItem(i){let t;const e=this.gridItems||1;this.itemSize===null?t=i>0?this.sizes[i-1].accumulator:0:t=Math.floor(i/e)*this.itemSize,this.scrollToPosition(t)},scrollToPosition(i){const t=this.direction==="vertical"?{scroll:"scrollTop",start:"top"}:{scroll:"scrollLeft",start:"left"};let e,n,r;if(this.pageMode){const o=We(this.$el),p=o.tagName==="HTML"?0:o[t.scroll],d=o.getBoundingClientRect(),b=this.$el.getBoundingClientRect()[t.start]-d[t.start];e=o,n=t.scroll,r=i+p+b}else e=this.$el,n=t.scroll,r=i;e[n]=r},itemsLimitError(){throw setTimeout(()=>{console.log("It seems the scroller element isn't scrolling, so it tries to render all the items at once.","Scroller:",this.$el),console.log("Make sure the scroller has a fixed height (or width) and 'overflow-y' (or 'overflow-x') set to 'auto' so it can scroll correctly and only render the items visible in the scroll viewport.")}),new Error("Rendered items limit reached")},sortViews(){this.pool.sort((i,t)=>i.nr.index-t.nr.index)}}};const _i={key:0,ref:"before",class:"vue-recycle-scroller__slot"},Ci={key:1,ref:"after",class:"vue-recycle-scroller__slot"};function wi(i,t,e,n,r,o){const p=Wt("ResizeObserver"),d=Kt("observe-visibility");return Yt((a(),h("div",{class:Y(["vue-recycle-scroller",{ready:r.ready,"page-mode":e.pageMode,[`direction-${i.direction}`]:!0}]),onScrollPassive:t[0]||(t[0]=(...v)=>o.handleScroll&&o.handleScroll(...v))},[i.$slots.before?(a(),h("div",_i,[te(i.$slots,"before")],512)):A("v-if",!0),(a(),R($e(e.listTag),{ref:"wrapper",style:rt({[i.direction==="vertical"?"minHeight":"minWidth"]:r.totalSize+"px"}),class:Y(["vue-recycle-scroller__item-wrapper",e.listClass])},{default:I(()=>[(a(!0),h(H,null,J(r.pool,v=>(a(),R($e(e.itemTag),qt({key:v.nr.id,style:r.ready?{transform:`translate${i.direction==="vertical"?"Y":"X"}(${v.position}px) translate${i.direction==="vertical"?"X":"Y"}(${v.offset}px)`,width:e.gridItems?`${i.direction==="vertical"&&e.itemSecondarySize||e.itemSize}px`:void 0,height:e.gridItems?`${i.direction==="horizontal"&&e.itemSecondarySize||e.itemSize}px`:void 0}:null,class:["vue-recycle-scroller__item-view",[e.itemClass,{hover:!e.skipHover&&r.hoverKey===v.nr.key}]]},Gt(e.skipHover?{}:{mouseenter:()=>{r.hoverKey=v.nr.key},mouseleave:()=>{r.hoverKey=null}})),{default:I(()=>[te(i.$slots,"default",{item:v.item,index:v.nr.index,active:v.nr.used})]),_:2},1040,["style","class"]))),128)),te(i.$slots,"empty")]),_:3},8,["style","class"])),i.$slots.after?(a(),h("div",Ci,[te(i.$slots,"after")],512)):A("v-if",!0),S(p,{onNotify:o.handleResize},null,8,["onNotify"])],34)),[[d,o.handleVisibilityChange]])}pt.render=wi;pt.__file="src/components/RecycleScroller.vue";const Ke=X({__name:"ContextMenu",props:{file:{},idx:{},selectedTag:{},isSelectedMutilFiles:{type:Boolean}},emits:["contextMenuClick"],setup(i,{emit:t}){const e=i,n=Ie(),r=V(()=>{var o;return(((o=n.conf)==null?void 0:o.all_custom_tags)??[]).reduce((p,d)=>[...p,{...d,selected:!!e.selectedTag.find(v=>v.id===d.id)}],[])});return(o,p)=>{const d=st,v=Zt,b=Xt,E=ot;return a(),R(E,{onClick:p[0]||(p[0]=w=>t("contextMenuClick",w,o.file,o.idx))},{default:I(()=>{var w;return[S(d,{key:"deleteFiles"},{default:I(()=>[_(y(o.$t("deleteSelected")),1)]),_:1}),S(d,{key:"openWithDefaultApp"},{default:I(()=>[_(y(o.$t("openWithDefaultApp")),1)]),_:1}),S(d,{key:"saveSelectedAsJson"},{default:I(()=>[_(y(o.$t("saveSelectedAsJson")),1)]),_:1}),o.file.type==="dir"?(a(),h(H,{key:0},[S(d,{key:"openInNewTab"},{default:I(()=>[_(y(o.$t("openInNewTab")),1)]),_:1}),S(d,{key:"openOnTheRight"},{default:I(()=>[_(y(o.$t("openOnTheRight")),1)]),_:1}),S(d,{key:"openWithWalkMode"},{default:I(()=>[_(y(o.$t("openWithWalkMode")),1)]),_:1})],64)):A("",!0),o.file.type==="file"?(a(),h(H,{key:1},[Q(ae)(o.file.name)?(a(),h(H,{key:0},[S(d,{key:"viewGenInfo"},{default:I(()=>[_(y(o.$t("viewGenerationInfo")),1)]),_:1}),S(v),((w=Q(n).conf)==null?void 0:w.launch_mode)!=="server"?(a(),h(H,{key:0},[S(d,{key:"send2txt2img"},{default:I(()=>[_(y(o.$t("sendToTxt2img")),1)]),_:1}),S(d,{key:"send2img2img"},{default:I(()=>[_(y(o.$t("sendToImg2img")),1)]),_:1}),S(d,{key:"send2inpaint"},{default:I(()=>[_(y(o.$t("sendToInpaint")),1)]),_:1}),S(d,{key:"send2extras"},{default:I(()=>[_(y(o.$t("sendToExtraFeatures")),1)]),_:1}),S(b,{key:"sendToThirdPartyExtension",title:o.$t("sendToThirdPartyExtension")},{default:I(()=>[S(d,{key:"send2controlnet-txt2img"},{default:I(()=>[_("ControlNet - "+y(o.$t("t2i")),1)]),_:1}),S(d,{key:"send2controlnet-img2img"},{default:I(()=>[_("ControlNet - "+y(o.$t("i2i")),1)]),_:1}),S(d,{key:"send2outpaint"},{default:I(()=>[_("openOutpaint")]),_:1})]),_:1},8,["title"])],64)):A("",!0),S(d,{key:"send2BatchDownload"},{default:I(()=>[_(y(o.$t("sendToBatchDownload")),1)]),_:1}),S(b,{key:"copy2target",title:o.$t("copyTo")},{default:I(()=>[(a(!0),h(H,null,J(Q(n).quickMovePaths,T=>(a(),R(d,{key:`copy-to-${T.dir}`},{default:I(()=>[_(y(T.zh),1)]),_:2},1024))),128))]),_:1},8,["title"]),S(b,{key:"move2target",title:o.$t("moveTo")},{default:I(()=>[(a(!0),h(H,null,J(Q(n).quickMovePaths,T=>(a(),R(d,{key:`move-to-${T.dir}`},{default:I(()=>[_(y(T.zh),1)]),_:2},1024))),128))]),_:1},8,["title"]),S(v),o.isSelectedMutilFiles?(a(),h(H,{key:1},[S(b,{key:"batch-add-tag",title:o.$t("batchAddTag")},{default:I(()=>[(a(!0),h(H,null,J(r.value,T=>(a(),R(d,{key:`batch-add-tag-${T.id}`},{default:I(()=>[_(y(T.name),1)]),_:2},1024))),128))]),_:1},8,["title"]),S(b,{key:"batch-remove-tag",title:o.$t("batchRemoveTag")},{default:I(()=>[(a(!0),h(H,null,J(r.value,T=>(a(),R(d,{key:`batch-remove-tag-${T.id}`},{default:I(()=>[_(y(T.name),1)]),_:2},1024))),128))]),_:1},8,["title"])],64)):(a(),R(b,{key:"toggle-tag",title:o.$t("toggleTag")},{default:I(()=>[(a(!0),h(H,null,J(r.value,T=>(a(),R(d,{key:`toggle-tag-${T.id}`},{default:I(()=>[_(y(T.name)+" ",1),T.selected?(a(),R(Q(at),{key:0})):(a(),R(Q(ut),{key:1}))]),_:2},1024))),128))]),_:1},8,["title"])),S(v),S(d,{key:"openFileLocationInNewTab"},{default:I(()=>[_(y(o.$t("openFileLocationInNewTab")),1)]),_:1}),S(d,{key:"openWithLocalFileBrowser"},{default:I(()=>[_(y(o.$t("openWithLocalFileBrowser")),1)]),_:1})],64)):A("",!0),S(v),S(d,{key:"rename"},{default:I(()=>[_(y(o.$t("rename")),1)]),_:1}),S(d,{key:"previewInNewWindow"},{default:I(()=>[_(y(o.$t("previewInNewWindow")),1)]),_:1}),S(d,{key:"download"},{default:I(()=>[_(y(o.$t("download")),1)]),_:1}),S(d,{key:"copyPreviewUrl"},{default:I(()=>[_(y(o.$t("copySourceFilePreviewLink")),1)]),_:1}),S(d,{key:"copyFilePath"},{default:I(()=>[_(y(o.$t("copyFilePath")),1)]),_:1})],64)):A("",!0)]}),_:1})}}}),D=i=>(tt("data-v-78cd67a3"),i=i(),nt(),i),Ei={class:"changeIndicatorWrapper"},Ti={key:0,class:"changeIndicatorsLeft changeIndicators"},Pi={key:0,class:"promptChangeIndicator changeIndicator"},Oi={key:1,class:"negpromptChangeIndicator changeIndicator"},Di={key:2,class:"seedChangeIndicator changeIndicator"},Ni={key:3,class:"stepsChangeIndicator changeIndicator"},zi={key:4,class:"cfgChangeIndicator changeIndicator"},$i={key:5,class:"sizeChangeIndicator changeIndicator"},Mi={key:6,class:"modelChangeIndicator changeIndicator"},Qi={key:7,class:"samplerChangeIndicator changeIndicator"},Bi={key:8,class:"otherChangeIndicator changeIndicator"},Fi={class:"hoverOverlay"},Ri=D(()=>u("strong",null,"This file",-1)),Li=D(()=>u("br",null,null,-1)),ji=D(()=>u("br",null,null,-1)),Hi={key:0},Vi=D(()=>u("td",null,[u("span",{class:"promptChangeIndicator"},"+ Prompt")],-1)),xi={key:1},Ui=D(()=>u("td",null,[u("span",{class:"negpromptChangeIndicator"},"- Prompt")],-1)),Ji={key:2},Wi=D(()=>u("td",null,[u("span",{class:"seedChangeIndicator"},"Seed")],-1)),Ki={key:3},Yi=D(()=>u("td",null,[u("span",{class:"stepsChangeIndicator"},"Steps")],-1)),qi={key:4},Gi=D(()=>u("td",null,[u("span",{class:"cfgChangeIndicator"},"Cfg Scale")],-1)),Zi={key:5},Xi=D(()=>u("td",null,[u("span",{class:"sizeChangeIndicator"},"Size")],-1)),er={key:6},tr=D(()=>u("td",null,[u("span",{class:"modelChangeIndicator"},"Model")],-1)),nr=D(()=>u("br",null,null,-1)),ir={key:7},rr=D(()=>u("td",null,[u("span",{class:"samplerChangeIndicator"},"Sampler")],-1)),sr=D(()=>u("br",null,null,-1)),or=D(()=>u("br",null,null,-1)),lr={key:0},ar=D(()=>u("span",{class:"otherChangeIndicator"},"Other",-1)),ur=D(()=>u("br",null,null,-1)),cr=D(()=>u("br",null,null,-1)),dr={key:1,class:"changeIndicatorsRight changeIndicators"},fr={key:0,class:"promptChangeIndicator changeIndicator"},hr={key:1,class:"negpromptChangeIndicator changeIndicator"},gr={key:2,class:"seedChangeIndicator changeIndicator"},pr={key:3,class:"stepsChangeIndicator changeIndicator"},vr={key:4,class:"cfgChangeIndicator changeIndicator"},mr={key:5,class:"sizeChangeIndicator changeIndicator"},yr={key:6,class:"modelChangeIndicator changeIndicator"},br={key:7,class:"samplerChangeIndicator changeIndicator"},Ar={key:8,class:"otherChangeIndicator changeIndicator"},Sr={class:"hoverOverlay"},kr=D(()=>u("strong",null,"This file",-1)),Ir=D(()=>u("br",null,null,-1)),_r=D(()=>u("br",null,null,-1)),Cr={key:0},wr=D(()=>u("td",null,[u("span",{class:"promptChangeIndicator"},"+ Prompt")],-1)),Er={key:1},Tr=D(()=>u("td",null,[u("span",{class:"negpromptChangeIndicator"},"- Prompt")],-1)),Pr={key:2},Or=D(()=>u("td",null,[u("span",{class:"seedChangeIndicator"},"Seed")],-1)),Dr={key:3},Nr=D(()=>u("td",null,[u("span",{class:"stepsChangeIndicator"},"Steps")],-1)),zr={key:4},$r=D(()=>u("td",null,[u("span",{class:"cfgChangeIndicator"},"Cfg Scale")],-1)),Mr={key:5},Qr=D(()=>u("td",null,[u("span",{class:"sizeChangeIndicator"},"Size")],-1)),Br={key:6},Fr=D(()=>u("td",null,[u("span",{class:"modelChangeIndicator"},"Model")],-1)),Rr=D(()=>u("br",null,null,-1)),Lr={key:7},jr=D(()=>u("td",null,[u("span",{class:"samplerChangeIndicator"},"Sampler")],-1)),Hr=D(()=>u("br",null,null,-1)),Vr=D(()=>u("br",null,null,-1)),xr={key:0},Ur=D(()=>u("span",{class:"otherChangeIndicator"},"Other",-1)),Jr=D(()=>u("br",null,null,-1)),Wr=D(()=>u("br",null,null,-1)),Kr=X({__name:"ChangeIndicator",props:{genDiffToPrevious:{},genDiffToNext:{},genInfo:{}},setup(i){function t(n){const r=["prompt","negativePrompt","seed","steps","cfgScale","size","Model","others"],o=Object.keys(n).filter(p=>!r.includes(p));return Object.fromEntries(o.map(p=>[p,n[p]]))}function e(n){return Object.keys(t(n)).length>0}return(n,r)=>(a(),h("div",Ei,[n.genDiffToPrevious.empty?A("",!0):(a(),h("div",Ti,["prompt"in n.genDiffToPrevious.diff?(a(),h("div",Pi,"P+")):A("",!0),"negativePrompt"in n.genDiffToPrevious.diff?(a(),h("div",Oi,"P-")):A("",!0),"seed"in n.genDiffToPrevious.diff?(a(),h("div",Di,"Se")):A("",!0),"steps"in n.genDiffToPrevious.diff?(a(),h("div",Ni,"St")):A("",!0),"cfgScale"in n.genDiffToPrevious.diff?(a(),h("div",zi,"Cf")):A("",!0),"size"in n.genDiffToPrevious.diff?(a(),h("div",$i,"Si")):A("",!0),"Model"in n.genDiffToPrevious.diff?(a(),h("div",Mi,"Mo")):A("",!0),"Sampler"in n.genDiffToPrevious.diff?(a(),h("div",Qi,"Sa")):A("",!0),e(n.genDiffToPrevious.diff)?(a(),h("div",Bi,"Ot")):A("",!0)])),u("div",Fi,[u("small",null,[S(Q(je)),Ri,_(" vs "+y(n.genDiffToPrevious.otherFile)+" ",1),Li,ji,u("table",null,["prompt"in n.genDiffToPrevious.diff?(a(),h("tr",Hi,[Vi,u("td",null,y(n.genDiffToPrevious.diff.prompt)+" tokens changed",1)])):A("",!0),"negativePrompt"in n.genDiffToPrevious.diff?(a(),h("tr",xi,[Ui,u("td",null,y(n.genDiffToPrevious.diff.negativePrompt)+" tokens changed",1)])):A("",!0),"seed"in n.genDiffToPrevious.diff?(a(),h("tr",Ji,[Wi,u("td",null,[u("strong",null,y(n.genDiffToPrevious.diff.seed[0]),1),_(" vs "+y(n.genDiffToPrevious.diff.seed[1]),1)])])):A("",!0),"steps"in n.genDiffToPrevious.diff?(a(),h("tr",Ki,[Yi,u("td",null,[u("strong",null,y(n.genDiffToPrevious.diff.steps[0]),1),_(" vs "+y(n.genDiffToPrevious.diff.steps[1]),1)])])):A("",!0),"cfgScale"in n.genDiffToPrevious.diff?(a(),h("tr",qi,[Gi,u("td",null,[u("strong",null,y(n.genDiffToPrevious.diff.cfgScale[0]),1),_(" vs "+y(n.genDiffToPrevious.diff.cfgScale[1]),1)])])):A("",!0),"size"in n.genDiffToPrevious.diff?(a(),h("tr",Zi,[Xi,u("td",null,[u("strong",null,y(n.genDiffToPrevious.diff.size[0]),1),_(" vs "+y(n.genDiffToPrevious.diff.size[1]),1)])])):A("",!0),"Model"in n.genDiffToPrevious.diff?(a(),h("tr",er,[tr,u("td",null,[u("strong",null,y(n.genDiffToPrevious.diff.Model[0]),1),nr,_(" vs "+y(n.genDiffToPrevious.diff.Model[1]),1)])])):A("",!0),"Sampler"in n.genDiffToPrevious.diff?(a(),h("tr",ir,[rr,u("td",null,[u("strong",null,y(n.genDiffToPrevious.diff.Sampler[0]),1),sr,_(" vs "+y(n.genDiffToPrevious.diff.Sampler[1]),1)])])):A("",!0)]),or,e(n.genDiffToPrevious.diff)?(a(),h("div",lr,[ar,_(" props that changed:"),ur,cr,u("ul",null,[(a(!0),h(H,null,J(t(n.genDiffToPrevious.diff),(o,p)=>(a(),h("li",null,y(p),1))),256))])])):A("",!0)])]),n.genDiffToNext.empty?A("",!0):(a(),h("div",dr,["prompt"in n.genDiffToNext.diff?(a(),h("div",fr,"P+")):A("",!0),"negativePrompt"in n.genDiffToNext.diff?(a(),h("div",hr,"P-")):A("",!0),"seed"in n.genDiffToNext.diff?(a(),h("div",gr,"Se")):A("",!0),"steps"in n.genDiffToNext.diff?(a(),h("div",pr,"St")):A("",!0),"cfgScale"in n.genDiffToNext.diff?(a(),h("div",vr,"Cf")):A("",!0),"size"in n.genDiffToNext.diff?(a(),h("div",mr,"Si")):A("",!0),"Model"in n.genDiffToNext.diff?(a(),h("div",yr,"Mo")):A("",!0),"Sampler"in n.genDiffToNext.diff?(a(),h("div",br,"Sa")):A("",!0),e(n.genDiffToNext.diff)?(a(),h("div",Ar,"Ot")):A("",!0)])),u("div",Sr,[u("small",null,[S(Q(je)),kr,_(" vs "+y(n.genDiffToNext.otherFile)+" ",1),Ir,_r,u("table",null,["prompt"in n.genDiffToNext.diff?(a(),h("tr",Cr,[wr,u("td",null,y(n.genDiffToNext.diff.prompt)+" tokens changed",1)])):A("",!0),"negativePrompt"in n.genDiffToNext.diff?(a(),h("tr",Er,[Tr,u("td",null,y(n.genDiffToNext.diff.negativePrompt)+" tokens changed",1)])):A("",!0),"seed"in n.genDiffToNext.diff?(a(),h("tr",Pr,[Or,u("td",null,[u("strong",null,y(n.genDiffToNext.diff.seed[0]),1),_(" vs "+y(n.genDiffToNext.diff.seed[1]),1)])])):A("",!0),"steps"in n.genDiffToNext.diff?(a(),h("tr",Dr,[Nr,u("td",null,[u("strong",null,y(n.genDiffToNext.diff.steps[0]),1),_(" vs "+y(n.genDiffToNext.diff.steps[1]),1)])])):A("",!0),"cfgScale"in n.genDiffToNext.diff?(a(),h("tr",zr,[$r,u("td",null,[u("strong",null,y(n.genDiffToNext.diff.cfgScale[0]),1),_(" vs "+y(n.genDiffToNext.diff.cfgScale[1]),1)])])):A("",!0),"size"in n.genDiffToNext.diff?(a(),h("tr",Mr,[Qr,u("td",null,[u("strong",null,y(n.genDiffToNext.diff.size[0]),1),_(" vs "+y(n.genDiffToNext.diff.size[1]),1)])])):A("",!0),"Model"in n.genDiffToNext.diff?(a(),h("tr",Br,[Fr,u("td",null,[u("strong",null,y(n.genDiffToNext.diff.Model[0]),1),Rr,_(" vs "+y(n.genDiffToNext.diff.Model[1]),1)])])):A("",!0),"Sampler"in n.genDiffToNext.diff?(a(),h("tr",Lr,[jr,u("td",null,[u("strong",null,y(n.genDiffToNext.diff.Sampler[0]),1),Hr,_(" vs "+y(n.genDiffToNext.diff.Sampler[1]),1)])])):A("",!0)]),Vr,e(n.genDiffToNext.diff)?(a(),h("div",xr,[Ur,_(" props that changed:"),Jr,Wr,u("ul",null,[(a(!0),h(H,null,J(t(n.genDiffToNext.diff),(o,p)=>(a(),h("li",null,y(p),1))),256))])])):A("",!0)])])]))}});const Yr=lt(Kr,[["__scopeId","data-v-78cd67a3"]]),qr=["data-idx"],Gr={key:1,class:"more"},Zr={class:"float-btn-wrap"},Xr={key:1,class:"tags-container"},es=["url"],ts={class:"play-icon"},ns=["src"],is={key:0,class:"tags-container"},rs={key:4,class:"preview-icon-wrap"},ss={key:1,class:"dir-cover-container"},os=["src"],ls={key:5,class:"profile"},as=["title"],us={class:"basic-info"},cs={style:{"margin-right":"4px"}},he=160,ds=X({__name:"FileItem",props:{file:{},idx:{},selected:{type:Boolean,default:!1},showMenuIdx:{},cellWidth:{},fullScreenPreviewImageUrl:{},enableRightClickMenu:{type:Boolean,default:!0},enableCloseIcon:{type:Boolean,default:!1},isSelectedMutilFiles:{type:Boolean},genInfo:{},enableChangeIndicator:{type:Boolean},extraTags:{},coverFiles:{},getGenDiff:{},getGenDiffWatchDep:{}},emits:["update:showMenuIdx","fileItemClick","dragstart","dragend","previewVisibleChange","contextMenuClick","close-icon-click"],setup(i,{emit:t}){const e=i;en(l=>({"6b392466":l.$props.cellWidth+"px"}));const n=Ie(),r=Xe(),o=j(),p=j(),d=pe(()=>{const{getGenDiff:l,file:s,idx:c}=e;l&&(p.value=l(s.gen_info_obj,c,1,s),o.value=l(s.gen_info_obj,c,-1,s))},200+100*Math.random());se(()=>{var l;return(l=e.getGenDiffWatchDep)==null?void 0:l.call(e,e.idx)},()=>{d()},{immediate:!0,deep:!0});const v=V(()=>r.tagMap.get(e.file.fullpath)??[]),b=V(()=>{const l=n.gridThumbnailResolution;return n.enableThumbnail?Me(e.file,[l,l].join("x")):tn(e.file)}),E=V(()=>{var l;return(((l=n.conf)==null?void 0:l.all_custom_tags)??[]).reduce((s,c)=>[...s,{...c,selected:!!v.value.find(m=>m.id===c.id)}],[])}),w=V(()=>E.value.find(l=>l.type==="custom"&&l.name==="like")),T=()=>{ge(w.value),t("contextMenuClick",{key:`toggle-tag-${w.value.id}`},e.file,e.idx)};return(l,s)=>{const c=G,m=st,N=ot,O=ln,z=pn;return a(),R(c,{trigger:["contextmenu"],visible:Q(n).longPressOpenContextMenu?typeof l.idx=="number"&&l.showMenuIdx===l.idx:void 0,"onUpdate:visible":s[8]||(s[8]=g=>typeof l.idx=="number"&&t("update:showMenuIdx",g?l.idx:-1))},{overlay:I(()=>[l.enableRightClickMenu?(a(),R(Ke,{key:0,file:l.file,idx:l.idx,"selected-tag":v.value,onContextMenuClick:s[7]||(s[7]=(g,f,C)=>t("contextMenuClick",g,f,C)),"is-selected-mutil-files":l.isSelectedMutilFiles},null,8,["file","idx","selected-tag","is-selected-mutil-files"])):A("",!0)]),default:I(()=>{var g;return[(a(),h("li",{class:Y(["file file-item-trigger grid",{clickable:l.file.type==="dir",selected:l.selected}]),"data-idx":l.idx,key:l.file.name,draggable:"true",onDragstart:s[4]||(s[4]=f=>t("dragstart",f,l.idx)),onDragend:s[5]||(s[5]=f=>t("dragend",f,l.idx)),onClickCapture:s[6]||(s[6]=f=>t("fileItemClick",f,l.file,l.idx))},[u("div",null,[l.enableCloseIcon?(a(),h("div",{key:0,class:"close-icon",onClick:s[0]||(s[0]=f=>t("close-icon-click"))},[S(Q(nn))])):A("",!0),l.enableRightClickMenu?(a(),h("div",Gr,[S(c,null,{overlay:I(()=>[S(Ke,{file:l.file,idx:l.idx,"selected-tag":v.value,onContextMenuClick:s[1]||(s[1]=(f,C,M)=>t("contextMenuClick",f,C,M)),"is-selected-mutil-files":l.isSelectedMutilFiles},null,8,["file","idx","selected-tag","is-selected-mutil-files"])]),default:I(()=>[u("div",Zr,[S(Q(rn))])]),_:1}),l.file.type==="file"?(a(),R(c,{key:0},{overlay:I(()=>[E.value.length>1?(a(),R(N,{key:0,onClick:s[2]||(s[2]=f=>t("contextMenuClick",f,l.file,l.idx))},{default:I(()=>[(a(!0),h(H,null,J(E.value,f=>(a(),R(m,{key:`toggle-tag-${f.id}`},{default:I(()=>[_(y(f.name)+" ",1),f.selected?(a(),R(Q(at),{key:0})):(a(),R(Q(ut),{key:1}))]),_:2},1024))),128))]),_:1})):A("",!0)]),default:I(()=>{var f,C;return[u("div",{class:Y(["float-btn-wrap",{"like-selected":(f=w.value)==null?void 0:f.selected}]),onClick:T},[(C=w.value)!=null&&C.selected?(a(),R(Q(Vn),{key:0})):(a(),R(Q(Wn),{key:1}))],2)]}),_:1})):A("",!0)])):A("",!0),Q(sn)(l.file.name)?(a(),h("div",{key:l.file.fullpath,class:Y(`idx-${l.idx} item-content`)},[l.enableChangeIndicator&&p.value&&o.value?(a(),R(Yr,{key:0,"gen-diff-to-next":p.value,"gen-diff-to-previous":o.value},null,8,["gen-diff-to-next","gen-diff-to-previous"])):A("",!0),S(O,{src:b.value,fallback:Q(Mn),preview:{src:l.fullScreenPreviewImageUrl,onVisibleChange:(f,C)=>t("previewVisibleChange",f,C)}},null,8,["src","fallback","preview"]),v.value&&l.cellWidth>he?(a(),h("div",Xr,[(a(!0),h(H,null,J(l.extraTags??v.value,f=>(a(),R(z,{key:f.id,color:Q(r).getColor(f.name)},{default:I(()=>[_(y(f.name),1)]),_:2},1032,["color"]))),128))])):A("",!0)],2)):Q(on)(l.file.name)?(a(),h("div",{key:3,class:Y(`idx-${l.idx} item-content video`),url:Q(de)(l.file),style:rt({"background-image":`url('${l.file.cover_url??Q(de)(l.file)}')`}),onClick:s[3]||(s[3]=f=>Q(un)(l.file,C=>t("contextMenuClick",{key:`toggle-tag-${C}`},l.file,l.idx)))},[u("div",ts,[u("img",{src:Q(Gn),style:{width:"40px",height:"40px"}},null,8,ns)]),v.value&&l.cellWidth>he?(a(),h("div",is,[(a(!0),h(H,null,J(v.value,f=>(a(),R(z,{key:f.id,color:Q(r).getColor(f.name)},{default:I(()=>[_(y(f.name),1)]),_:2},1032,["color"]))),128))])):A("",!0)],14,es)):(a(),h("div",rs,[l.file.type==="file"?(a(),R(Q(kn),{key:0,class:"icon center"})):(g=l.coverFiles)!=null&&g.length&&l.cellWidth>160?(a(),h("div",ss,[(a(!0),h(H,null,J(l.coverFiles,f=>(a(),h("img",{class:"dir-cover-item",src:f.media_type==="image"?Q(Me)(f):Q(de)(f),key:f.fullpath},null,8,os))),128))])):(a(),R(Q(wn),{key:2,class:"icon center"}))])),l.cellWidth>he?(a(),h("div",ls,[u("div",{class:"name line-clamp-1",title:l.file.name},y(l.file.name),9,as),u("div",us,[u("div",cs,y(l.file.type)+" "+y(l.file.size),1),u("div",null,y(l.file.date),1)])])):A("",!0)])],42,qr))]}),_:1},8,["visible"])}}});const Es=lt(ds,[["__scopeId","data-v-9a0c2829"]]);export{Es as F,ms as N,Ke as _,ys as a,ws as b,Cs as c,pt as d,_s as e,Ss as f,q as g,Is as h,As as i,ks as j,Zn as k,$n as r,bs as s,ei as t,le as u}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/ImgSliPagePane-868b21f8.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/ImgSliPagePane-868b21f8.css new file mode 100644 index 0000000000000000000000000000000000000000..08ad926d24820371b914976269b54c4251251f7a --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/ImgSliPagePane-868b21f8.css @@ -0,0 +1 @@ +.img-sli-container[data-v-ae3fb9a8]{position:relative;overflow-y:auto;height:calc(100vh - 40px)} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/ImgSliPagePane-c51a65d0.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/ImgSliPagePane-c51a65d0.js new file mode 100644 index 0000000000000000000000000000000000000000..09477fb61c6c7e06771be9407871d50c3e477ad0 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/ImgSliPagePane-c51a65d0.js @@ -0,0 +1 @@ +import{d as a,S as t,T as s,c as n,cx as _,Z as o}from"./index-93a218ca.js";const c={class:"img-sli-container"},i=a({__name:"ImgSliPagePane",props:{paneIdx:{},tabIdx:{},left:{},right:{}},setup(l){return(e,r)=>(t(),s("div",c,[n(_,{left:e.left,right:e.right},null,8,["left","right"])]))}});const d=o(i,[["__scopeId","data-v-ae3fb9a8"]]);export{d as default}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MatchedImageGrid-30741878.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MatchedImageGrid-30741878.js new file mode 100644 index 0000000000000000000000000000000000000000..71ac014baafa9e7d8baddc974d7bb3f6cc404cc6 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MatchedImageGrid-30741878.js @@ -0,0 +1 @@ +import{d as ce,m as F,ax as re,$ as pe,S as p,T as I,c as s,a2 as e,a1 as n,a4 as R,U as d,J as ue,W as o,V as m,a0 as V,ad as me,Y as k,ae as G,ag as ge,R as fe,ai as z,aN as ve,aO as he,bD as Ie,Z as ke}from"./index-93a218ca.js";import{S as _e}from"./index-c7fe1b25.js";import{L as Ce,R as we,f as Se,M as be}from"./MultiSelectKeep-e20b5f46.js";import{c as xe,d as ye,F as Me}from"./FileItem-9a94f7dd.js";import{c as Ae,u as De}from"./hook-983826b5.js";import{a as Te}from"./functionalCallableComp-685da399.js";import"./shortcut-ff09b8ec.js";import"./index-4596f42d.js";/* empty css */import"./index-f60e0de3.js";const $e=c=>(ve("data-v-caebce58"),c=c(),he(),c),Fe={class:"hint"},Re={class:"action-bar"},Ve=$e(()=>d("div",{style:{padding:"16px 0 512px"}},null,-1)),Ge={key:1},ze={class:"no-res-hint"},Be={class:"hint"},Ne={key:2,class:"preview-switch"},Ue=ce({__name:"MatchedImageGrid",props:{tabIdx:{},paneIdx:{},selectedTagIds:{},id:{}},setup(c){const g=c,f=Ae(t=>Ie(g.selectedTagIds,t)),{queue:B,images:i,onContextMenuClickU:_,stackViewEl:N,previewIdx:r,previewing:C,onPreviewVisibleChange:U,previewImgMove:w,canPreview:S,itemSize:b,gridItems:J,showGenInfo:u,imageGenInfo:x,q:E,multiSelectedIdxs:v,onFileItemClick:L,scroller:y,showMenuIdx:h,onFileDragStart:O,onFileDragEnd:P,cellWidth:K,onScroll:M,saveAllFileAsJson:W,props:q,saveLoadedFileAsJson:Q,changeIndchecked:Y,seedChangeChecked:Z,getGenDiff:j,getGenDiffWatchDep:H}=De(f);F(()=>g.selectedTagIds,async()=>{var t;await f.reset(),await re(),(t=y.value)==null||t.scrollToItem(0),M()},{immediate:!0}),F(()=>g,async t=>{q.value=t},{deep:!0,immediate:!0});const X=pe(),{onClearAllSelected:ee,onSelectAll:te,onReverseSelect:le}=xe();return(t,l)=>{const se=be,ne=ge,ae=fe,A=z,ie=z,oe=_e;return p(),I("div",{class:"container",ref_key:"stackViewEl",ref:N},[s(se,{show:!!e(v).length||e(X).keepMultiSelect,onClearAllSelected:e(ee),onSelectAll:e(te),onReverseSelect:e(le)},null,8,["show","onClearAllSelected","onSelectAll","onReverseSelect"]),s(oe,{size:"large",spinning:!e(B).isIdle},{default:n(()=>{var D,T;return[s(ae,{visible:e(u),"onUpdate:visible":l[1]||(l[1]=a=>R(u)?u.value=a:null),width:"70vw","mask-closable":"",onOk:l[2]||(l[2]=a=>u.value=!1)},{cancelText:n(()=>[]),default:n(()=>[s(ne,{active:"",loading:!e(E).isIdle},{default:n(()=>[d("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:l[0]||(l[0]=a=>e(ue)(e(x)))},[d("div",Fe,o(t.$t("doubleClickToCopy")),1),m(" "+o(e(x)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),d("div",Re,[s(A,{onClick:e(Q)},{default:n(()=>[m(o(t.$t("saveLoadedImageAsJson")),1)]),_:1},8,["onClick"]),s(A,{onClick:e(W)},{default:n(()=>[m(o(t.$t("saveAllAsJson")),1)]),_:1},8,["onClick"])]),(D=e(i))!=null&&D.length?(p(),V(e(ye),{key:0,ref_key:"scroller",ref:y,class:"file-list",items:e(i),"item-size":e(b).first,"key-field":"fullpath","item-secondary-size":e(b).second,gridItems:e(J),onScroll:e(M)},{after:n(()=>[Ve]),default:n(({item:a,index:$})=>[s(Me,{idx:$,file:a,"cell-width":e(K),"show-menu-idx":e(h),"onUpdate:showMenuIdx":l[3]||(l[3]=de=>R(h)?h.value=de:null),onDragstart:e(O),onDragend:e(P),onFileItemClick:e(L),"full-screen-preview-image-url":e(i)[e(r)]?e(me)(e(i)[e(r)]):"",selected:e(v).includes($),onContextMenuClick:e(_),onPreviewVisibleChange:e(U),"is-selected-mutil-files":e(v).length>1,"enable-change-indicator":e(Y),"seed-change-checked":e(Z),"get-gen-diff":e(j),"get-gen-diff-watch-dep":e(H)},null,8,["idx","file","cell-width","show-menu-idx","onDragstart","onDragend","onFileItemClick","full-screen-preview-image-url","selected","onContextMenuClick","onPreviewVisibleChange","is-selected-mutil-files","enable-change-indicator","seed-change-checked","get-gen-diff","get-gen-diff-watch-dep"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"])):e(f).load&&t.selectedTagIds.and_tags.length===1&&!((T=t.selectedTagIds.folder_paths_str)!=null&&T.trim())?(p(),I("div",Ge,[d("div",ze,[d("p",Be,o(t.$t("tagSearchNoResultsMessage")),1),s(ie,{onClick:l[4]||(l[4]=a=>e(Te)()),type:"primary"},{default:n(()=>[m(o(t.$t("rebuildImageIndex")),1)]),_:1})])])):k("",!0),e(C)?(p(),I("div",Ne,[s(e(Ce),{onClick:l[5]||(l[5]=a=>e(w)("prev")),class:G({disable:!e(S)("prev")})},null,8,["class"]),s(e(we),{onClick:l[6]||(l[6]=a=>e(w)("next")),class:G({disable:!e(S)("next")})},null,8,["class"])])):k("",!0)]}),_:1},8,["spinning"]),e(C)&&e(i)&&e(i)[e(r)]?(p(),V(Se,{key:0,file:e(i)[e(r)],idx:e(r),onContextMenuClick:e(_)},null,8,["file","idx","onContextMenuClick"])):k("",!0)],512)}}});const Ze=ke(Ue,[["__scopeId","data-v-caebce58"]]);export{Ze as default}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MatchedImageGrid-fd659ec7.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MatchedImageGrid-fd659ec7.css new file mode 100644 index 0000000000000000000000000000000000000000..da864f85ba5a038ca5b43845627123364179dbe8 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MatchedImageGrid-fd659ec7.css @@ -0,0 +1 @@ +.container[data-v-caebce58]{background:var(--zp-secondary-background);position:relative}.container .action-bar[data-v-caebce58]{display:flex;align-items:center;user-select:none;gap:4px;padding:4px}.container .action-bar>*[data-v-caebce58]{flex-wrap:wrap}.container .file-list[data-v-caebce58]{list-style:none;padding:8px;overflow:auto;height:calc(var(--pane-max-height) - 40px);width:100%}.container .no-res-hint[data-v-caebce58]{height:var(--pane-max-height);display:flex;align-items:center;flex-direction:column;justify-content:center}.container .no-res-hint .hint[data-v-caebce58]{font-size:1.6em;margin-bottom:2em;text-align:center} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MultiSelectKeep-cedcdbe1.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MultiSelectKeep-cedcdbe1.css new file mode 100644 index 0000000000000000000000000000000000000000..7c4894ad3bb3c5f06f8ecfeced286872b79cf9b0 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MultiSelectKeep-cedcdbe1.css @@ -0,0 +1 @@ +.full-screen-menu[data-v-a9e90b6a]{position:fixed;z-index:9999;background:var(--zp-primary-background);padding:8px 16px;box-shadow:0 0 4px var(--zp-secondary);border-radius:4px}.full-screen-menu .tags-container[data-v-a9e90b6a]{margin:4px 0}.full-screen-menu .tags-container .tag[data-v-a9e90b6a]{margin-right:4px;margin-bottom:4px;padding:2px 16px;border-radius:4px;display:inline-block;cursor:pointer;font-weight:700;transition:.5s all ease;border:2px solid var(--tag-color);color:var(--tag-color);background:var(--zp-primary-background);user-select:none}.full-screen-menu .tags-container .tag.selected[data-v-a9e90b6a]{background:var(--tag-color);color:#fff}.full-screen-menu .container[data-v-a9e90b6a]{height:100%;display:flex;overflow:hidden;flex-direction:column}.full-screen-menu .gen-info[data-v-a9e90b6a]{flex:1;word-break:break-all;white-space:pre-line;overflow:auto;z-index:1;padding-top:4px;position:relative}.full-screen-menu .gen-info code[data-v-a9e90b6a]{font-size:.9em;display:block;padding:4px;background:var(--zp-primary-background);border-radius:4px;margin-right:20px;white-space:pre-wrap;word-break:break-word;line-height:1.78em}.full-screen-menu .gen-info code[data-v-a9e90b6a] .short-tag{word-break:break-all;white-space:nowrap}.full-screen-menu .gen-info code[data-v-a9e90b6a] span.tag{background:var(--zp-secondary-variant-background);color:var(--zp-primary);padding:2px 4px;border-radius:6px;margin-right:6px;margin-top:4px;line-height:1.3em;display:inline-block}.full-screen-menu .gen-info code[data-v-a9e90b6a] .has-parentheses.tag{background:rgba(255,100,100,.14)}.full-screen-menu .gen-info code[data-v-a9e90b6a] span.tag:hover{background:rgba(120,0,0,.15)}.full-screen-menu .gen-info table[data-v-a9e90b6a]{font-size:1em;border-radius:4px;border-collapse:separate;margin-bottom:3em}.full-screen-menu .gen-info table tr td[data-v-a9e90b6a]:first-child{white-space:nowrap}.full-screen-menu .gen-info table td[data-v-a9e90b6a]{padding-right:14px;padding-left:4px;border-bottom:1px solid var(--zp-secondary);border-collapse:collapse}.full-screen-menu .gen-info .info-tags .info-tag[data-v-a9e90b6a]{display:inline-block;overflow:hidden;border-radius:4px;margin-right:8px;border:2px solid var(--zp-primary)}.full-screen-menu .gen-info .info-tags .name[data-v-a9e90b6a]{background-color:var(--zp-primary);color:var(--zp-primary-background);padding:4px;border-bottom-right-radius:4px}.full-screen-menu .gen-info .info-tags .value[data-v-a9e90b6a]{padding:4px}.full-screen-menu.unset-size[data-v-a9e90b6a]{width:unset!important;height:unset!important}.full-screen-menu .mouse-sensor[data-v-a9e90b6a]{position:absolute;bottom:0;right:0;transform:rotate(90deg);cursor:se-resize;z-index:1;background:var(--zp-primary-background);border-radius:2px}.full-screen-menu .mouse-sensor>*[data-v-a9e90b6a]{font-size:18px;padding:4px}.full-screen-menu .action-bar[data-v-a9e90b6a]{display:flex;align-items:center;user-select:none;gap:4px}.full-screen-menu .action-bar .icon[data-v-a9e90b6a]{font-size:1.5em;padding:2px 4px;border-radius:4px}.full-screen-menu .action-bar .icon[data-v-a9e90b6a]:hover{background:var(--zp-secondary-variant-background)}.full-screen-menu .action-bar>*[data-v-a9e90b6a]{flex-wrap:wrap}.full-screen-menu.lr[data-v-a9e90b6a]{top:var(--edcc2c4a)!important;right:0!important;bottom:0!important;left:100vw!important;height:unset!important;width:var(--95805fe4)!important;transition:left ease .3s}.full-screen-menu.lr.always-on[data-v-a9e90b6a],.full-screen-menu.lr.mouse-in[data-v-a9e90b6a]{left:var(--5cbd8d52)!important}.lr-layout-control[data-v-a9e90b6a]{display:flex;align-items:center;gap:16px;padding:4px 8px;flex-wrap:wrap;border-radius:2px;border-left:3px solid var(--zp-luminous);background-color:var(--zp-secondary-background)}.lr-layout-control .ctrl-item[data-v-a9e90b6a]{display:flex;align-items:center;gap:4px;flex-wrap:nowrap}.select-actions[data-v-b04c3508]>:not(:last-child){margin-right:4px}.float-panel[data-v-b04c3508]{position:absolute;bottom:32px;right:32px;background:var(--zp-primary-background);border-radius:4px;z-index:1000;padding:8px;box-shadow:0 0 4px var(--zp-secondary)} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MultiSelectKeep-e20b5f46.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MultiSelectKeep-e20b5f46.js new file mode 100644 index 0000000000000000000000000000000000000000..9878eddef5d7eded8e54391db65418594b4262e8 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/MultiSelectKeep-e20b5f46.js @@ -0,0 +1,3 @@ +import{bY as gt,n as ge,bf as ot,c,A as ce,bZ as X,y as Z,z as T,m as ie,x as Pe,b_ as Ge,b$ as ht,bN as Te,R as pe,ai as se,c0 as yt,r as K,N as Se,c1 as st,G as Xe,ad as ae,c2 as _t,c3 as wt,V as C,ac as bt,c4 as Fe,O as ye,K as kt,c5 as Ot,J as me,c6 as Et,c7 as Lt,bX as $t,c8 as Pt,c9 as St,q as Ie,ca as Ft,cb as xe,o as it,ax as It,$ as Ae,I as Ce,ak as xt,E as Q,cc as Ct,cd as Mt,Q as Dt,d as rt,ce as Tt,cf as At,S as k,T as x,a2 as g,Y as B,U as A,a0 as oe,a1 as b,cg as zt,X as Y,W as E,a6 as ve,ae as Ye,a5 as jt,a4 as _e,ch as Oe,a3 as Nt,aj as Wt,ci as Bt,cj as Ht,M as Ut,aL as Vt,ck as qt,cl as Gt,aN as Xt,aO as Yt,Z as ct}from"./index-93a218ca.js";import{u as he,e as Me,g as W,f as Ke,h as te,r as Kt,t as Ee,i as Jt,s as Je,j as we,_ as Qt}from"./FileItem-9a94f7dd.js";import{M as ut,c as dt,m as De,b as Zt,d as Rt,e as en}from"./functionalCallableComp-685da399.js";import{C as tn,g as nn}from"./shortcut-ff09b8ec.js";/* empty css */import{_ as ln}from"./index-4596f42d.js";import{D as an}from"./index-f60e0de3.js";var on="Expected a function";function sn(e,t,n){var l=!0,i=!0;if(typeof e!="function")throw new TypeError(on);return gt(n)&&(l="leading"in n?!!n.leading:l,i="trailing"in n?!!n.trailing:i),ge(e,t,{leading:l,maxWait:t,trailing:i})}const re=(...e)=>{document.addEventListener(...e),ot(()=>document.removeEventListener(...e))};var rn={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M855 160.1l-189.2 23.5c-6.6.8-9.3 8.8-4.7 13.5l54.7 54.7-153.5 153.5a8.03 8.03 0 000 11.3l45.1 45.1c3.1 3.1 8.2 3.1 11.3 0l153.6-153.6 54.7 54.7a7.94 7.94 0 0013.5-4.7L863.9 169a7.9 7.9 0 00-8.9-8.9zM416.6 562.3a8.03 8.03 0 00-11.3 0L251.8 715.9l-54.7-54.7a7.94 7.94 0 00-13.5 4.7L160.1 855c-.6 5.2 3.7 9.5 8.9 8.9l189.2-23.5c6.6-.8 9.3-8.8 4.7-13.5l-54.7-54.7 153.6-153.6c3.1-3.1 3.1-8.2 0-11.3l-45.2-45z"}}]},name:"arrows-alt",theme:"outlined"};const cn=rn;function Qe(e){for(var t=1;t{const e=Array.from(document.querySelectorAll(".ant-image-preview-wrap")).find(t=>t.style.display!=="none");e?(console.log("closeImageFullscreenPreview success"),xn(e)):console.log("closeImageFullscreenPreview not found")};function xn(e){if(!(e instanceof HTMLElement))throw new Error("The provided value is not an HTMLElement.");const t=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0,target:e});e.dispatchEvent(t)}const Cn=(e,t)=>{const n=t.querySelector(`.idx-${e} .ant-image-img`);n?n.click():console.log("openImageFullscreenPreview error: not found",e,t)};function Sl(e){const{previewIdx:t,eventEmitter:n,canLoadNext:l,previewing:i,sortedFiles:u,scroller:S,props:M}=he().toRefs(),{state:F}=he();let y=null;const _=(w,d)=>{var r;i.value=w,y!=null&&!w&&d&&((r=S.value)==null||r.scrollToItem(y),y=null)},I=()=>{if(!$("next")){if(e!=null&&e.loadNext)return e.loadNext();M.value.mode==="walk"&&l.value&&(Z.info(T("loadingNextFolder")),n.value.emit("loadNextDir",!0))}};re("keydown",w=>{var d;if(i.value){let r=t.value;if(["ArrowDown","ArrowRight"].includes(w.key))for(r++;u.value[r]&&!X(u.value[r].name);)r++;else if(["ArrowUp","ArrowLeft"].includes(w.key))for(r--;u.value[r]&&!X(u.value[r].name);)r--;if(X((d=u.value[r])==null?void 0:d.name)??""){t.value=r;const P=S.value;P&&!(r>=P.$_startIndex&&r<=P.$_endIndex)&&(y=r)}I()}});const h=w=>{var r;let d=t.value;if(w==="next")for(d++;u.value[d]&&!X(u.value[d].name);)d++;else if(w==="prev")for(d--;u.value[d]&&!X(u.value[d].name);)d--;if(X((r=u.value[d])==null?void 0:r.name)??""){t.value=d;const P=S.value;P&&!(d>=P.$_startIndex&&d<=P.$_endIndex)&&(y=d)}I()},$=w=>{var r;let d=t.value;if(w==="next")for(d++;u.value[d]&&!X(u.value[d].name);)d++;else if(w==="prev")for(d--;u.value[d]&&!X(u.value[d].name);)d--;return X((r=u.value[d])==null?void 0:r.name)};return Me("removeFiles",async()=>{i.value&&!F.sortedFiles[t.value]&&be()}),{previewIdx:t,onPreviewVisibleChange:_,previewing:i,previewImgMove:h,canPreview:$}}function Le(e){return typeof e=="function"||Object.prototype.toString.call(e)==="[object Object]"&&!yt(e)}function Fl(){const{currLocation:e,sortedFiles:t,currPage:n,multiSelectedIdxs:l,eventEmitter:i,walker:u}=he().toRefs(),S=()=>{l.value=[]};return re("click",()=>{W.keepMultiSelect||S()}),re("blur",()=>{W.keepMultiSelect||S()}),ie(n,S),{onFileDragStart:(_,I)=>{const h=Pe(t.value[I]);Ke.fileDragging=!0,console.log("onFileDragStart set drag file ",_,I,h);const $=[h];let w=h.type==="dir";if(l.value.includes(I)){const r=l.value.map(P=>t.value[P]);$.push(...r),w=r.some(P=>P.type==="dir")}const d={includeDir:w,loc:e.value||"search-result",path:Ge($,"fullpath").map(r=>r.fullpath),nodes:Ge($,"fullpath"),__id:"FileTransferData"};_.dataTransfer.setData("text/plain",JSON.stringify(d))},onDrop:async _=>{if(u.value)return;const I=ht(_);if(!I)return;const h=e.value;if(I.loc===h)return;const $=Te(),w=async()=>$.pushAction(async()=>{await dt(I.path,h),i.value.emit("refresh"),pe.destroyAll()}),d=()=>$.pushAction(async()=>{await De(I.path,h),te.emit("removeFiles",{paths:I.path,loc:I.loc}),i.value.emit("refresh"),pe.destroyAll()});pe.confirm({title:T("confirm")+"?",width:"60vw",content:()=>{let r,P,D;return c("div",null,[c("div",null,[`${T("moveSelectedFilesTo")} ${h}`,c("ol",{style:{maxHeight:"50vh",overflow:"auto"}},[I.path.map(L=>c("li",null,[L.split(/[/\\]/).pop()]))])]),c(ut,null,null),c("div",{style:{display:"flex",alignItems:"center",justifyContent:"flex-end"},class:"actions"},[c(se,{onClick:pe.destroyAll},Le(r=T("cancel"))?r:{default:()=>[r]}),c(se,{type:"primary",loading:!$.isIdle,onClick:w},Le(P=T("copy"))?P:{default:()=>[P]}),c(se,{type:"primary",loading:!$.isIdle,onClick:d},Le(D=T("move"))?D:{default:()=>[D]})])])},maskClosable:!0,wrapClassName:"hidden-antd-btns-modal"})},multiSelectedIdxs:l,onFileDragEnd:()=>{Ke.fileDragging=!1}}}function Il({openNext:e}){const t=K(!1),n=K(""),{sortedFiles:l,previewIdx:i,multiSelectedIdxs:u,stack:S,currLocation:M,spinning:F,previewing:y,stackViewEl:_,eventEmitter:I,props:h,deletedFiles:$}=he().toRefs(),w=Xe;Me("removeFiles",({paths:L,loc:o})=>{w(o)!==w(M.value)||!Se(S.value)||(L.forEach(v=>$.value.add(v)),L.filter(X).forEach(v=>$.value.add(v.replace(/\.\w+$/,".txt"))))}),Me("addFiles",({files:L,loc:o})=>{if(w(o)!==w(M.value))return;const m=Se(S.value);m&&m.files.unshift(...L)});const d=Te(),r=async(L,o,m)=>{i.value=m,W.fullscreenPreviewInitialUrl=ae(o);const v=u.value.indexOf(m);if(L.shiftKey){if(v!==-1)u.value.splice(v,1);else{u.value.push(m),u.value.sort((z,U)=>z-U);const N=u.value[0],q=u.value[u.value.length-1];u.value=Kt(N,q+1)}L.stopPropagation()}else L.ctrlKey||L.metaKey?(v!==-1?u.value.splice(v,1):u.value.push(m),L.stopPropagation()):await e(o)},P=async(L,o,m)=>{var R,ne,de;const v=ae(o),N=M.value,q={IIB_container_id:parent.IIB_container_id},z=()=>{let f=[];return u.value.includes(m)?f=u.value.map(p=>l.value[p]):f.push(o),f},U=async f=>{if(!F.value)try{F.value=!0,await Pt(o.fullpath),we.postMessage({...q,event:"click_hidden_button",btnEleId:"iib_hidden_img_update_trigger"}),await St(),we.postMessage({...q,event:"click_hidden_button",btnEleId:`iib_hidden_tab_${f}`})}catch(p){console.error(p),Z.error("发送图像失败,请携带console的错误消息找开发者")}finally{F.value=!1}},H=`${L.key}`;if(H.startsWith("toggle-tag-")){const f=+H.split("toggle-tag-")[1],{is_remove:p}=await _t({tag_id:f,img_path:o.fullpath}),a=(ne=(R=W.conf)==null?void 0:R.all_custom_tags.find(s=>s.id===f))==null?void 0:ne.name;await Ee.refreshTags([o.fullpath]),Z.success(T(p?"removedTagFromImage":"addedTagToImage",{tag:a}));return}else if(H.startsWith("batch-add-tag-")||H.startsWith("batch-remove-tag-")){const f=+H.split("-tag-")[1],p=H.includes("add")?"add":"remove",a=z().map(s=>s.fullpath);await wt({tag_id:f,img_paths:a,action:p}),await Ee.refreshTags(a),Z.success(T(p==="add"?"addCompleted":"removeCompleted"));return}else if(H.startsWith("copy-to-")){const f=H.split("copy-to-")[1],p=z(),a=p.map(s=>s.fullpath);await dt(a,f,!0),te.emit("addFiles",{files:p,loc:f}),Z.success(T("copySuccess"));return}else if(H.startsWith("move-to-")){const f=H.split("move-to-")[1],p=z(),a=p.map(s=>s.fullpath);await De(a,f,!0),te.emit("removeFiles",{paths:a,loc:M.value}),te.emit("addFiles",{files:p,loc:f}),Z.success(T("moveSuccess"));return}switch(L.key){case"previewInNewWindow":return window.open(v);case"copyFilePath":return me(o.fullpath);case"saveSelectedAsJson":return $t(z());case"openWithDefaultApp":return Lt(o.fullpath);case"download":{const f=z();Et(f.map(p=>ae(p,!0)));break}case"copyPreviewUrl":return me(parent.document.location.origin+v);case"rename":{let f=await Zt(o.fullpath);f=Xe(f);const p=Ee.tagMap;p.set(f,p.get(o.fullpath)??[]),p.delete(o.fullpath),o.fullpath=f,o.name=f.split(/[\\/]/).pop()??"";return}case"send2txt2img":return U("txt2img");case"send2img2img":return U("img2img");case"send2inpaint":return U("inpaint");case"send2extras":return U("extras");case"send2savedDir":{const f=W.quickMovePaths.find(s=>s.key==="outdir_save");if(!f)return Z.error(T("unknownSavedDir"));const p=Ot(f.dir,(de=W.conf)==null?void 0:de.sd_cwd),a=z();await De(a.map(s=>s.fullpath),p,!0),te.emit("removeFiles",{paths:a.map(s=>s.fullpath),loc:M.value}),te.emit("addFiles",{files:a,loc:p});break}case"send2controlnet-img2img":case"send2controlnet-txt2img":{const f=L.key.split("-")[1];we.postMessage({...q,event:"send_to_control_net",type:f,url:ae(o)});break}case"send2outpaint":{n.value=await d.pushAction(()=>Fe(o.fullpath)).res;const[f,p]=(n.value||"").split(` +`);we.postMessage({...q,event:"send_to_outpaint",url:ae(o),prompt:f,negPrompt:p.slice(17)});break}case"openWithWalkMode":{Je.set(N,S.value);const f=W.tabList[h.value.tabIdx],p={type:"local",key:ye(),path:o.fullpath,name:T("local"),stackKey:N,mode:"walk"};f.panes.push(p),f.key=p.key;break}case"openFileLocationInNewTab":case"openInNewTab":{const f=W.tabList[h.value.tabIdx],p={type:"local",key:ye(),path:L.key==="openInNewTab"?o.fullpath:kt(o.fullpath),name:T("local"),mode:"scanned-fixed"};f.panes.push(p),f.key=p.key;break}case"openOnTheRight":{Je.set(N,S.value);let f=W.tabList[h.value.tabIdx+1];f||(f={panes:[],key:"",id:ye()},W.tabList[h.value.tabIdx+1]=f);const p={type:"local",key:ye(),path:o.fullpath,name:T("local"),stackKey:N};f.panes.push(p),f.key=p.key;break}case"send2BatchDownload":{Jt.addFiles(z());break}case"viewGenInfo":{t.value=!0,n.value=await d.pushAction(()=>Fe(o.fullpath)).res;break}case"openWithLocalFileBrowser":{await bt(o.fullpath);break}case"deleteFiles":{const f=z(),p=async()=>{const a=f.map(s=>s.fullpath);if(await Rt(a),Z.success(T("deleteSuccess")),y.value){const s=ae(o)===W.fullscreenPreviewInitialUrl,ee=i.value===l.value.length-1;if((s||ee)&&(be(),await Ie(100),s&&l.value.length>1)){const J=i.value;Ie(0).then(()=>Cn(J,_.value))}}te.emit("removeFiles",{paths:a,loc:M.value})};if(f.length===1&&W.ignoredConfirmActions.deleteOneOnly)return p();await new Promise(a=>{pe.confirm({title:T("confirmDelete"),maskClosable:!0,width:"60vw",content:()=>c("div",null,[c("ol",{style:{maxHeight:"50vh",overflow:"auto"}},[f.map(s=>c("li",null,[s.fullpath.split(/[/\\]/).pop()]))]),c(ut,null,null),c(tn,{checked:W.ignoredConfirmActions.deleteOneOnly,"onUpdate:checked":s=>W.ignoredConfirmActions.deleteOneOnly=s},{default:()=>[T("deleteOneOnlySkipConfirm"),C(" ("),T("resetOnGlobalSettingsPage"),C(")")]})]),async onOk(){await p(),a()}})});break}}return{}},{isOutside:D}=st(_);return re("keydown",L=>{var m,v,N;const o=nn(L);if(y.value){o==="Esc"&&be();const q=(m=Object.entries(W.shortcut).find(z=>z[1]===o&&z[1]))==null?void 0:m[0];if(q){L.stopPropagation(),L.preventDefault();const z=i.value,U=l.value[z];switch(q){case"delete":return P({key:"deleteFiles"},U,z);case"download":return P({key:"download"},U,z);default:{const H=(v=/^toggle_tag_(.*)$/.exec(q))==null?void 0:v[1],R=(N=W.conf)==null?void 0:N.all_custom_tags.find(ne=>ne.name===H);return R?P({key:`toggle-tag-${R.id}`},U,z):void 0}}}}else!D.value&&["Ctrl + KeyA","Cmd + KeyA"].includes(o)&&(L.preventDefault(),L.stopPropagation(),I.value.emit("selectAll"))}),{onFileItemClick:r,onContextMenuClick:P,showGenInfo:t,imageGenInfo:n,q:d}}const $e=new Map,xl=()=>{const{useEventListen:e,sortedFiles:t,getViewableAreaFiles:n}=he().toRefs(),l=K(W.defaultChangeIndchecked),i=K(W.defaultSeedChangeChecked),u=async()=>{if(await Ie(100),!l.value)return;const F=n.value().filter(_=>X(_.fullpath)&&!_.gen_info_obj);if(!F.length)return;const y=await Ft(F.map(_=>_.fullpath).filter(_=>!$e.has(_)));F.forEach(_=>{const I=y[_.fullpath]||$e.get(_.fullpath)||"";$e.set(_.fullpath,I),_.gen_info_obj=xe(I),_.gen_info_raw=I})};e.value("viewableAreaFilesChange",u);const S=F=>{const y=t.value;return[F,i.value,y[F-1],y[F],y[F+1]]};function M(F,y,_,I){const h={diff:{},empty:!0,ownFile:"",otherFile:""};if(y+_<0||y+_>=t.value.length||t.value[y]==null||!("gen_info_obj"in t.value[y])||!("gen_info_obj"in t.value[y+_]))return h;const $=F,w=t.value[y+_].gen_info_obj;if(w==null)return h;const d=["hashes","resources"];h.diff={},h.ownFile=I.name,h.otherFile=t.value[y+_].name,h.empty=!1,i.value||d.push("seed");for(const r in $)if(!d.includes(r)){if(!(r in w)){h.diff[r]="+";continue}if($[r]!=w[r])if(r.includes("rompt")&&$[r]!=""&&w[r]!=""){const P=$[r].split(","),D=w[r].split(",");let L=0;for(const o in P)P[o]!=D[o]&&L++;h.diff[r]=L}else h.diff[r]=[$[r],w[r]]}return h}return{getGenDiff:M,changeIndchecked:l,seedChangeChecked:i,getRawGenParams:()=>u(),getGenDiffWatchDep:S}};function Mn(e,t,n,l){let i=0,u=0,S=typeof(l==null?void 0:l.width)=="number"?l.width:0,M=typeof(l==null?void 0:l.height)=="number"?l.height:0,F=typeof(l==null?void 0:l.left)=="number"?l.left:0,y=typeof(l==null?void 0:l.top)=="number"?l.top:0,_=!1;const I=o=>{o.stopPropagation(),o.preventDefault(),!(!e.value||!t.value)&&(i=o instanceof MouseEvent?o.clientX:o.touches[0].clientX,u=o instanceof MouseEvent?o.clientY:o.touches[0].clientY,S=e.value.offsetWidth,M=e.value.offsetHeight,t.value.offsetLeft,t.value.offsetTop,document.documentElement.addEventListener("mousemove",h),document.documentElement.addEventListener("touchmove",h),document.documentElement.addEventListener("mouseup",$),document.documentElement.addEventListener("touchend",$))},h=o=>{if(!e.value||!t.value)return;let m=S+((o instanceof MouseEvent?o.clientX:o.touches[0].clientX)-i),v=M+((o instanceof MouseEvent?o.clientY:o.touches[0].clientY)-u);e.value.offsetLeft+m>window.innerWidth&&(m=window.innerWidth-e.value.offsetLeft),e.value.offsetTop+v>window.innerHeight&&(v=window.innerHeight-e.value.offsetTop),e.value.style.width=`${m}px`,e.value.style.height=`${v}px`,l!=null&&l.onResize&&l.onResize(m,v)},$=()=>{document.documentElement.removeEventListener("mousemove",h),document.documentElement.removeEventListener("touchmove",h),document.documentElement.removeEventListener("mouseup",$),document.documentElement.removeEventListener("touchend",$)},w=o=>{o.stopPropagation(),o.preventDefault(),!(!e.value||!n.value)&&(_=!0,F=e.value.offsetLeft,y=e.value.offsetTop,i=o instanceof MouseEvent?o.clientX:o.touches[0].clientX,u=o instanceof MouseEvent?o.clientY:o.touches[0].clientY,document.documentElement.addEventListener("mousemove",d),document.documentElement.addEventListener("touchmove",d),document.documentElement.addEventListener("mouseup",r),document.documentElement.addEventListener("touchend",r))},d=o=>{if(!e.value||!n.value||!_)return;const m=F+((o instanceof MouseEvent?o.clientX:o.touches[0].clientX)-i),v=y+((o instanceof MouseEvent?o.clientY:o.touches[0].clientY)-u);m<0?e.value.style.left="0px":m+e.value.offsetWidth>window.innerWidth?e.value.style.left=`${window.innerWidth-e.value.offsetWidth}px`:e.value.style.left=`${m}px`,v<0?e.value.style.top="0px":v+e.value.offsetHeight>window.innerHeight?e.value.style.top=`${window.innerHeight-e.value.offsetHeight}px`:e.value.style.top=`${v}px`,l!=null&&l.onDrag&&l.onDrag(m,v)},r=()=>{_=!1,document.documentElement.removeEventListener("mousemove",d),document.documentElement.removeEventListener("touchmove",d),document.documentElement.removeEventListener("mouseup",r),document.documentElement.removeEventListener("touchend",r)},P=()=>{if(!e.value||!t.value)return;let o=e.value.offsetLeft,m=e.value.offsetTop,v=e.value.offsetWidth,N=e.value.offsetHeight;o+v>window.innerWidth&&(o=window.innerWidth-v,o<0&&(o=0,v=window.innerWidth)),m+N>window.innerHeight&&(m=window.innerHeight-N,m<0&&(m=0,N=window.innerHeight)),e.value.style.left=`${o}px`,e.value.style.top=`${m}px`,e.value.style.width=`${v}px`,e.value.style.height=`${N}px`},D=()=>{!e.value||!l||(typeof l.width=="number"&&(e.value.style.width=`${l.width}px`),typeof l.height=="number"&&(e.value.style.height=`${l.height}px`),typeof l.left=="number"&&(e.value.style.left=`${l.left}px`),typeof l.top=="number"&&(e.value.style.top=`${l.top}px`),P(),window.addEventListener("resize",P))},L=()=>{document.documentElement.removeEventListener("mousemove",h),document.documentElement.removeEventListener("touchmove",h),document.documentElement.removeEventListener("mouseup",$),document.documentElement.removeEventListener("touchend",$),document.documentElement.removeEventListener("mousemove",d),document.documentElement.removeEventListener("touchmove",d),document.documentElement.removeEventListener("mouseup",r),document.documentElement.removeEventListener("touchend",r),window.removeEventListener("resize",P)};return it(D),ot(L),ie(()=>l==null?void 0:l.disbaled,async o=>{await It(),o!==void 0&&(o?L():D())}),ie(()=>[e.value,t.value,n.value],([o,m,v])=>{o&&m&&(m.addEventListener("mousedown",I),m.addEventListener("touchstart",I)),o&&v&&(v.addEventListener("mousedown",w),v.addEventListener("touchstart",w))}),{handleResizeMouseDown:I,handleDragMouseDown:w}}let lt=null;const Dn=()=>{var F,y;const e=Ae(),t=Ce(Dt+"fullscreen_layout",{enable:!1,panelWidth:384,alwaysOn:!0}),n=xt(lt??((y=(F=e.conf)==null?void 0:F.app_fe_setting)==null?void 0:y.fullscreen_layout)??Pe(t.value)),l="--iib-lr-layout-info-panel-width",i=Q(()=>n.alwaysOn&&n.enable?n.panelWidth:0);ie(n,_=>{t.value=Pe(_),at(n,l,i),Tn(n),lt=n},{deep:!0}),it(()=>at(n,l,i));const{enable:u,panelWidth:S,alwaysOn:M}=Ct(n);return{state:n,isLeftRightLayout:u,panelwidtrhStyleVarName:l,lrLayoutInfoPanelWidth:S,lrMenuAlwaysOn:M}},Tn=ge(e=>Mt("fullscreen_layout",e),300),at=ge((e,t,n)=>{e.enable?(document.body.classList.add("fullscreen-lr-layout"),document.documentElement.style.setProperty(t,`${e.panelWidth}px`),document.documentElement.style.setProperty("--iib-lr-layout-container-offset",`${n.value}px`)):(document.documentElement.style.removeProperty(t),document.documentElement.style.removeProperty("--iib-lr-layout-container-offset"),document.body.classList.remove("fullscreen-lr-layout"))},300),ue=e=>(Xt("data-v-a9e90b6a"),e=e(),Yt(),e),An={key:0},zn={class:"container"},jn={class:"action-bar"},Nn=["title"],Wn=["title"],Bn=["title"],Hn=["src"],Un={key:0,class:"icon",style:{cursor:"pointer"}},Vn={key:2,"flex-placeholder":""},qn={key:3,class:"action-bar"},Gn={key:0,class:"gen-info"},Xn={class:"info-tags"},Yn={class:"name"},Kn={class:"value"},Jn={key:0,class:"tags-container"},Qn=["onClick"],Zn={class:"lr-layout-control"},Rn={class:"ctrl-item"},el={class:"ctrl-item"},tl={class:"ctrl-item"},nl=ue(()=>A("br",null,null,-1)),ll=ue(()=>A("h3",null,"Prompt",-1)),al=["innerHTML"],ol=ue(()=>A("br",null,null,-1)),sl=ue(()=>A("h3",null,"Negative Prompt",-1)),il=["innerHTML"],rl=ue(()=>A("br",null,null,-1)),cl=ue(()=>A("h3",null,"Params",-1)),ul={style:{"font-weight":"600","text-transform":"capitalize"}},dl=["onDblclick"],fl=["onDblclick"],vl=["title"],pl=rt({__name:"fullScreenContextMenu",props:{file:{},idx:{}},emits:["contextMenuClick"],setup(e,{emit:t}){const n=e;Tt(a=>({edcc2c4a:g(m)?0:"46px","95805fe4":g(o)+"px","5cbd8d52":`calc(100vw - ${g(o)}px)`}));const l=Ae(),i=At(),u=K(),S=Q(()=>i.tagMap.get(n.file.fullpath)??[]),M=K(""),F=Te(),y=K(""),_=Q(()=>y.value.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")),I=Q(()=>_.value.split(` +`)),h=Q(()=>xe(_.value)),$=Q(()=>{let a=xe(_.value);return delete a.prompt,delete a.negativePrompt,a});ie(()=>{var a;return(a=n==null?void 0:n.file)==null?void 0:a.fullpath},async a=>{a&&(F.tasks.forEach(s=>s.cancel()),F.pushAction(()=>Fe(a)).res.then(s=>{y.value=s}))},{immediate:!0});const w=Ce("iib@fullScreenContextMenu.prompt-tab","structedData"),d=K(),r=K(),P={left:100,top:100,width:512,height:384,expanded:!0},D=Ce("fullScreenContextMenu.vue-drag",P);D.value&&(D.value.left<0||D.value.top<0)&&(D.value={...P});const{isLeftRightLayout:L,lrLayoutInfoPanelWidth:o,lrMenuAlwaysOn:m}=Dn(),v=L;Mn(u,d,r,{disbaled:v,...D.value,onDrag:ge(function(a,s){D.value={...D.value,left:a,top:s}},300),onResize:ge(function(a,s){D.value={...D.value,width:a,height:s}},300)});const N=K(!1),{isOutside:q}=st(Q(()=>!v.value||m.value?null:N.value?u.value:Se(document.querySelectorAll(".iib-tab-edge-trigger"))));ie(q,sn(a=>{N.value=!a},300));function z(a){return a.parentNode}function U(a){if(!a)return"";const s=[],ee="BREAK",J=a.replace(/\sBREAK\s/g,","+ee+",").split(/[\n,]+/).map(G=>G.trim()).filter(G=>G);let j=!1;for(let G=0;GBREAK
');continue}const le=J[G];j||(j=le.includes("("));const fe=["tag"];j&&fe.push("has-parentheses"),le.length<32&&fe.push("short-tag"),s.push(`${le}`),j&&(j=!le.includes(")"))}return s.join(l.showCommaInInfoPanel?",":" ")}re("load",a=>{const s=a.target;s.className==="ant-image-preview-img"&&(M.value=`${s.naturalWidth} x ${s.naturalHeight}`)},{capture:!0});const H=Q(()=>{const a=[{name:T("fileName"),val:n.file.name},{name:T("fileSize"),val:n.file.size}];return M.value&&a.push({name:T("resolution"),val:M.value}),a}),R=()=>{const a="Negative prompt:",s=y.value.includes(a)?y.value.split(a)[0]:I.value[0]??"";me(Oe(s.trim()))},ne=()=>document.body.requestFullscreen(),de=a=>{me(typeof a=="object"?JSON.stringify(a,null,4):a)},f=a=>{a.key.startsWith("Arrow")?(a.stopPropagation(),a.preventDefault(),document.dispatchEvent(new KeyboardEvent("keydown",a))):a.key==="Escape"&&document.fullscreenElement&&document.exitFullscreen()};re("dblclick",a=>{var s;((s=a.target)==null?void 0:s.className)==="ant-image-preview-img"&&be()});const p=Q(()=>v.value||D.value.expanded);return(a,s)=>{var qe;const ee=an,J=se,j=Wt,G=Bt,le=Ht,fe=Ut,ft=se,Ue=ln,vt=en,pt=Vt,Ve=qt,mt=Gt;return k(),x("div",{ref_key:"el",ref:u,class:Ye(["full-screen-menu",{"unset-size":!g(D).expanded,lr:g(v),"always-on":g(m),"mouse-in":N.value}]),onWheelCapture:s[9]||(s[9]=Nt(()=>{},["stop"])),onKeydownCapture:f},[g(v)?(k(),x("div",An)):B("",!0),A("div",zn,[A("div",jn,[g(v)?B("",!0):(k(),x("div",{key:0,ref_key:"dragHandle",ref:r,class:"icon",style:{cursor:"grab"},title:g(T)("dragToMovePanel")},[c(g(mn))],8,Nn)),g(v)?B("",!0):(k(),x("div",{key:1,class:"icon",style:{cursor:"pointer"},onClick:s[0]||(s[0]=O=>g(D).expanded=!g(D).expanded),title:g(T)("clickToToggleMaximizeMinimize")},[p.value?(k(),oe(g(_n),{key:0})):(k(),oe(g(On),{key:1}))],8,Wn)),A("div",{style:{display:"flex","flex-direction":"column","align-items":"center",cursor:"grab"},class:"icon",title:g(T)("fullscreenview"),onClick:ne},[A("img",{src:g(In),style:{width:"21px",height:"21px","padding-bottom":"2px"},alt:""},null,8,Hn)],8,Bn),c(ee,{"get-popup-container":z},{overlay:b(()=>[c(Qt,{file:a.file,idx:a.idx,"selected-tag":S.value,onContextMenuClick:s[1]||(s[1]=(O,V,ke)=>t("contextMenuClick",O,V,ke))},null,8,["file","idx","selected-tag"])]),default:b(()=>[g(D).expanded?B("",!0):(k(),x("div",Un,[c(g(zt))]))]),_:1}),p.value?(k(),x("div",Vn)):B("",!0),p.value?(k(),x("div",qn,[c(ee,{trigger:["hover"],"get-popup-container":z},{overlay:b(()=>[c(fe,{onClick:s[2]||(s[2]=O=>t("contextMenuClick",O,a.file,a.idx))},{default:b(()=>{var O;return[((O=g(l).conf)==null?void 0:O.launch_mode)!=="server"?(k(),x(Y,{key:0},[c(j,{key:"send2txt2img"},{default:b(()=>[C(E(a.$t("sendToTxt2img")),1)]),_:1}),c(j,{key:"send2img2img"},{default:b(()=>[C(E(a.$t("sendToImg2img")),1)]),_:1}),c(j,{key:"send2inpaint"},{default:b(()=>[C(E(a.$t("sendToInpaint")),1)]),_:1}),c(j,{key:"send2extras"},{default:b(()=>[C(E(a.$t("sendToExtraFeatures")),1)]),_:1}),c(G,{key:"sendToThirdPartyExtension",title:a.$t("sendToThirdPartyExtension")},{default:b(()=>[c(j,{key:"send2controlnet-txt2img"},{default:b(()=>[C("ControlNet - "+E(a.$t("t2i")),1)]),_:1}),c(j,{key:"send2controlnet-img2img"},{default:b(()=>[C("ControlNet - "+E(a.$t("i2i")),1)]),_:1}),c(j,{key:"send2outpaint"},{default:b(()=>[C("openOutpaint")]),_:1})]),_:1},8,["title"])],64)):B("",!0),c(j,{key:"send2BatchDownload"},{default:b(()=>[C(E(a.$t("sendToBatchDownload")),1)]),_:1}),c(G,{key:"copy2target",title:a.$t("copyTo")},{default:b(()=>[(k(!0),x(Y,null,ve(g(l).quickMovePaths,V=>(k(),oe(j,{key:`copy-to-${V.dir}`},{default:b(()=>[C(E(V.zh),1)]),_:2},1024))),128))]),_:1},8,["title"]),c(G,{key:"move2target",title:a.$t("moveTo")},{default:b(()=>[(k(!0),x(Y,null,ve(g(l).quickMovePaths,V=>(k(),oe(j,{key:`move-to-${V.dir}`},{default:b(()=>[C(E(V.zh),1)]),_:2},1024))),128))]),_:1},8,["title"]),c(le),c(j,{key:"deleteFiles"},{default:b(()=>[C(E(a.$t("deleteSelected")),1)]),_:1}),c(j,{key:"previewInNewWindow"},{default:b(()=>[C(E(a.$t("previewInNewWindow")),1)]),_:1}),c(j,{key:"copyPreviewUrl"},{default:b(()=>[C(E(a.$t("copySourceFilePreviewLink")),1)]),_:1}),c(j,{key:"copyFilePath"},{default:b(()=>[C(E(a.$t("copyFilePath")),1)]),_:1})]}),_:1})]),default:b(()=>[c(J,null,{default:b(()=>[C(E(g(T)("openContextMenu")),1)]),_:1})]),_:1}),c(ft,{onClick:s[3]||(s[3]=O=>t("contextMenuClick",{key:"download"},n.file,n.idx))},{default:b(()=>[C(E(a.$t("download")),1)]),_:1}),y.value?(k(),oe(J,{key:0,onClick:s[4]||(s[4]=O=>g(me)(y.value))},{default:b(()=>[C(E(a.$t("copyPrompt")),1)]),_:1})):B("",!0),y.value?(k(),oe(J,{key:1,onClick:R},{default:b(()=>[C(E(a.$t("copyPositivePrompt")),1)]),_:1})):B("",!0)])):B("",!0)]),p.value?(k(),x("div",Gn,[A("div",Xn,[(k(!0),x(Y,null,ve(H.value,O=>(k(),x("span",{class:"info-tag",key:O.name},[A("span",Yn,E(O.name),1),A("span",Kn,E(O.val),1)]))),128))]),(qe=g(l).conf)!=null&&qe.all_custom_tags?(k(),x("div",Jn,[(k(!0),x(Y,null,ve(g(l).conf.all_custom_tags,O=>(k(),x("div",{class:Ye(["tag",{selected:S.value.some(V=>V.id===O.id)}]),onClick:V=>t("contextMenuClick",{key:`toggle-tag-${O.id}`},a.file,a.idx),key:O.id,style:jt({"--tag-color":g(i).getColor(O.name)})},E(O.name),15,Qn))),128))])):B("",!0),A("div",Zn,[A("div",Rn,[C(E(a.$t("experimentalLRLayout"))+": ",1),c(Ue,{checked:g(v),"onUpdate:checked":s[5]||(s[5]=O=>_e(v)?v.value=O:null),size:"small"},null,8,["checked"])]),g(v)?(k(),x(Y,{key:0},[A("div",el,[C(E(a.$t("width"))+": ",1),c(vt,{value:g(o),"onUpdate:value":s[6]||(s[6]=O=>_e(o)?o.value=O:null),style:{width:"64px"},step:16,min:128,max:1024},null,8,["value"])]),c(pt,{title:a.$t("alwaysOnTooltipInfo")},{default:b(()=>[A("div",tl,[C(E(a.$t("alwaysOn"))+": ",1),c(Ue,{checked:g(m),"onUpdate:checked":s[7]||(s[7]=O=>_e(m)?m.value=O:null),size:"small"},null,8,["checked"])])]),_:1},8,["title"])],64)):B("",!0)]),c(mt,{activeKey:g(w),"onUpdate:activeKey":s[8]||(s[8]=O=>_e(w)?w.value=O:null)},{default:b(()=>[c(Ve,{key:"structedData",tab:a.$t("structuredData")},{default:b(()=>[A("div",null,[h.value.prompt?(k(),x(Y,{key:0},[nl,ll,A("code",{innerHTML:U(h.value.prompt??"")},null,8,al)],64)):B("",!0),h.value.negativePrompt?(k(),x(Y,{key:1},[ol,sl,A("code",{innerHTML:U(h.value.negativePrompt??"")},null,8,il)],64)):B("",!0)]),Object.keys($.value).length?(k(),x(Y,{key:0},[rl,cl,A("table",null,[(k(!0),x(Y,null,ve($.value,(O,V)=>(k(),x("tr",{key:V,class:"gen-info-frag"},[A("td",ul,E(V),1),typeof O=="object"?(k(),x("td",{key:0,style:{cursor:"pointer"},onDblclick:ke=>de(O)},[A("code",null,E(O),1)],40,dl)):(k(),x("td",{key:1,style:{cursor:"pointer"},onDblclick:ke=>de(g(Oe)(O))},E(g(Oe)(O)),41,fl))]))),128))])],64)):B("",!0)]),_:1},8,["tab"]),c(Ve,{key:"sourceText",tab:a.$t("sourceText")},{default:b(()=>[A("code",null,E(y.value),1)]),_:1},8,["tab"])]),_:1},8,["activeKey"])])):B("",!0)]),g(D).expanded&&!g(v)?(k(),x("div",{key:1,class:"mouse-sensor",ref_key:"resizeHandle",ref:d,title:g(T)("dragToResizePanel")},[c(g(dn))],8,vl)):B("",!0)],34)}}});const Cl=ct(pl,[["__scopeId","data-v-a9e90b6a"]]),ml={key:0,class:"float-panel"},gl={key:0,class:"select-actions"},hl={key:1},yl=rt({__name:"MultiSelectKeep",props:{show:{type:Boolean}},emits:["selectAll","reverseSelect","clearAllSelected"],setup(e,{emit:t}){const n=Ae(),l=()=>{t("clearAllSelected"),n.keepMultiSelect=!1},i=()=>{n.keepMultiSelect=!0};return(u,S)=>{const M=se;return u.show?(k(),x("div",ml,[g(n).keepMultiSelect?(k(),x("div",gl,[c(M,{size:"small",onClick:S[0]||(S[0]=F=>t("selectAll"))},{default:b(()=>[C(E(u.$t("select-all")),1)]),_:1}),c(M,{size:"small",onClick:S[1]||(S[1]=F=>t("reverseSelect"))},{default:b(()=>[C(E(u.$t("rerverse-select")),1)]),_:1}),c(M,{size:"small",onClick:S[2]||(S[2]=F=>t("clearAllSelected"))},{default:b(()=>[C(E(u.$t("clear-all-selected")),1)]),_:1}),c(M,{size:"small",onClick:l},{default:b(()=>[C(E(u.$t("exit")),1)]),_:1})])):(k(),x("div",hl,[c(M,{size:"small",type:"primary",onClick:i},{default:b(()=>[C(E(u.$t("keep-multi-selected")),1)]),_:1})]))])):B("",!0)}}});const Ml=ct(yl,[["__scopeId","data-v-b04c3508"]]);export{$l as L,Ml as M,Pl as R,Fl as a,Il as b,Sl as c,xl as d,Cl as f,re as u}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/SubstrSearch-89fa6d6e.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/SubstrSearch-89fa6d6e.css new file mode 100644 index 0000000000000000000000000000000000000000..7724a97c06bdefdd23657dce27d741741cd4c8eb --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/SubstrSearch-89fa6d6e.css @@ -0,0 +1 @@ +[data-v-8a9796fb] .float-panel{position:fixed}.regex-icon[data-v-8a9796fb]{user-select:none;padding:4px;margin:0 4px;cursor:pointer;border:1px solid var(--zp-border);border-radius:4px}.regex-icon img[data-v-8a9796fb]{height:1.5em}.regex-icon[data-v-8a9796fb]:hover{background:var(--zp-border)}.regex-icon.selected[data-v-8a9796fb]{background:var(--primary-color-1);border:1px solid var(--primary-color)}.search-bar[data-v-8a9796fb]{padding:8px 8px 0;display:flex}.search-bar.last[data-v-8a9796fb]{padding-bottom:8px}.search-bar .form-name[data-v-8a9796fb]{flex-shrink:0;padding:4px 8px}.search-bar .actions>*[data-v-8a9796fb]{margin-right:4px}.container[data-v-8a9796fb]{background:var(--zp-secondary-background);position:relative}.container .file-list[data-v-8a9796fb]{list-style:none;padding:8px;height:100%;overflow:auto;height:var(--pane-max-height);width:100%} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/SubstrSearch-8ed8bfa5.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/SubstrSearch-8ed8bfa5.js new file mode 100644 index 0000000000000000000000000000000000000000..1e23c185879442cebb4b601a68da7cab176a7995 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/SubstrSearch-8ed8bfa5.js @@ -0,0 +1 @@ +import{d as $e,r as y,o as Ae,bO as W,m as Me,B as ze,ax as De,y as Fe,z as Ue,C as Q,bP as Be,$ as Ve,S as c,T as I,c as s,a1 as t,a2 as e,U as d,V as i,W as o,a0 as g,Y as f,a3 as X,bV as Ee,ae as F,a4 as Y,J as Te,ad as He,X as Ke,R as Z,ah as Ge,ai as j,bS as Oe,ag as Je,aN as Le,aO as Ne,bW as Pe,bU as qe,Z as We}from"./index-93a218ca.js";import{S as Qe}from"./index-c7fe1b25.js";/* empty css */import"./index-67a7e5e1.js";import{c as Xe,d as Ye,F as Ze}from"./FileItem-9a94f7dd.js";import{M as je,L as et,R as tt,f as st}from"./MultiSelectKeep-e20b5f46.js";import{c as at,u as lt}from"./hook-983826b5.js";import{f as R,H as ee,_ as nt,a as ot}from"./searchHistory-7d99798b.js";import"./functionalCallableComp-685da399.js";/* empty css */import"./index-f60e0de3.js";import"./shortcut-ff09b8ec.js";import"./index-4596f42d.js";const it="/infinite_image_browsing/fe-static/assets/regex-a447f877.svg",U=b=>(Le("data-v-8a9796fb"),b=b(),Ne(),b),rt={style:{"padding-right":"16px"}},dt=U(()=>d("div",null,null,-1)),ut=["src"],ct={class:"search-bar"},pt={class:"form-name"},ft={class:"search-bar last actions"},vt={class:"hint"},gt={key:0,style:{margin:"64px 16px 32px",padding:"8px",background:"var(--zp-secondary-variant-background)","border-radius":"16px"}},mt={style:{margin:"16px 32px 16px"}},_t={style:{"padding-right":"16px"}},ht=U(()=>d("div",null,null,-1)),kt=U(()=>d("div",{style:{padding:"16px 0 512px"}},null,-1)),yt={key:2,class:"preview-switch"},bt=$e({__name:"SubstrSearch",props:{tabIdx:{},paneIdx:{},searchScope:{}},setup(b){const $=b,m=y(!1),_=y(""),h=y($.searchScope??""),w=y(!1),B=y(0),A=at(a=>{const l={cursor:a,regexp:m.value?_.value:"",surstr:m.value?"":_.value,folder_paths:(h.value??"").split(/,|\n/).map(r=>r.trim()).filter(r=>r)};return Pe(l)}),{queue:k,images:p,onContextMenuClickU:V,stackViewEl:te,previewIdx:S,previewing:E,onPreviewVisibleChange:se,previewImgMove:T,canPreview:H,itemSize:K,gridItems:ae,showGenInfo:x,imageGenInfo:G,q:le,multiSelectedIdxs:M,onFileItemClick:ne,scroller:O,showMenuIdx:z,onFileDragStart:oe,onFileDragEnd:ie,cellWidth:re,onScroll:J,saveAllFileAsJson:de,saveLoadedFileAsJson:ue,props:ce,changeIndchecked:pe,seedChangeChecked:fe,getGenDiff:ve,getGenDiffWatchDep:ge}=lt(A),u=y();Ae(async()=>{u.value=await W(),u.value.img_count&&u.value.expired&&await L(),$.searchScope&&await C()}),Me(()=>$,async a=>{ce.value=a},{deep:!0,immediate:!0});const L=ze(()=>k.pushAction(async()=>(await qe(),u.value=await W(),u.value)).res),N=a=>{_.value=a.substr,h.value=a.folder_paths_str,m.value=a.isRegex,w.value=!1,C()},C=async()=>{B.value++,R.value.add({substr:_.value,folder_paths_str:h.value,isRegex:m.value}),await A.reset({refetch:!0}),await De(),J(),O.value.scrollToItem(0),p.value.length||Fe.info(Ue("fuzzy-search-noResults"))};Q("returnToIIB",async()=>{const a=await k.pushAction(Be).res;u.value.expired=a.expired}),Q("searchIndexExpired",()=>u.value&&(u.value.expired=!0));const me=()=>{m.value=!m.value},_e=Ve(),{onClearAllSelected:he,onSelectAll:ke,onReverseSelect:ye}=Xe();return(a,l)=>{const r=nt,v=ot,be=Z,we=Ge,P=j,Se=Oe,D=j,xe=Je,Ce=Z,Ie=Qe;return c(),I(Ke,null,[s(be,{visible:w.value,"onUpdate:visible":l[0]||(l[0]=n=>w.value=n),width:"70vw","mask-closable":"",onOk:l[1]||(l[1]=n=>w.value=!1)},{default:t(()=>[s(ee,{records:e(R),onReuseRecord:N},{default:t(({record:n})=>[d("div",rt,[s(v,null,{default:t(()=>[s(r,{span:4},{default:t(()=>[i(o(a.$t("historyRecordsSubstr"))+":",1)]),_:1}),s(r,{span:20},{default:t(()=>[i(o(n.substr),1)]),_:2},1024)]),_:2},1024),n.folder_paths_str?(c(),g(v,{key:0},{default:t(()=>[s(r,{span:4},{default:t(()=>[i(o(a.$t("searchScope"))+":",1)]),_:1}),s(r,{span:20},{default:t(()=>[i(o(n.folder_paths_str),1)]),_:2},1024)]),_:2},1024)):f("",!0),s(v,null,{default:t(()=>[s(r,{span:4},{default:t(()=>[i(o(a.$t("historyRecordsisRegex"))+":",1)]),_:1}),s(r,{span:20},{default:t(()=>[i(o(n.isRegex),1)]),_:2},1024)]),_:2},1024),s(v,null,{default:t(()=>[s(r,{span:4},{default:t(()=>[i(o(a.$t("time"))+":",1)]),_:1}),s(r,{span:20},{default:t(()=>[i(o(n.time),1)]),_:2},1024)]),_:2},1024),dt])]),_:1},8,["records"])]),_:1},8,["visible"]),d("div",{class:"container",ref_key:"stackViewEl",ref:te},[s(je,{show:!!e(M).length||e(_e).keepMultiSelect,onClearAllSelected:e(he),onSelectAll:e(ke),onReverseSelect:e(ye)},null,8,["show","onClearAllSelected","onSelectAll","onReverseSelect"]),u.value?(c(),I("div",{key:0,class:"search-bar",onKeydown:l[4]||(l[4]=X(()=>{},["stop"]))},[s(we,{value:_.value,"onUpdate:value":l[2]||(l[2]=n=>_.value=n),placeholder:a.$t("fuzzy-search-placeholder")+" "+a.$t("regexSearchEnabledHint"),disabled:!e(k).isIdle,onKeydown:Ee(C,["enter"]),"allow-clear":""},null,8,["value","placeholder","disabled","onKeydown"]),d("div",{class:F(["regex-icon",{selected:m.value}]),onKeydown:l[3]||(l[3]=X(()=>{},["stop"])),onClick:me,title:"Use Regular Expression"},[d("img",{src:e(it)},null,8,ut)],34),u.value.expired||!u.value.img_count?(c(),g(P,{key:0,onClick:e(L),loading:!e(k).isIdle,type:"primary"},{default:t(()=>[i(o(u.value.img_count===0?a.$t("generateIndexHint"):a.$t("UpdateIndex")),1)]),_:1},8,["onClick","loading"])):(c(),g(P,{key:1,type:"primary",onClick:C,loading:!e(k).isIdle||e(A).loading,disabled:!_.value&&!h.value},{default:t(()=>[i(o(a.$t("search")),1)]),_:1},8,["loading","disabled"]))],32)):f("",!0),d("div",ct,[d("div",pt,o(a.$t("searchScope")),1),s(Se,{"auto-size":{maxRows:8},value:h.value,"onUpdate:value":l[5]||(l[5]=n=>h.value=n),placeholder:a.$t("specifiedSearchFolder")},null,8,["value","placeholder"])]),d("div",ft,[e(p).length?(c(),g(D,{key:0,onClick:e(ue)},{default:t(()=>[i(o(a.$t("saveLoadedImageAsJson")),1)]),_:1},8,["onClick"])):f("",!0),e(p).length?(c(),g(D,{key:1,onClick:e(de)},{default:t(()=>[i(o(a.$t("saveAllAsJson")),1)]),_:1},8,["onClick"])):f("",!0),s(D,{onClick:l[6]||(l[6]=n=>w.value=!0)},{default:t(()=>[i(o(a.$t("history")),1)]),_:1})]),s(Ie,{size:"large",spinning:!e(k).isIdle},{default:t(()=>[s(Ce,{visible:e(x),"onUpdate:visible":l[8]||(l[8]=n=>Y(x)?x.value=n:null),width:"70vw","mask-closable":"",onOk:l[9]||(l[9]=n=>x.value=!1)},{cancelText:t(()=>[]),default:t(()=>[s(xe,{active:"",loading:!e(le).isIdle},{default:t(()=>[d("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:l[7]||(l[7]=n=>e(Te)(e(G)))},[d("div",vt,o(a.$t("doubleClickToCopy")),1),i(" "+o(e(G)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),B.value===0&&!e(p).length&&e(R).getRecords().length?(c(),I("div",gt,[d("h2",mt,o(a.$t("restoreFromHistory")),1),s(ee,{records:e(R),onReuseRecord:N},{default:t(({record:n})=>[d("div",_t,[s(v,null,{default:t(()=>[s(r,{span:4},{default:t(()=>[i(o(a.$t("historyRecordsSubstr"))+":",1)]),_:1}),s(r,{span:20},{default:t(()=>[i(o(n.substr),1)]),_:2},1024)]),_:2},1024),n.folder_paths_str?(c(),g(v,{key:0},{default:t(()=>[s(r,{span:4},{default:t(()=>[i(o(a.$t("searchScope"))+":",1)]),_:1}),s(r,{span:20},{default:t(()=>[i(o(n.folder_paths_str),1)]),_:2},1024)]),_:2},1024)):f("",!0),s(v,null,{default:t(()=>[s(r,{span:4},{default:t(()=>[i(o(a.$t("historyRecordsisRegex"))+":",1)]),_:1}),s(r,{span:20},{default:t(()=>[i(o(n.isRegex),1)]),_:2},1024)]),_:2},1024),s(v,null,{default:t(()=>[s(r,{span:4},{default:t(()=>[i(o(a.$t("time"))+":",1)]),_:1}),s(r,{span:20},{default:t(()=>[i(o(n.time),1)]),_:2},1024)]),_:2},1024),ht])]),_:1},8,["records"])])):f("",!0),e(p)?(c(),g(e(Ye),{key:1,ref_key:"scroller",ref:O,class:"file-list",items:e(p),"item-size":e(K).first,"key-field":"fullpath","item-secondary-size":e(K).second,gridItems:e(ae),onScroll:e(J)},{after:t(()=>[kt]),default:t(({item:n,index:q})=>[s(Ze,{idx:q,file:n,"show-menu-idx":e(z),"onUpdate:showMenuIdx":l[10]||(l[10]=Re=>Y(z)?z.value=Re:null),onFileItemClick:e(ne),"full-screen-preview-image-url":e(p)[e(S)]?e(He)(e(p)[e(S)]):"","cell-width":e(re),selected:e(M).includes(q),onContextMenuClick:e(V),onDragstart:e(oe),onDragend:e(ie),"enable-change-indicator":e(pe),"seed-change-checked":e(fe),"get-gen-diff":e(ve),"get-gen-diff-watch-dep":e(ge),"is-selected-mutil-files":e(M).length>1,onPreviewVisibleChange:e(se)},null,8,["idx","file","show-menu-idx","onFileItemClick","full-screen-preview-image-url","cell-width","selected","onContextMenuClick","onDragstart","onDragend","enable-change-indicator","seed-change-checked","get-gen-diff","get-gen-diff-watch-dep","is-selected-mutil-files","onPreviewVisibleChange"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"])):f("",!0),e(E)?(c(),I("div",yt,[s(e(et),{onClick:l[11]||(l[11]=n=>e(T)("prev")),class:F({disable:!e(H)("prev")})},null,8,["class"]),s(e(tt),{onClick:l[12]||(l[12]=n=>e(T)("next")),class:F({disable:!e(H)("next")})},null,8,["class"])])):f("",!0)]),_:1},8,["spinning"]),e(E)&&e(p)&&e(p)[e(S)]?(c(),g(st,{key:1,file:e(p)[e(S)],idx:e(S),onContextMenuClick:e(V)},null,8,["file","idx","onContextMenuClick"])):f("",!0)],512)],64)}}});const Bt=We(bt,[["__scopeId","data-v-8a9796fb"]]);export{Bt as default}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/TagSearch-0a65f26b.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/TagSearch-0a65f26b.js new file mode 100644 index 0000000000000000000000000000000000000000..f7ee2f77f84725b96e9aaea943e107f0a7022d7a --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/TagSearch-0a65f26b.js @@ -0,0 +1 @@ +import{P as z,aw as Ke,d as ne,bB as Ae,b4 as De,r as F,bE as Fe,m as we,u as ke,E as ee,al as oe,h as k,c as r,a as ae,bF as Ue,b as je,f as Le,bG as ze,an as fe,bH as He,aX as Ve,i as Ge,bc as qe,bI as Xe,ar as Qe,as as We,aq as Ye,A as Ze,a_ as Je,aY as ea,bJ as aa,aZ as ta,bK as na,S as b,T,U as C,W as y,a0 as L,a2 as K,bL as la,Y as D,a3 as ce,bM as oa,ae as xe,Z as Se,$ as sa,bN as ia,ak as ra,n as da,x as ca,O as ua,o as va,bO as ge,q as pa,ax as fa,C as me,B as ga,bP as ma,a1 as h,V as A,X as J,aa as re,a6 as _e,aK as _a,bQ as ha,y as he,z as de,R as ye,bR as ya,ai as be,bS as ba,ah as Ca,bT as Ia,aN as $a,aO as Aa,bU as wa}from"./index-93a218ca.js";import{S as ka}from"./index-c7fe1b25.js";/* empty css *//* empty css */import{t as Ce,_ as xa,a as Sa,H as Oa}from"./searchHistory-7d99798b.js";import"./index-67a7e5e1.js";var Pa=function(){return{prefixCls:String,activeKey:{type:[Array,Number,String]},defaultActiveKey:{type:[Array,Number,String]},accordion:{type:Boolean,default:void 0},destroyInactivePanel:{type:Boolean,default:void 0},bordered:{type:Boolean,default:void 0},expandIcon:Function,openAnimation:z.object,expandIconPosition:z.oneOf(Ke("left","right")),collapsible:{type:String},ghost:{type:Boolean,default:void 0},onChange:Function,"onUpdate:activeKey":Function}},Oe=function(){return{openAnimation:z.object,prefixCls:String,header:z.any,headerClass:String,showArrow:{type:Boolean,default:void 0},isActive:{type:Boolean,default:void 0},destroyInactivePanel:{type:Boolean,default:void 0},disabled:{type:Boolean,default:void 0},accordion:{type:Boolean,default:void 0},forceRender:{type:Boolean,default:void 0},expandIcon:Function,extra:z.any,panelKey:z.oneOfType([z.string,z.number]),collapsible:{type:String},role:String,onItemClick:{type:Function}}};function Ie(o){var a=o;if(!Array.isArray(a)){var t=je(a);a=t==="number"||t==="string"?[a]:[]}return a.map(function(l){return String(l)})}const te=ne({compatConfig:{MODE:3},name:"ACollapse",inheritAttrs:!1,props:Ae(Pa(),{accordion:!1,destroyInactivePanel:!1,bordered:!0,openAnimation:De("ant-motion-collapse",!1),expandIconPosition:"left"}),slots:["expandIcon"],setup:function(a,t){var l=t.attrs,c=t.slots,s=t.emit,f=F(Ie(Fe([a.activeKey,a.defaultActiveKey])));we(function(){return a.activeKey},function(){f.value=Ie(a.activeKey)},{deep:!0});var d=ke("collapse",a),g=d.prefixCls,B=d.direction,N=ee(function(){var p=a.expandIconPosition;return p!==void 0?p:B.value==="rtl"?"right":"left"}),E=function(v){var m=a.expandIcon,w=m===void 0?c.expandIcon:m,I=w?w(v):r(He,{rotate:v.isActive?90:void 0},null);return r("div",null,[Ve(Array.isArray(w)?I[0]:I)?fe(I,{class:"".concat(g.value,"-arrow")},!1):I])},U=function(v){a.activeKey===void 0&&(f.value=v);var m=a.accordion?v[0]:v;s("update:activeKey",m),s("change",m)},O=function(v){var m=f.value;if(a.accordion)m=m[0]===v?[]:[v];else{m=Ge(m);var w=m.indexOf(v),I=w>-1;I?m.splice(w,1):m.push(v)}U(m)},H=function(v,m){var w,I,j;if(!ze(v)){var R=f.value,V=a.accordion,W=a.destroyInactivePanel,G=a.collapsible,Y=a.openAnimation,P=String((w=v.key)!==null&&w!==void 0?w:m),M=v.props||{},x=M.header,q=x===void 0?(I=v.children)===null||I===void 0||(j=I.header)===null||j===void 0?void 0:j.call(I):x,e=M.headerClass,n=M.collapsible,u=M.disabled,_=!1;V?_=R[0]===P:_=R.indexOf(P)>-1;var S=n??G;(u||u==="")&&(S="disabled");var Z={key:P,panelKey:P,header:q,headerClass:e,isActive:_,prefixCls:g.value,destroyInactivePanel:W,openAnimation:Y,accordion:V,onItemClick:S==="disabled"?null:O,expandIcon:E,collapsible:S};return fe(v,Z)}},Q=function(){var v;return Le((v=c.default)===null||v===void 0?void 0:v.call(c)).map(H)};return function(){var p,v=a.accordion,m=a.bordered,w=a.ghost,I=oe((p={},k(p,g.value,!0),k(p,"".concat(g.value,"-borderless"),!m),k(p,"".concat(g.value,"-icon-position-").concat(N.value),!0),k(p,"".concat(g.value,"-rtl"),B.value==="rtl"),k(p,"".concat(g.value,"-ghost"),!!w),k(p,l.class,!!l.class),p));return r("div",ae(ae({class:I},Ue(l)),{},{style:l.style,role:v?"tablist":null}),[Q()])}}}),Ta=ne({compatConfig:{MODE:3},name:"PanelContent",props:Oe(),setup:function(a,t){var l=t.slots,c=F(!1);return qe(function(){(a.isActive||a.forceRender)&&(c.value=!0)}),function(){var s,f;if(!c.value)return null;var d=a.prefixCls,g=a.isActive,B=a.role;return r("div",{ref:F,class:oe("".concat(d,"-content"),(s={},k(s,"".concat(d,"-content-active"),g),k(s,"".concat(d,"-content-inactive"),!g),s)),role:B},[r("div",{class:"".concat(d,"-content-box")},[(f=l.default)===null||f===void 0?void 0:f.call(l)])])}}}),se=ne({compatConfig:{MODE:3},name:"ACollapsePanel",inheritAttrs:!1,props:Ae(Oe(),{showArrow:!0,isActive:!1,onItemClick:function(){},headerClass:"",forceRender:!1}),slots:["expandIcon","extra","header"],setup:function(a,t){var l=t.slots,c=t.emit,s=t.attrs;Xe(a.disabled===void 0,"Collapse.Panel",'`disabled` is deprecated. Please use `collapsible="disabled"` instead.');var f=ke("collapse",a),d=f.prefixCls,g=function(){c("itemClick",a.panelKey)},B=function(E){(E.key==="Enter"||E.keyCode===13||E.which===13)&&g()};return function(){var N,E,U,O,H=a.header,Q=H===void 0?(N=l.header)===null||N===void 0?void 0:N.call(l):H,p=a.headerClass,v=a.isActive,m=a.showArrow,w=a.destroyInactivePanel,I=a.accordion,j=a.forceRender,R=a.openAnimation,V=a.expandIcon,W=V===void 0?l.expandIcon:V,G=a.extra,Y=G===void 0?(E=l.extra)===null||E===void 0?void 0:E.call(l):G,P=a.collapsible,M=P==="disabled",x=d.value,q=oe("".concat(x,"-header"),(U={},k(U,p,p),k(U,"".concat(x,"-header-collapsible-only"),P==="header"),U)),e=oe((O={},k(O,"".concat(x,"-item"),!0),k(O,"".concat(x,"-item-active"),v),k(O,"".concat(x,"-item-disabled"),M),k(O,"".concat(x,"-no-arrow"),!m),k(O,"".concat(s.class),!!s.class),O)),n=r("i",{class:"arrow"},null);m&&typeof W=="function"&&(n=W(a));var u=Qe(r(Ta,{prefixCls:x,isActive:v,forceRender:j,role:I?"tabpanel":null},{default:l.default}),[[We,v]]),_=ae({appear:!1,css:!1},R);return r("div",ae(ae({},s),{},{class:e}),[r("div",{class:q,onClick:function(){return P!=="header"&&g()},role:I?"tab":"button",tabindex:M?-1:0,"aria-expanded":v,onKeypress:B},[m&&n,P==="header"?r("span",{onClick:g,class:"".concat(x,"-header-text")},[Q]):Q,Y&&r("div",{class:"".concat(x,"-extra")},[Y])]),r(Ye,_,{default:function(){return[!w||v?u:null]}})])}}});te.Panel=se;te.install=function(o){return o.component(te.name,te),o.component(se.name,se),o};var Na={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91.5c1.9 0 3.8-.7 5.2-2L869 536.2a32.07 32.07 0 000-48.4z"}}]},name:"arrow-right",theme:"outlined"};const Ea=Na;function $e(o){for(var a=1;a(l?`[${t.type}] `:"")+(t.display_name?`${t.display_name} : ${t.name}`:t.name);return(t,l)=>(b(),T("div",Wa,[C("div",Ya,[C("div",{onClick:l[0]||(l[0]=c=>t.$emit("toggleAnd"))},y(t.$t("exactMatch")),1),C("div",{onClick:l[1]||(l[1]=c=>t.$emit("toggleOr"))},y(t.$t("anyMatch")),1),C("div",{onClick:l[2]||(l[2]=c=>t.$emit("toggleNot"))},y(t.$t("exclude")),1)]),C("li",{class:xe(["tag",{selected:t.selected}]),title:a(t.tag),onClick:l[4]||(l[4]=c=>t.$emit("click"))},[t.selected?(b(),L(K(la),{key:0})):D("",!0),C("div",Ja,y(a(t.tag)),1),t.name==="custom"&&t.idx!==0?(b(),T("span",{key:1,class:"remove",onClickCapture:l[3]||(l[3]=ce(c=>t.$emit("remove"),["stop"]))},[r(K(oa))],32)):D("",!0)],10,Za)]))}});const at=Se(et,[["__scopeId","data-v-3aaf060d"]]),Pe=o=>($a("data-v-494e8b82"),o=o(),Aa(),o),tt={class:"container"},nt={style:{"padding-right":"16px"}},lt=Pe(()=>C("div",null,null,-1)),ot={class:"search-bar"},st={class:"form-name"},it={class:"search-bar"},rt={class:"form-name"},dt=Pe(()=>C("div",{style:{"padding-left":"4px"}},null,-1)),ct={class:"search-bar"},ut={class:"form-name"},vt={class:"search-bar"},pt={class:"form-name"},ft={key:0,class:"generate-idx-hint"},gt={class:"list-container"},mt={key:0,class:"tag-list"},_t=["onClick"],ht={key:1},yt={key:2,class:"spin-container"},bt=ne({__name:"TagSearch",props:{tabIdx:{},paneIdx:{},searchScope:{}},setup(o){const a=o,t=sa(),l=ia(),c=ee(()=>!l.isIdle),s=F(),f=F(!1),d=F({and_tags:[],or_tags:[],not_tags:[],folder_paths_str:a.searchScope}),g=ee(()=>s.value?s.value.tags.slice().sort((e,n)=>n.count-e.count):[]),B=["custom","Source Identifier","Model","Media Type","lora","lyco","pos","size","Sampler","Postprocess upscaler","Postprocess upscale by"].reduce((e,n,u)=>(e[n]=u,e),{}),N=ee(()=>Object.entries(Qa(g.value,e=>e.type)).sort((e,n)=>{const u=B[e[0]]!==void 0?B[e[0]]:Number.MAX_SAFE_INTEGER,_=B[n[0]]!==void 0?B[n[0]]:Number.MAX_SAFE_INTEGER;return u-_})),E=ra(new Map),U=e=>E.get(e)??512,O=F({}),H=F({});we(O,da(e=>{H.value=ca(e)},300),{deep:!0});const Q=ua(),p=F(N.value.map(e=>e[0]));va(async()=>{console.log(new Date().toLocaleString()),s.value=await ge(),await pa(20),console.log(new Date().toLocaleString()),p.value=N.value.map(e=>e[0]),fa(()=>{console.log(new Date().toLocaleString())}),s.value.img_count&&s.value.expired&&await v(),a.searchScope&&m()}),me("searchIndexExpired",()=>s.value&&(s.value.expired=!0));const v=ga(()=>l.pushAction(async()=>(await wa(),s.value=await ge(),p.value=N.value.map(e=>e[0]),s.value)).res),m=()=>{Ce.value.add(d.value),t.openTagSearchMatchedImageGridInRight(a.tabIdx,Q,d.value)},w=e=>{d.value=e,f.value=!1,m()};me("returnToIIB",async()=>{const e=await l.pushAction(ma).res;s.value.expired=e.expired});const I=(e,n=!1)=>(n?`[${e.type}] `:"")+(e.display_name?`${e.display_name} : ${e.name}`:e.name),j=F(!1),R=F(""),V=async()=>{var n,u,_;if(!R.value){j.value=!1;return}const e=await l.pushAction(()=>ha({tag_name:R.value})).res;e.type!=="custom"&&he.error(de("existInOtherType")),(n=s.value)!=null&&n.tags.find(S=>S.id===e.id)?he.error(de("alreadyExists")):((u=s.value)==null||u.tags.push(e),(_=t.conf)==null||_.all_custom_tags.push(e)),R.value="",j.value=!1},W=e=>{ye.confirm({title:de("confirmDelete"),async onOk(){var u,_,S,Z;await ya({tag_id:e});const n=((u=s.value)==null?void 0:u.tags.findIndex(X=>X.id===e))??-1;(_=s.value)==null||_.tags.splice(n,1),(Z=t.conf)==null||Z.all_custom_tags.splice((S=t.conf)==null?void 0:S.all_custom_tags.findIndex(X=>X.id===e),1)}})},G=ee(()=>new Set([d.value.and_tags,d.value.or_tags,d.value.not_tags].flat())),Y=e=>{G.value.has(e.id)?(d.value.and_tags=d.value.and_tags.filter(n=>n!==e.id),d.value.or_tags=d.value.or_tags.filter(n=>n!==e.id),d.value.not_tags=d.value.not_tags.filter(n=>n!==e.id)):d.value.and_tags.push(e.id)},P={value:e=>e.id,text:I,optionText:e=>I(e,!0)},M=(e,n)=>{const u=n.indexOf(e);u===-1?n.push(e):n.splice(u,1)},x=(e,n)=>{const u=U(n);let _=H.value[n];return _&&(_=_.trim(),e=e.filter(S=>I(S).toLowerCase().includes(_.toLowerCase()))),e.slice(0,u)},q=e=>e.map(n=>{var u;return(u=g.value.find(_=>_.id===n))==null?void 0:u.name}).join(", ");return(e,n)=>{const u=xa,_=Sa,S=Oa,Z=ye,X=be,Te=ba,ve=Ca,pe=be,Ne=Ia,Ee=se,Be=te,Re=ka;return b(),T("div",tt,[r(Z,{visible:f.value,"onUpdate:visible":n[0]||(n[0]=i=>f.value=i),width:"70vw","mask-closable":"",onOk:n[1]||(n[1]=i=>f.value=!1)},{default:h(()=>[r(S,{records:K(Ce),onReuseRecord:w},{default:h(({record:i})=>[C("div",nt,[i.and_tags.length?(b(),L(_,{key:0},{default:h(()=>[r(u,{span:4},{default:h(()=>[A(y(e.$t("exactMatch"))+":",1)]),_:1}),r(u,{span:20},{default:h(()=>[A(y(q(i.and_tags)),1)]),_:2},1024)]),_:2},1024)):D("",!0),i.or_tags.length?(b(),L(_,{key:1},{default:h(()=>[r(u,{span:4},{default:h(()=>[A(y(e.$t("anyMatch"))+":",1)]),_:1}),r(u,{span:20},{default:h(()=>[A(y(q(i.or_tags)),1)]),_:2},1024)]),_:2},1024)):D("",!0),i.not_tags.length?(b(),L(_,{key:2},{default:h(()=>[r(u,{span:4},{default:h(()=>[A(y(e.$t("exclude"))+":",1)]),_:1}),r(u,{span:20},{default:h(()=>[A(y(q(i.not_tags)),1)]),_:2},1024)]),_:2},1024)):D("",!0),i.folder_paths_str?(b(),L(_,{key:3},{default:h(()=>[r(u,{span:4},{default:h(()=>[A(y(e.$t("searchScope"))+":",1)]),_:1}),r(u,{span:20},{default:h(()=>[A(y(i.folder_paths_str),1)]),_:2},1024)]),_:2},1024)):D("",!0),r(_,null,{default:h(()=>[r(u,{span:4},{default:h(()=>[A(y(e.$t("time"))+":",1)]),_:1}),r(u,{span:20},{default:h(()=>[A(y(i.time),1)]),_:2},1024)]),_:2},1024),lt])]),_:1},8,["records"])]),_:1},8,["visible"]),D("",!0),s.value?(b(),T(J,{key:1},[C("div",null,[C("div",ot,[C("div",st,y(e.$t("exactMatch")),1),r(K(re),{conv:P,mode:"multiple",style:{width:"100%"},options:g.value,value:d.value.and_tags,"onUpdate:value":n[2]||(n[2]=i=>d.value.and_tags=i),disabled:!g.value.length,placeholder:e.$t("selectExactMatchTag")},null,8,["options","value","disabled","placeholder"]),s.value.expired||!s.value.img_count?(b(),L(X,{key:0,onClick:K(v),loading:!K(l).isIdle,type:"primary"},{default:h(()=>[A(y(s.value.img_count===0?e.$t("generateIndexHint"):e.$t("UpdateIndex")),1)]),_:1},8,["onClick","loading"])):(b(),L(X,{key:1,type:"primary",onClick:m,loading:!K(l).isIdle},{default:h(()=>[A(y(e.$t("search")),1)]),_:1},8,["loading"]))]),C("div",it,[C("div",rt,y(e.$t("anyMatch")),1),r(K(re),{conv:P,mode:"multiple",style:{width:"100%"},options:g.value,value:d.value.or_tags,"onUpdate:value":n[3]||(n[3]=i=>d.value.or_tags=i),disabled:!g.value.length,placeholder:e.$t("selectAnyMatchTag")},null,8,["options","value","disabled","placeholder"]),dt,r(X,{onClick:n[4]||(n[4]=i=>f.value=!0)},{default:h(()=>[A(y(e.$t("history")),1)]),_:1})]),C("div",ct,[C("div",ut,y(e.$t("exclude")),1),r(K(re),{conv:P,mode:"multiple",style:{width:"100%"},options:g.value,value:d.value.not_tags,"onUpdate:value":n[5]||(n[5]=i=>d.value.not_tags=i),disabled:!g.value.length,placeholder:e.$t("selectExcludeTag")},null,8,["options","value","disabled","placeholder"])]),C("div",vt,[C("div",pt,y(e.$t("searchScope")),1),r(Te,{"auto-size":{maxRows:8},value:d.value.folder_paths_str,"onUpdate:value":n[6]||(n[6]=i=>d.value.folder_paths_str=i),placeholder:e.$t("specifiedSearchFolder")},null,8,["value","placeholder"])])]),g.value.filter(i=>i.type!=="custom").length?D("",!0):(b(),T("p",ft,y(e.$t("needGenerateIdx")),1)),C("div",gt,[(b(!0),T(J,null,_e(N.value,([i,ie])=>(b(),T(J,{key:i},[i!=="Media Type"||ie.length>1?(b(),T("ul",mt,[C("h3",{class:"cat-name",onClick:$=>p.value.includes(i)?p.value.splice(p.value.indexOf(i),1):p.value.push(i)},[r(K(Ra),{class:xe(["arrow",{down:p.value.includes(i)}])},null,8,["class"]),A(" "+y(e.$t(i))+" ",1),C("div",{onClick:n[7]||(n[7]=ce(()=>{},["stop","prevent"])),class:"filter-input"},[r(ve,{value:O.value[i],"onUpdate:value":$=>O.value[i]=$,size:"small",allowClear:"",placeholder:e.$t("filterByKeyword")},null,8,["value","onUpdate:value","placeholder"])])],8,_t),r(Be,{ghost:"",activeKey:p.value,"onUpdate:activeKey":n[10]||(n[10]=$=>p.value=$)},{expandIcon:h(()=>[]),default:h(()=>[(b(),L(Ee,{key:i},{default:h(()=>[(b(!0),T(J,null,_e(x(ie,i),($,Me)=>(b(),L(at,{onClick:le=>Y($),onRemove:le=>W($.id),onToggleAnd:le=>M($.id,d.value.and_tags),onToggleOr:le=>M($.id,d.value.or_tags),onToggleNot:le=>M($.id,d.value.not_tags),key:$.id,idx:Me,name:i,tag:$,selected:G.value.has($.id)},null,8,["onClick","onRemove","onToggleAnd","onToggleOr","onToggleNot","idx","name","tag","selected"]))),128)),i==="custom"?(b(),T("li",{key:0,class:"tag",onClick:n[9]||(n[9]=$=>j.value=!0)},[j.value?(b(),L(Ne,{key:0,compact:""},{default:h(()=>[r(ve,{value:R.value,"onUpdate:value":n[8]||(n[8]=$=>R.value=$),style:{width:"128px"},loading:c.value,"allow-clear":"",size:"small"},null,8,["value","loading"]),r(pe,{size:"small",type:"primary",onClickCapture:ce(V,["stop"]),loading:c.value},{default:h(()=>[A(y(R.value?e.$t("submit"):e.$t("cancel")),1)]),_:1},8,["onClickCapture","loading"])]),_:1})):(b(),T(J,{key:1},[r(K(_a)),A(" "+y(e.$t("add")),1)],64))])):D("",!0),U(i)E.set(i,U(i)+512)},{default:h(()=>[A(y(e.$t("loadmore")),1)]),_:2},1032,["onClick"])])):D("",!0)]),_:2},1024))]),_:2},1032,["activeKey"])])):D("",!0)],64))),128))])],64)):(b(),T("div",yt,[r(Re,{size:"large"})]))])}}});const xt=Se(bt,[["__scopeId","data-v-494e8b82"]]);export{xt as default}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/TagSearch-fba3e5b0.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/TagSearch-fba3e5b0.css new file mode 100644 index 0000000000000000000000000000000000000000..2d285c491bc20765a7567774ae769bf43ce17223 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/TagSearch-fba3e5b0.css @@ -0,0 +1 @@ +@charset "UTF-8";.ant-collapse{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";background-color:#fafafa;border:1px solid #d9d9d9;border-bottom:0;border-radius:2px}.ant-collapse>.ant-collapse-item{border-bottom:1px solid #d9d9d9}.ant-collapse>.ant-collapse-item:last-child,.ant-collapse>.ant-collapse-item:last-child>.ant-collapse-header{border-radius:0 0 2px 2px}.ant-collapse>.ant-collapse-item>.ant-collapse-header{position:relative;display:flex;flex-wrap:nowrap;align-items:flex-start;padding:12px 16px;color:#000000d9;line-height:1.5715;cursor:pointer;transition:all .3s,visibility 0s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{display:inline-block;margin-right:12px;font-size:12px;vertical-align:-1px}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{transition:transform .24s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-extra{margin-left:auto}.ant-collapse>.ant-collapse-item>.ant-collapse-header:focus{outline:none}.ant-collapse>.ant-collapse-item .ant-collapse-header-collapsible-only{cursor:default}.ant-collapse>.ant-collapse-item .ant-collapse-header-collapsible-only .ant-collapse-header-text{cursor:pointer}.ant-collapse>.ant-collapse-item.ant-collapse-no-arrow>.ant-collapse-header{padding-left:12px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header{position:relative;padding:12px 40px 12px 16px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{position:absolute;top:50%;right:16px;left:auto;margin:0;transform:translateY(-50%)}.ant-collapse-content{color:#000000d9;background-color:#fff;border-top:1px solid #d9d9d9}.ant-collapse-content>.ant-collapse-content-box{padding:16px}.ant-collapse-content-hidden{display:none}.ant-collapse-item:last-child>.ant-collapse-content{border-radius:0 0 2px 2px}.ant-collapse-borderless{background-color:#fafafa;border:0}.ant-collapse-borderless>.ant-collapse-item{border-bottom:1px solid #d9d9d9}.ant-collapse-borderless>.ant-collapse-item:last-child,.ant-collapse-borderless>.ant-collapse-item:last-child .ant-collapse-header{border-radius:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content{background-color:transparent;border-top:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box{padding-top:4px}.ant-collapse-ghost{background-color:transparent;border:0}.ant-collapse-ghost>.ant-collapse-item{border-bottom:0}.ant-collapse-ghost>.ant-collapse-item>.ant-collapse-content{background-color:transparent;border-top:0}.ant-collapse-ghost>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box{padding-top:12px;padding-bottom:12px}.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:#00000040;cursor:not-allowed}.ant-collapse-rtl{direction:rtl}.ant-collapse-rtl .ant-collapse>.ant-collapse-item>.ant-collapse-header{padding:12px 40px 12px 16px}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{margin-right:0;margin-left:12px}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{transform:rotate(180deg)}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-extra{margin-right:auto;margin-left:0}.ant-collapse-rtl.ant-collapse>.ant-collapse-item.ant-collapse-no-arrow>.ant-collapse-header{padding-right:12px;padding-left:0}.tag-wrap[data-v-3aaf060d]{display:inline-block;position:relative}.tag-wrap:hover .float-actions[data-v-3aaf060d],.tag-wrap .float-actions:hover[data-v-3aaf060d]{display:flex;flex-wrap:nowrap;opacity:1;max-height:100px}.tag-wrap .tag[data-v-3aaf060d]{border:2px solid var(--zp-secondary);color:var(--zp-primary);border-radius:999px;padding:4px 16px;margin:4px;cursor:pointer;position:relative;display:flex;align-items:center;justify-content:space-between;flex-direction:row;gap:4px}.tag-wrap .tag.selected[data-v-3aaf060d]{color:var(--primary-color);border:2px solid var(--primary-color)}.tag-wrap .tag-name[data-v-3aaf060d]{max-width:192px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tag-wrap .float-actions[data-v-3aaf060d]{position:absolute;left:0;top:-32px;transition-delay:.5s;transition-duration:.3s;background:var(--zp-primary-background);box-sizing:border-box;border-radius:4px;user-select:none;display:none;opacity:0;max-height:0;overflow:hidden;z-index:9999;transition-property:opacity,max-height;transition-duration:.5s;transition-timing-function:ease-in-out;padding:4px;box-shadow:0 4px 16px var(--zp-luminous-deep);border-bottom:3px solid var(--zp-luminous)}.tag-wrap .float-actions div[data-v-3aaf060d]{background:var(--zp-secondary-background);cursor:pointer;white-space:pre;font-size:12px;padding:1px 4px}.tag-wrap .float-actions div[data-v-3aaf060d]:hover{background:var(--zp-secondary-variant-background)}.tag-wrap .float-actions[data-v-3aaf060d]>:not(:last-child){margin-right:4px}.spin-container[data-v-494e8b82]{text-align:center;background:rgba(0,0,0,.05);border-radius:4px;padding:256px}[data-v-494e8b82] .ant-collapse>.ant-collapse-item>.ant-collapse-header{padding:0}.container[data-v-494e8b82]{height:var(--pane-max-height);overflow:auto;display:flex;flex-direction:column;align-items:stretch}.container .generate-idx-hint[data-v-494e8b82]{margin:64px;padding:64px;font-size:2em;text-align:center;background-color:var(--zp-secondary-background);white-space:pre-line;line-height:2.5em;border-radius:16px}.container .remove[data-v-494e8b82]{padding:4px;position:cursor;border-radius:2px}.container .remove[data-v-494e8b82]:hover{background-color:var(--zp-secondary-background)}.container .select[data-v-494e8b82]{padding:8px}.container .search-bar[data-v-494e8b82]{padding:8px;display:flex}.container .search-bar .form-name[data-v-494e8b82]{flex-shrink:0;padding:4px 8px;width:128px}.container .list-container[data-v-494e8b82]{background-color:var(--zp-secondary-background);overflow:scroll}.container .cat-name[data-v-494e8b82]{user-select:none;position:sticky;top:0;padding:4px 16px;background:var(--zp-primary-background);margin:4px;transition:all .3s ease;border-left:4px solid var(--primary-color);cursor:pointer;z-index:1;display:flex;align-items:center;flex-direction:row}.container .cat-name .filter-input[data-v-494e8b82]{margin-left:32px;width:256px}.container .cat-name .filter-input>span[data-v-494e8b82]{border-radius:6px}.container .cat-name[data-v-494e8b82]:hover{border-radius:4px;background-color:var(--zp-secondary-background)}.container .cat-name .arrow[data-v-494e8b82]{color:var(--primary-color);transition:all .3s ease;margin-right:16px}.container .cat-name .arrow.down[data-v-494e8b82]{transform:rotate(90deg)}.container .tag-list[data-v-494e8b82]{list-style:none;margin:16px;border-radius:16px;background:var(--zp-primary-background);padding:8px}.container .tag-list .tag[data-v-494e8b82]{border:2px solid var(--zp-secondary);color:var(--zp-primary);border-radius:999px;padding:4px 16px;margin:4px;display:inline-block;cursor:pointer;position:relative} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/antd.dark-35e9b327.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/antd.dark-35e9b327.js new file mode 100644 index 0000000000000000000000000000000000000000..87b83fdf892321f331061f6765243c8701e29279 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/antd.dark-35e9b327.js @@ -0,0 +1,139 @@ +const t=`/*! + * + * ant-design-vue v3.2.20 + * + * Copyright 2017-present, ant-design-vue. + * All rights reserved. + * + *//*!****************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/style/index.less ***! + \\****************************************************************************************************************************************************************************************************************************************************/[class^=ant-]::-ms-clear,[class*=ant-]::-ms-clear,[class^=ant-] input::-ms-clear,[class*=ant-] input::-ms-clear,[class^=ant-] input::-ms-reveal,[class*=ant-] input::-ms-reveal{display:none}html,body{width:100%;height:100%}input::-ms-clear,input::-ms-reveal{display:none}*,*:before,*:after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{margin:0;color:#ffffffd9;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-variant:tabular-nums;line-height:1.5715;background-color:#000;font-feature-settings:"tnum"}[tabindex="-1"]:focus{outline:none!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5em;color:#ffffffd9;font-weight:500}p{margin-top:0;margin-bottom:1em}abbr[title],abbr[data-original-title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;border-bottom:0;cursor:help}address{margin-bottom:1em;font-style:normal;line-height:inherit}input[type=text],input[type=password],input[type=number],textarea{-webkit-appearance:none}ol,ul,dl{margin-top:0;margin-bottom:1em}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#177ddc;text-decoration:none;background-color:transparent;outline:none;cursor:pointer;transition:color .3s;-webkit-text-decoration-skip:objects}a:hover{color:#165996}a:active{color:#388ed3}a:active,a:hover{text-decoration:none;outline:0}a:focus{text-decoration:none;outline:0}a[disabled]{color:#ffffff4d;cursor:not-allowed}pre,code,kbd,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}pre{margin-top:0;margin-bottom:1em;overflow:auto}figure{margin:0 0 1em}img{vertical-align:middle;border-style:none}a,area,button,[role=button],input:not([type="range"]),label,select,summary,textarea{touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75em;padding-bottom:.3em;color:#ffffff73;text-align:left;caption-side:bottom}input,button,select,optgroup,textarea{margin:0;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{padding:0;border-style:none}input[type=radio],input[type=checkbox]{box-sizing:border-box;padding:0}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;margin:0;padding:0;border:0}legend{display:block;width:100%;max-width:100%;margin-bottom:.5em;padding:0;color:inherit;font-size:1.5em;line-height:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{padding:.2em;background-color:#2b2611}::-moz-selection{color:#fff;background:#177ddc}::selection{color:#fff;background:#177ddc}.clearfix:before{display:table;content:""}.clearfix:after{display:table;clear:both;content:""}.anticon{display:inline-block;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.anticon>*{line-height:1}.anticon svg{display:inline-block}.anticon:before{display:none}.anticon .anticon-icon{display:block}.anticon>.anticon{line-height:0;vertical-align:0}.anticon[tabindex]{cursor:pointer}.anticon-spin:before{display:inline-block;animation:loadingCircle 1s infinite linear}.anticon-spin{display:inline-block;animation:loadingCircle 1s infinite linear}.ant-fade-enter,.ant-fade-appear,.ant-fade-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-fade-enter.ant-fade-enter-active,.ant-fade-appear.ant-fade-appear-active{animation-name:antFadeIn;animation-play-state:running}.ant-fade-leave.ant-fade-leave-active{animation-name:antFadeOut;animation-play-state:running;pointer-events:none}.ant-fade-enter,.ant-fade-appear{opacity:0;animation-timing-function:linear}.ant-fade-leave{animation-timing-function:linear}.fade-enter,.fade-appear,.fade-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.fade-enter.fade-enter-active,.fade-appear.fade-appear-active{animation-name:antFadeIn;animation-play-state:running}.fade-leave.fade-leave-active{animation-name:antFadeOut;animation-play-state:running;pointer-events:none}.fade-enter,.fade-appear{opacity:0;animation-timing-function:linear}.fade-leave{animation-timing-function:linear}@keyframes antFadeIn{0%{opacity:0}to{opacity:1}}@keyframes antFadeOut{0%{opacity:1}to{opacity:0}}.ant-move-up-enter,.ant-move-up-appear,.ant-move-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-move-up-enter.ant-move-up-enter-active,.ant-move-up-appear.ant-move-up-appear-active{animation-name:antMoveUpIn;animation-play-state:running}.ant-move-up-leave.ant-move-up-leave-active{animation-name:antMoveUpOut;animation-play-state:running;pointer-events:none}.ant-move-up-enter,.ant-move-up-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-move-up-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.move-up-enter,.move-up-appear,.move-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.move-up-enter.move-up-enter-active,.move-up-appear.move-up-appear-active{animation-name:antMoveUpIn;animation-play-state:running}.move-up-leave.move-up-leave-active{animation-name:antMoveUpOut;animation-play-state:running;pointer-events:none}.move-up-enter,.move-up-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-up-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.ant-move-down-enter,.ant-move-down-appear,.ant-move-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-move-down-enter.ant-move-down-enter-active,.ant-move-down-appear.ant-move-down-appear-active{animation-name:antMoveDownIn;animation-play-state:running}.ant-move-down-leave.ant-move-down-leave-active{animation-name:antMoveDownOut;animation-play-state:running;pointer-events:none}.ant-move-down-enter,.ant-move-down-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-move-down-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.move-down-enter,.move-down-appear,.move-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.move-down-enter.move-down-enter-active,.move-down-appear.move-down-appear-active{animation-name:antMoveDownIn;animation-play-state:running}.move-down-leave.move-down-leave-active{animation-name:antMoveDownOut;animation-play-state:running;pointer-events:none}.move-down-enter,.move-down-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-down-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.ant-move-left-enter,.ant-move-left-appear,.ant-move-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-move-left-enter.ant-move-left-enter-active,.ant-move-left-appear.ant-move-left-appear-active{animation-name:antMoveLeftIn;animation-play-state:running}.ant-move-left-leave.ant-move-left-leave-active{animation-name:antMoveLeftOut;animation-play-state:running;pointer-events:none}.ant-move-left-enter,.ant-move-left-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-move-left-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.move-left-enter,.move-left-appear,.move-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.move-left-enter.move-left-enter-active,.move-left-appear.move-left-appear-active{animation-name:antMoveLeftIn;animation-play-state:running}.move-left-leave.move-left-leave-active{animation-name:antMoveLeftOut;animation-play-state:running;pointer-events:none}.move-left-enter,.move-left-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-left-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.ant-move-right-enter,.ant-move-right-appear,.ant-move-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-move-right-enter.ant-move-right-enter-active,.ant-move-right-appear.ant-move-right-appear-active{animation-name:antMoveRightIn;animation-play-state:running}.ant-move-right-leave.ant-move-right-leave-active{animation-name:antMoveRightOut;animation-play-state:running;pointer-events:none}.ant-move-right-enter,.ant-move-right-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-move-right-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.move-right-enter,.move-right-appear,.move-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.move-right-enter.move-right-enter-active,.move-right-appear.move-right-appear-active{animation-name:antMoveRightIn;animation-play-state:running}.move-right-leave.move-right-leave-active{animation-name:antMoveRightOut;animation-play-state:running;pointer-events:none}.move-right-enter,.move-right-appear{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-right-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}@keyframes antMoveDownIn{0%{transform:translateY(100%);transform-origin:0 0;opacity:0}to{transform:translateY(0);transform-origin:0 0;opacity:1}}@keyframes antMoveDownOut{0%{transform:translateY(0);transform-origin:0 0;opacity:1}to{transform:translateY(100%);transform-origin:0 0;opacity:0}}@keyframes antMoveLeftIn{0%{transform:translate(-100%);transform-origin:0 0;opacity:0}to{transform:translate(0);transform-origin:0 0;opacity:1}}@keyframes antMoveLeftOut{0%{transform:translate(0);transform-origin:0 0;opacity:1}to{transform:translate(-100%);transform-origin:0 0;opacity:0}}@keyframes antMoveRightIn{0%{transform:translate(100%);transform-origin:0 0;opacity:0}to{transform:translate(0);transform-origin:0 0;opacity:1}}@keyframes antMoveRightOut{0%{transform:translate(0);transform-origin:0 0;opacity:1}to{transform:translate(100%);transform-origin:0 0;opacity:0}}@keyframes antMoveUpIn{0%{transform:translateY(-100%);transform-origin:0 0;opacity:0}to{transform:translateY(0);transform-origin:0 0;opacity:1}}@keyframes antMoveUpOut{0%{transform:translateY(0);transform-origin:0 0;opacity:1}to{transform:translateY(-100%);transform-origin:0 0;opacity:0}}@keyframes loadingCircle{to{transform:rotate(360deg)}}[ant-click-animating=true],[ant-click-animating-without-extra-node=true]{position:relative}html{--antd-wave-shadow-color: #177ddc;--scroll-bar: 0}[ant-click-animating-without-extra-node=true]:after,.ant-click-animating-node{position:absolute;top:0;right:0;bottom:0;left:0;display:block;border-radius:inherit;box-shadow:0 0 #177ddc;box-shadow:0 0 0 0 var(--antd-wave-shadow-color);opacity:.2;animation:fadeEffect 2s cubic-bezier(.08,.82,.17,1),waveEffect .4s cubic-bezier(.08,.82,.17,1);animation-fill-mode:forwards;content:"";pointer-events:none}@keyframes waveEffect{to{box-shadow:0 0 #177ddc;box-shadow:0 0 0 6px var(--antd-wave-shadow-color)}}@keyframes fadeEffect{to{opacity:0}}.slide-up-enter,.slide-up-appear,.slide-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.slide-up-enter.slide-up-enter-active,.slide-up-appear.slide-up-appear-active{animation-name:antSlideUpIn;animation-play-state:running}.slide-up-leave.slide-up-leave-active{animation-name:antSlideUpOut;animation-play-state:running;pointer-events:none}.slide-up-enter,.slide-up-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-up-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.slide-down-enter,.slide-down-appear,.slide-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.slide-down-enter.slide-down-enter-active,.slide-down-appear.slide-down-appear-active{animation-name:antSlideDownIn;animation-play-state:running}.slide-down-leave.slide-down-leave-active{animation-name:antSlideDownOut;animation-play-state:running;pointer-events:none}.slide-down-enter,.slide-down-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-down-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.slide-left-enter,.slide-left-appear,.slide-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.slide-left-enter.slide-left-enter-active,.slide-left-appear.slide-left-appear-active{animation-name:antSlideLeftIn;animation-play-state:running}.slide-left-leave.slide-left-leave-active{animation-name:antSlideLeftOut;animation-play-state:running;pointer-events:none}.slide-left-enter,.slide-left-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-left-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.slide-right-enter,.slide-right-appear,.slide-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.slide-right-enter.slide-right-enter-active,.slide-right-appear.slide-right-appear-active{animation-name:antSlideRightIn;animation-play-state:running}.slide-right-leave.slide-right-leave-active{animation-name:antSlideRightOut;animation-play-state:running;pointer-events:none}.slide-right-enter,.slide-right-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-right-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.ant-slide-up-enter,.ant-slide-up-appear,.ant-slide-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-slide-up-enter.ant-slide-up-enter-active,.ant-slide-up-appear.ant-slide-up-appear-active{animation-name:antSlideUpIn;animation-play-state:running}.ant-slide-up-leave.ant-slide-up-leave-active{animation-name:antSlideUpOut;animation-play-state:running;pointer-events:none}.ant-slide-up-enter,.ant-slide-up-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.ant-slide-up-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.ant-slide-down-enter,.ant-slide-down-appear,.ant-slide-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-slide-down-enter.ant-slide-down-enter-active,.ant-slide-down-appear.ant-slide-down-appear-active{animation-name:antSlideDownIn;animation-play-state:running}.ant-slide-down-leave.ant-slide-down-leave-active{animation-name:antSlideDownOut;animation-play-state:running;pointer-events:none}.ant-slide-down-enter,.ant-slide-down-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.ant-slide-down-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.ant-slide-left-enter,.ant-slide-left-appear,.ant-slide-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-slide-left-enter.ant-slide-left-enter-active,.ant-slide-left-appear.ant-slide-left-appear-active{animation-name:antSlideLeftIn;animation-play-state:running}.ant-slide-left-leave.ant-slide-left-leave-active{animation-name:antSlideLeftOut;animation-play-state:running;pointer-events:none}.ant-slide-left-enter,.ant-slide-left-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.ant-slide-left-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.ant-slide-right-enter,.ant-slide-right-appear,.ant-slide-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-slide-right-enter.ant-slide-right-enter-active,.ant-slide-right-appear.ant-slide-right-appear-active{animation-name:antSlideRightIn;animation-play-state:running}.ant-slide-right-leave.ant-slide-right-leave-active{animation-name:antSlideRightOut;animation-play-state:running;pointer-events:none}.ant-slide-right-enter,.ant-slide-right-appear{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.ant-slide-right-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}@keyframes antSlideUpIn{0%{transform:scaleY(.8);transform-origin:0% 0%;opacity:0}to{transform:scaleY(1);transform-origin:0% 0%;opacity:1}}@keyframes antSlideUpOut{0%{transform:scaleY(1);transform-origin:0% 0%;opacity:1}to{transform:scaleY(.8);transform-origin:0% 0%;opacity:0}}@keyframes antSlideDownIn{0%{transform:scaleY(.8);transform-origin:100% 100%;opacity:0}to{transform:scaleY(1);transform-origin:100% 100%;opacity:1}}@keyframes antSlideDownOut{0%{transform:scaleY(1);transform-origin:100% 100%;opacity:1}to{transform:scaleY(.8);transform-origin:100% 100%;opacity:0}}@keyframes antSlideLeftIn{0%{transform:scaleX(.8);transform-origin:0% 0%;opacity:0}to{transform:scaleX(1);transform-origin:0% 0%;opacity:1}}@keyframes antSlideLeftOut{0%{transform:scaleX(1);transform-origin:0% 0%;opacity:1}to{transform:scaleX(.8);transform-origin:0% 0%;opacity:0}}@keyframes antSlideRightIn{0%{transform:scaleX(.8);transform-origin:100% 0%;opacity:0}to{transform:scaleX(1);transform-origin:100% 0%;opacity:1}}@keyframes antSlideRightOut{0%{transform:scaleX(1);transform-origin:100% 0%;opacity:1}to{transform:scaleX(.8);transform-origin:100% 0%;opacity:0}}.ant-zoom-enter,.ant-zoom-appear,.ant-zoom-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-enter.ant-zoom-enter-active,.ant-zoom-appear.ant-zoom-appear-active{animation-name:antZoomIn;animation-play-state:running}.ant-zoom-leave.ant-zoom-leave-active{animation-name:antZoomOut;animation-play-state:running;pointer-events:none}.ant-zoom-enter,.ant-zoom-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-enter-prepare,.ant-zoom-appear-prepare{transform:none}.ant-zoom-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-enter,.zoom-appear,.zoom-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-enter.zoom-enter-active,.zoom-appear.zoom-appear-active{animation-name:antZoomIn;animation-play-state:running}.zoom-leave.zoom-leave-active{animation-name:antZoomOut;animation-play-state:running;pointer-events:none}.zoom-enter,.zoom-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-enter-prepare,.zoom-appear-prepare{transform:none}.zoom-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-big-enter,.ant-zoom-big-appear,.ant-zoom-big-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-big-enter.ant-zoom-big-enter-active,.ant-zoom-big-appear.ant-zoom-big-appear-active{animation-name:antZoomBigIn;animation-play-state:running}.ant-zoom-big-leave.ant-zoom-big-leave-active{animation-name:antZoomBigOut;animation-play-state:running;pointer-events:none}.ant-zoom-big-enter,.ant-zoom-big-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-big-enter-prepare,.ant-zoom-big-appear-prepare{transform:none}.ant-zoom-big-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-big-enter,.zoom-big-appear,.zoom-big-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-big-enter.zoom-big-enter-active,.zoom-big-appear.zoom-big-appear-active{animation-name:antZoomBigIn;animation-play-state:running}.zoom-big-leave.zoom-big-leave-active{animation-name:antZoomBigOut;animation-play-state:running;pointer-events:none}.zoom-big-enter,.zoom-big-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-big-enter-prepare,.zoom-big-appear-prepare{transform:none}.zoom-big-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-big-fast-enter,.ant-zoom-big-fast-appear,.ant-zoom-big-fast-leave{animation-duration:.1s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-big-fast-enter.ant-zoom-big-fast-enter-active,.ant-zoom-big-fast-appear.ant-zoom-big-fast-appear-active{animation-name:antZoomBigIn;animation-play-state:running}.ant-zoom-big-fast-leave.ant-zoom-big-fast-leave-active{animation-name:antZoomBigOut;animation-play-state:running;pointer-events:none}.ant-zoom-big-fast-enter,.ant-zoom-big-fast-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-big-fast-enter-prepare,.ant-zoom-big-fast-appear-prepare{transform:none}.ant-zoom-big-fast-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-big-fast-enter,.zoom-big-fast-appear,.zoom-big-fast-leave{animation-duration:.1s;animation-fill-mode:both;animation-play-state:paused}.zoom-big-fast-enter.zoom-big-fast-enter-active,.zoom-big-fast-appear.zoom-big-fast-appear-active{animation-name:antZoomBigIn;animation-play-state:running}.zoom-big-fast-leave.zoom-big-fast-leave-active{animation-name:antZoomBigOut;animation-play-state:running;pointer-events:none}.zoom-big-fast-enter,.zoom-big-fast-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-big-fast-enter-prepare,.zoom-big-fast-appear-prepare{transform:none}.zoom-big-fast-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-up-enter,.ant-zoom-up-appear,.ant-zoom-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-up-enter.ant-zoom-up-enter-active,.ant-zoom-up-appear.ant-zoom-up-appear-active{animation-name:antZoomUpIn;animation-play-state:running}.ant-zoom-up-leave.ant-zoom-up-leave-active{animation-name:antZoomUpOut;animation-play-state:running;pointer-events:none}.ant-zoom-up-enter,.ant-zoom-up-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-up-enter-prepare,.ant-zoom-up-appear-prepare{transform:none}.ant-zoom-up-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-up-enter,.zoom-up-appear,.zoom-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-up-enter.zoom-up-enter-active,.zoom-up-appear.zoom-up-appear-active{animation-name:antZoomUpIn;animation-play-state:running}.zoom-up-leave.zoom-up-leave-active{animation-name:antZoomUpOut;animation-play-state:running;pointer-events:none}.zoom-up-enter,.zoom-up-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-up-enter-prepare,.zoom-up-appear-prepare{transform:none}.zoom-up-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-down-enter,.ant-zoom-down-appear,.ant-zoom-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-down-enter.ant-zoom-down-enter-active,.ant-zoom-down-appear.ant-zoom-down-appear-active{animation-name:antZoomDownIn;animation-play-state:running}.ant-zoom-down-leave.ant-zoom-down-leave-active{animation-name:antZoomDownOut;animation-play-state:running;pointer-events:none}.ant-zoom-down-enter,.ant-zoom-down-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-down-enter-prepare,.ant-zoom-down-appear-prepare{transform:none}.ant-zoom-down-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-down-enter,.zoom-down-appear,.zoom-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-down-enter.zoom-down-enter-active,.zoom-down-appear.zoom-down-appear-active{animation-name:antZoomDownIn;animation-play-state:running}.zoom-down-leave.zoom-down-leave-active{animation-name:antZoomDownOut;animation-play-state:running;pointer-events:none}.zoom-down-enter,.zoom-down-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-down-enter-prepare,.zoom-down-appear-prepare{transform:none}.zoom-down-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-left-enter,.ant-zoom-left-appear,.ant-zoom-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-left-enter.ant-zoom-left-enter-active,.ant-zoom-left-appear.ant-zoom-left-appear-active{animation-name:antZoomLeftIn;animation-play-state:running}.ant-zoom-left-leave.ant-zoom-left-leave-active{animation-name:antZoomLeftOut;animation-play-state:running;pointer-events:none}.ant-zoom-left-enter,.ant-zoom-left-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-left-enter-prepare,.ant-zoom-left-appear-prepare{transform:none}.ant-zoom-left-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-left-enter,.zoom-left-appear,.zoom-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-left-enter.zoom-left-enter-active,.zoom-left-appear.zoom-left-appear-active{animation-name:antZoomLeftIn;animation-play-state:running}.zoom-left-leave.zoom-left-leave-active{animation-name:antZoomLeftOut;animation-play-state:running;pointer-events:none}.zoom-left-enter,.zoom-left-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-left-enter-prepare,.zoom-left-appear-prepare{transform:none}.zoom-left-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.ant-zoom-right-enter,.ant-zoom-right-appear,.ant-zoom-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.ant-zoom-right-enter.ant-zoom-right-enter-active,.ant-zoom-right-appear.ant-zoom-right-appear-active{animation-name:antZoomRightIn;animation-play-state:running}.ant-zoom-right-leave.ant-zoom-right-leave-active{animation-name:antZoomRightOut;animation-play-state:running;pointer-events:none}.ant-zoom-right-enter,.ant-zoom-right-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.ant-zoom-right-enter-prepare,.ant-zoom-right-appear-prepare{transform:none}.ant-zoom-right-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-right-enter,.zoom-right-appear,.zoom-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-right-enter.zoom-right-enter-active,.zoom-right-appear.zoom-right-appear-active{animation-name:antZoomRightIn;animation-play-state:running}.zoom-right-leave.zoom-right-leave-active{animation-name:antZoomRightOut;animation-play-state:running;pointer-events:none}.zoom-right-enter,.zoom-right-appear{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-right-enter-prepare,.zoom-right-appear-prepare{transform:none}.zoom-right-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}@keyframes antZoomIn{0%{transform:scale(.2);opacity:0}to{transform:scale(1);opacity:1}}@keyframes antZoomOut{0%{transform:scale(1)}to{transform:scale(.2);opacity:0}}@keyframes antZoomBigIn{0%{transform:scale(.8);opacity:0}to{transform:scale(1);opacity:1}}@keyframes antZoomBigOut{0%{transform:scale(1)}to{transform:scale(.8);opacity:0}}@keyframes antZoomUpIn{0%{transform:scale(.8);transform-origin:50% 0%;opacity:0}to{transform:scale(1);transform-origin:50% 0%}}@keyframes antZoomUpOut{0%{transform:scale(1);transform-origin:50% 0%}to{transform:scale(.8);transform-origin:50% 0%;opacity:0}}@keyframes antZoomLeftIn{0%{transform:scale(.8);transform-origin:0% 50%;opacity:0}to{transform:scale(1);transform-origin:0% 50%}}@keyframes antZoomLeftOut{0%{transform:scale(1);transform-origin:0% 50%}to{transform:scale(.8);transform-origin:0% 50%;opacity:0}}@keyframes antZoomRightIn{0%{transform:scale(.8);transform-origin:100% 50%;opacity:0}to{transform:scale(1);transform-origin:100% 50%}}@keyframes antZoomRightOut{0%{transform:scale(1);transform-origin:100% 50%}to{transform:scale(.8);transform-origin:100% 50%;opacity:0}}@keyframes antZoomDownIn{0%{transform:scale(.8);transform-origin:50% 100%;opacity:0}to{transform:scale(1);transform-origin:50% 100%}}@keyframes antZoomDownOut{0%{transform:scale(1);transform-origin:50% 100%}to{transform:scale(.8);transform-origin:50% 100%;opacity:0}}.ant-motion-collapse-legacy{overflow:hidden}.ant-motion-collapse-legacy-active{transition:height .2s cubic-bezier(.645,.045,.355,1),opacity .2s cubic-bezier(.645,.045,.355,1)!important}.ant-motion-collapse{overflow:hidden;transition:height .2s cubic-bezier(.645,.045,.355,1),opacity .2s cubic-bezier(.645,.045,.355,1)!important}/*!**********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/affix/style/index.less ***! + \\**********************************************************************************************************************************************************************************************************************************************************/.ant-affix{position:fixed;z-index:10}/*!**********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/alert/style/index.less ***! + \\**********************************************************************************************************************************************************************************************************************************************************/.ant-alert{box-sizing:border-box;margin:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:flex;align-items:center;padding:8px 15px;word-wrap:break-word;border-radius:2px}.ant-alert-content{flex:1;min-width:0}.ant-alert-icon{margin-right:8px}.ant-alert-description{display:none;font-size:14px;line-height:22px}.ant-alert-success{background-color:#162312;border:1px solid #274916}.ant-alert-success .ant-alert-icon{color:#49aa19}.ant-alert-info{background-color:#111b26;border:1px solid #153450}.ant-alert-info .ant-alert-icon{color:#177ddc}.ant-alert-warning{background-color:#2b2111;border:1px solid #594214}.ant-alert-warning .ant-alert-icon{color:#d89614}.ant-alert-error{background-color:#2a1215;border:1px solid #58181c}.ant-alert-error .ant-alert-icon{color:#a61d24}.ant-alert-error .ant-alert-description>pre{margin:0;padding:0}.ant-alert-action{margin-left:8px}.ant-alert-close-icon{margin-left:8px;padding:0;overflow:hidden;font-size:12px;line-height:12px;background-color:transparent;border:none;outline:none;cursor:pointer}.ant-alert-close-icon .anticon-close{color:#ffffff73;transition:color .3s}.ant-alert-close-icon .anticon-close:hover{color:#ffffffbf}.ant-alert-close-text{color:#ffffff73;transition:color .3s}.ant-alert-close-text:hover{color:#ffffffbf}.ant-alert-with-description{align-items:flex-start;padding:15px 15px 15px 24px}.ant-alert-with-description.ant-alert-no-icon{padding:15px}.ant-alert-with-description .ant-alert-icon{margin-right:15px;font-size:24px}.ant-alert-with-description .ant-alert-message{display:block;margin-bottom:4px;color:#ffffffd9;font-size:16px}.ant-alert-message{color:#ffffffd9}.ant-alert-with-description .ant-alert-description{display:block}.ant-alert.ant-alert-motion-leave{overflow:hidden;opacity:1;transition:max-height .3s cubic-bezier(.78,.14,.15,.86),opacity .3s cubic-bezier(.78,.14,.15,.86),padding-top .3s cubic-bezier(.78,.14,.15,.86),padding-bottom .3s cubic-bezier(.78,.14,.15,.86),margin-bottom .3s cubic-bezier(.78,.14,.15,.86)}.ant-alert.ant-alert-motion-leave-active{max-height:0;margin-bottom:0!important;padding-top:0;padding-bottom:0;opacity:0}.ant-alert-banner{margin-bottom:0;border:0;border-radius:0}.ant-alert.ant-alert-rtl{direction:rtl}.ant-alert-rtl .ant-alert-icon{margin-right:auto;margin-left:8px}.ant-alert-rtl .ant-alert-action,.ant-alert-rtl .ant-alert-close-icon{margin-right:8px;margin-left:auto}.ant-alert-rtl.ant-alert-with-description{padding-right:24px;padding-left:15px}.ant-alert-rtl.ant-alert-with-description .ant-alert-icon{margin-right:auto;margin-left:15px}/*!***********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/anchor/style/index.less ***! + \\***********************************************************************************************************************************************************************************************************************************************************/.ant-anchor{box-sizing:border-box;margin:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;padding:0 0 0 2px}.ant-anchor-wrapper{margin-left:-4px;padding-left:4px;overflow:auto;background-color:transparent}.ant-anchor-ink{position:absolute;top:0;left:0;height:100%}.ant-anchor-ink:before{position:relative;display:block;width:2px;height:100%;margin:0 auto;background-color:#303030;content:" "}.ant-anchor-ink-ball{position:absolute;left:50%;display:none;width:8px;height:8px;background-color:#141414;border:2px solid #177ddc;border-radius:8px;transform:translate(-50%);transition:top .3s ease-in-out}.ant-anchor-ink-ball.visible{display:inline-block}.ant-anchor-fixed .ant-anchor-ink .ant-anchor-ink-ball{display:none}.ant-anchor-link{padding:7px 0 7px 16px;line-height:1.143}.ant-anchor-link-title{position:relative;display:block;margin-bottom:6px;overflow:hidden;color:#ffffffd9;white-space:nowrap;text-overflow:ellipsis;transition:all .3s}.ant-anchor-link-title:only-child{margin-bottom:0}.ant-anchor-link-active>.ant-anchor-link-title{color:#177ddc}.ant-anchor-link .ant-anchor-link{padding-top:5px;padding-bottom:5px}.ant-anchor-rtl{direction:rtl}.ant-anchor-rtl.ant-anchor-wrapper{margin-right:-4px;margin-left:0;padding-right:4px;padding-left:0}.ant-anchor-rtl .ant-anchor-ink{right:0;left:auto}.ant-anchor-rtl .ant-anchor-ink-ball{right:50%;left:0;transform:translate(50%)}.ant-anchor-rtl .ant-anchor-link{padding:7px 16px 7px 0}/*!******************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/auto-complete/style/index.less ***! + \\******************************************************************************************************************************************************************************************************************************************************************/.ant-select-auto-complete{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum"}.ant-select-auto-complete .ant-select-clear{right:13px}/*!***********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/select/style/index.less ***! + \\***********************************************************************************************************************************************************************************************************************************************************/.ant-select-single .ant-select-selector{display:flex}.ant-select-single .ant-select-selector .ant-select-selection-search{position:absolute;top:0;right:11px;bottom:0;left:11px}.ant-select-single .ant-select-selector .ant-select-selection-search-input{width:100%}.ant-select-single .ant-select-selector .ant-select-selection-item,.ant-select-single .ant-select-selector .ant-select-selection-placeholder{padding:0;line-height:30px;transition:all .3s}@supports (-moz-appearance: meterbar){.ant-select-single .ant-select-selector .ant-select-selection-item,.ant-select-single .ant-select-selector .ant-select-selection-placeholder{line-height:30px}}.ant-select-single .ant-select-selector .ant-select-selection-item{position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-select-single .ant-select-selector .ant-select-selection-placeholder{transition:none;pointer-events:none}.ant-select-single .ant-select-selector:after,.ant-select-single .ant-select-selector .ant-select-selection-item:after,.ant-select-single .ant-select-selector .ant-select-selection-placeholder:after{display:inline-block;width:0;visibility:hidden;content:" "}.ant-select-single.ant-select-show-arrow .ant-select-selection-search{right:25px}.ant-select-single.ant-select-show-arrow .ant-select-selection-item,.ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder{padding-right:18px}.ant-select-single.ant-select-open .ant-select-selection-item{color:#ffffff4d}.ant-select-single:not(.ant-select-customize-input) .ant-select-selector{width:100%;height:32px;padding:0 11px}.ant-select-single:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input{height:30px}.ant-select-single:not(.ant-select-customize-input) .ant-select-selector:after{line-height:30px}.ant-select-single.ant-select-customize-input .ant-select-selector:after{display:none}.ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-search{position:static;width:100%}.ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-placeholder{position:absolute;right:0;left:0;padding:0 11px}.ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-placeholder:after{display:none}.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector{height:40px}.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector:after,.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-item,.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-placeholder{line-height:38px}.ant-select-single.ant-select-lg:not(.ant-select-customize-input):not(.ant-select-customize-input) .ant-select-selection-search-input{height:38px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector{height:24px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector:after,.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-item,.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-placeholder{line-height:22px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input):not(.ant-select-customize-input) .ant-select-selection-search-input{height:22px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selection-search{right:7px;left:7px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector{padding:0 7px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-search{right:28px}.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-item,.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-placeholder{padding-right:21px}.ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector{padding:0 11px}.ant-select-selection-overflow{position:relative;display:flex;flex:auto;flex-wrap:wrap;max-width:100%}.ant-select-selection-overflow-item{flex:none;align-self:center;max-width:100%}.ant-select-multiple .ant-select-selector{display:flex;flex-wrap:wrap;align-items:center;padding:1px 4px}.ant-select-show-search.ant-select-multiple .ant-select-selector{cursor:text}.ant-select-disabled.ant-select-multiple .ant-select-selector{background:#141414;cursor:not-allowed}.ant-select-multiple .ant-select-selector:after{display:inline-block;width:0;margin:2px 0;line-height:24px;content:" "}.ant-select-multiple.ant-select-show-arrow .ant-select-selector,.ant-select-multiple.ant-select-allow-clear .ant-select-selector{padding-right:24px}.ant-select-multiple .ant-select-selection-item{position:relative;display:flex;flex:none;box-sizing:border-box;max-width:100%;height:24px;margin-top:2px;margin-bottom:2px;line-height:22px;background:rgba(255,255,255,.08);border:1px solid #303030;border-radius:2px;cursor:default;transition:font-size .3s,line-height .3s,height .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-margin-end:4px;margin-inline-end:4px;-webkit-padding-start:8px;padding-inline-start:8px;-webkit-padding-end:4px;padding-inline-end:4px}.ant-select-disabled.ant-select-multiple .ant-select-selection-item{color:#595959;border-color:#1f1f1f;cursor:not-allowed}.ant-select-multiple .ant-select-selection-item-content{display:inline-block;margin-right:4px;overflow:hidden;white-space:pre;text-overflow:ellipsis}.ant-select-multiple .ant-select-selection-item-remove{color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;color:#ffffff73;font-weight:700;font-size:10px;line-height:inherit;cursor:pointer}.ant-select-multiple .ant-select-selection-item-remove>*{line-height:1}.ant-select-multiple .ant-select-selection-item-remove svg{display:inline-block}.ant-select-multiple .ant-select-selection-item-remove:before{display:none}.ant-select-multiple .ant-select-selection-item-remove .ant-select-multiple .ant-select-selection-item-remove-icon{display:block}.ant-select-multiple .ant-select-selection-item-remove>.anticon{vertical-align:-.2em}.ant-select-multiple .ant-select-selection-item-remove:hover{color:#ffffffbf}.ant-select-multiple .ant-select-selection-overflow-item+.ant-select-selection-overflow-item .ant-select-selection-search{-webkit-margin-start:0;margin-inline-start:0}.ant-select-multiple .ant-select-selection-search{position:relative;max-width:100%;-webkit-margin-start:7px;margin-inline-start:7px}.ant-select-multiple .ant-select-selection-search-input,.ant-select-multiple .ant-select-selection-search-mirror{height:24px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:24px;transition:all .3s}.ant-select-multiple .ant-select-selection-search-input{width:100%;min-width:4.1px}.ant-select-multiple .ant-select-selection-search-mirror{position:absolute;top:0;left:0;z-index:999;white-space:pre;visibility:hidden}.ant-select-multiple .ant-select-selection-placeholder{position:absolute;top:50%;right:11px;left:11px;transform:translateY(-50%);transition:all .3s}.ant-select-multiple.ant-select-lg .ant-select-selector:after{line-height:32px}.ant-select-multiple.ant-select-lg .ant-select-selection-item{height:32px;line-height:30px}.ant-select-multiple.ant-select-lg .ant-select-selection-search{height:32px;line-height:32px}.ant-select-multiple.ant-select-lg .ant-select-selection-search-input,.ant-select-multiple.ant-select-lg .ant-select-selection-search-mirror{height:32px;line-height:30px}.ant-select-multiple.ant-select-sm .ant-select-selector:after{line-height:16px}.ant-select-multiple.ant-select-sm .ant-select-selection-item{height:16px;line-height:14px}.ant-select-multiple.ant-select-sm .ant-select-selection-search{height:16px;line-height:16px}.ant-select-multiple.ant-select-sm .ant-select-selection-search-input,.ant-select-multiple.ant-select-sm .ant-select-selection-search-mirror{height:16px;line-height:14px}.ant-select-multiple.ant-select-sm .ant-select-selection-placeholder{left:7px}.ant-select-multiple.ant-select-sm .ant-select-selection-search{-webkit-margin-start:3px;margin-inline-start:3px}.ant-select-multiple.ant-select-lg .ant-select-selection-item{height:32px;line-height:32px}.ant-select-disabled .ant-select-selection-item-remove{display:none}.ant-select{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;cursor:pointer}.ant-select:not(.ant-select-customize-input) .ant-select-selector{position:relative;background-color:transparent;border:1px solid #434343;border-radius:2px;transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-select:not(.ant-select-customize-input) .ant-select-selector input{cursor:pointer}.ant-select-show-search.ant-select:not(.ant-select-customize-input) .ant-select-selector{cursor:text}.ant-select-show-search.ant-select:not(.ant-select-customize-input) .ant-select-selector input{cursor:auto}.ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) .ant-select-selector{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector{color:#ffffff4d;background:rgba(255,255,255,.08);cursor:not-allowed}.ant-select-multiple.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector{background:#141414}.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector input{cursor:not-allowed}.ant-select:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input{margin:0;padding:0;background:transparent;border:none;outline:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.ant-select:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input::-webkit-search-cancel-button{display:none;-webkit-appearance:none}.ant-select:not(.ant-select-disabled):hover .ant-select-selector{border-color:#165996;border-right-width:1px!important}.ant-select-selection-item{flex:1;overflow:hidden;font-weight:400;white-space:nowrap;text-overflow:ellipsis}@media all and (-ms-high-contrast: none){.ant-select-selection-item *::-ms-backdrop,.ant-select-selection-item{flex:auto}}.ant-select-selection-placeholder{flex:1;overflow:hidden;color:#ffffff4d;white-space:nowrap;text-overflow:ellipsis;pointer-events:none}@media all and (-ms-high-contrast: none){.ant-select-selection-placeholder *::-ms-backdrop,.ant-select-selection-placeholder{flex:auto}}.ant-select-arrow{display:inline-block;color:inherit;font-style:normal;line-height:0;text-transform:none;vertical-align:-.125em;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:50%;right:11px;width:12px;height:12px;margin-top:-6px;color:#ffffff4d;font-size:12px;line-height:1;text-align:center;pointer-events:none}.ant-select-arrow>*{line-height:1}.ant-select-arrow svg{display:inline-block}.ant-select-arrow:before{display:none}.ant-select-arrow .ant-select-arrow-icon{display:block}.ant-select-arrow .anticon{vertical-align:top;transition:transform .3s}.ant-select-arrow .anticon>svg{vertical-align:top}.ant-select-arrow .anticon:not(.ant-select-suffix){pointer-events:auto}.ant-select-disabled .ant-select-arrow{cursor:not-allowed}.ant-select-clear{position:absolute;top:50%;right:11px;z-index:1;display:inline-block;width:12px;height:12px;margin-top:-6px;color:#ffffff4d;font-size:12px;font-style:normal;line-height:1;text-align:center;text-transform:none;background:#141414;cursor:pointer;opacity:0;transition:color .3s ease,opacity .15s ease;text-rendering:auto}.ant-select-clear:before{display:block}.ant-select-clear:hover{color:#ffffff73}.ant-select:hover .ant-select-clear{opacity:1}.ant-select-dropdown{margin:0;color:#ffffffd9;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;box-sizing:border-box;padding:4px 0;overflow:hidden;font-size:14px;font-variant:initial;background-color:#1f1f1f;border-radius:2px;outline:none;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003}.ant-select-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-select-dropdown-placement-bottomLeft,.ant-select-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-select-dropdown-placement-bottomLeft{animation-name:antSlideUpIn}.ant-select-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-select-dropdown-placement-topLeft,.ant-select-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-select-dropdown-placement-topLeft{animation-name:antSlideDownIn}.ant-select-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-select-dropdown-placement-bottomLeft{animation-name:antSlideUpOut}.ant-select-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-select-dropdown-placement-topLeft{animation-name:antSlideDownOut}.ant-select-dropdown-hidden{display:none}.ant-select-dropdown-empty{color:#ffffff4d}.ant-select-item-empty{position:relative;display:block;min-height:32px;padding:5px 12px;color:#ffffffd9;font-weight:400;font-size:14px;line-height:22px;color:#ffffff4d}.ant-select-item{position:relative;display:block;min-height:32px;padding:5px 12px;color:#ffffffd9;font-weight:400;font-size:14px;line-height:22px;cursor:pointer;transition:background .3s ease}.ant-select-item-group{color:#ffffff73;font-size:12px;cursor:default}.ant-select-item-option{display:flex}.ant-select-item-option-content{flex:auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-select-item-option-state{flex:none}.ant-select-item-option-active:not(.ant-select-item-option-disabled){background-color:#ffffff14}.ant-select-item-option-selected:not(.ant-select-item-option-disabled){color:#ffffffd9;font-weight:600;background-color:#111b26}.ant-select-item-option-selected:not(.ant-select-item-option-disabled) .ant-select-item-option-state{color:#177ddc}.ant-select-item-option-disabled{color:#ffffff4d;cursor:not-allowed}.ant-select-item-option-disabled.ant-select-item-option-selected{background-color:#141414}.ant-select-item-option-grouped{padding-left:24px}.ant-select-lg{font-size:16px}.ant-select-borderless .ant-select-selector{background-color:transparent!important;border-color:transparent!important;box-shadow:none!important}.ant-select-rtl{direction:rtl}.ant-select-rtl .ant-select-arrow,.ant-select-rtl .ant-select-clear{right:initial;left:11px}.ant-select-dropdown-rtl{direction:rtl}.ant-select-dropdown-rtl .ant-select-item-option-grouped{padding-right:24px;padding-left:12px}.ant-select-rtl.ant-select-multiple.ant-select-show-arrow .ant-select-selector,.ant-select-rtl.ant-select-multiple.ant-select-allow-clear .ant-select-selector{padding-right:4px;padding-left:24px}.ant-select-rtl.ant-select-multiple .ant-select-selection-item{text-align:right}.ant-select-rtl.ant-select-multiple .ant-select-selection-item-content{margin-right:0;margin-left:4px;text-align:right}.ant-select-rtl.ant-select-multiple .ant-select-selection-search-mirror{right:0;left:auto}.ant-select-rtl.ant-select-multiple .ant-select-selection-placeholder{right:11px;left:auto}.ant-select-rtl.ant-select-multiple.ant-select-sm .ant-select-selection-placeholder{right:7px}.ant-select-rtl.ant-select-single .ant-select-selector .ant-select-selection-item,.ant-select-rtl.ant-select-single .ant-select-selector .ant-select-selection-placeholder{right:0;left:9px;text-align:right}.ant-select-rtl.ant-select-single.ant-select-show-arrow .ant-select-selection-search{right:11px;left:25px}.ant-select-rtl.ant-select-single.ant-select-show-arrow .ant-select-selection-item,.ant-select-rtl.ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder{padding-right:0;padding-left:18px}.ant-select-rtl.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-search{right:6px}.ant-select-rtl.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-item,.ant-select-rtl.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-placeholder{padding-right:0;padding-left:21px}/*!**********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/empty/style/index.less ***! + \\**********************************************************************************************************************************************************************************************************************************************************/.ant-empty{margin:0 8px;font-size:14px;line-height:1.5715;text-align:center}.ant-empty-image{height:100px;margin-bottom:8px}.ant-empty-image img{height:100%}.ant-empty-image svg{height:100%;margin:auto}.ant-empty-footer{margin-top:16px}.ant-empty-normal{margin:32px 0;color:#ffffff4d}.ant-empty-normal .ant-empty-image{height:40px}.ant-empty-small{margin:8px 0;color:#ffffff4d}.ant-empty-small .ant-empty-image{height:35px}.ant-empty-img-default-ellipse{fill:#fff;fill-opacity:.08}.ant-empty-img-default-path-1{fill:#262626}.ant-empty-img-default-path-2{fill:url(#linearGradient-1)}.ant-empty-img-default-path-3{fill:#595959}.ant-empty-img-default-path-4{fill:#434343}.ant-empty-img-default-path-5{fill:#595959}.ant-empty-img-default-g{fill:#434343}.ant-empty-img-simple-ellipse{fill:#fff;fill-opacity:.08}.ant-empty-img-simple-g{stroke:#434343}.ant-empty-img-simple-path{fill:#262626;stroke:#434343}.ant-empty-rtl{direction:rtl}/*!***********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/avatar/style/index.less ***! + \\***********************************************************************************************************************************************************************************************************************************************************/.ant-avatar{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;overflow:hidden;color:#fff;white-space:nowrap;text-align:center;vertical-align:middle;background:rgba(255,255,255,.3);width:32px;height:32px;line-height:32px;border-radius:50%}.ant-avatar-image{background:transparent}.ant-avatar .ant-image-img{display:block}.ant-avatar-string{position:absolute;left:50%;transform-origin:0 center}.ant-avatar.ant-avatar-icon{font-size:18px}.ant-avatar.ant-avatar-icon>.anticon{margin:0}.ant-avatar-lg{width:40px;height:40px;line-height:40px;border-radius:50%}.ant-avatar-lg-string{position:absolute;left:50%;transform-origin:0 center}.ant-avatar-lg.ant-avatar-icon{font-size:24px}.ant-avatar-lg.ant-avatar-icon>.anticon{margin:0}.ant-avatar-sm{width:24px;height:24px;line-height:24px;border-radius:50%}.ant-avatar-sm-string{position:absolute;left:50%;transform-origin:0 center}.ant-avatar-sm.ant-avatar-icon{font-size:14px}.ant-avatar-sm.ant-avatar-icon>.anticon{margin:0}.ant-avatar-square{border-radius:2px}.ant-avatar>img{display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.ant-avatar-group{display:inline-flex}.ant-avatar-group .ant-avatar{border:1px solid #fff}.ant-avatar-group .ant-avatar:not(:first-child){margin-left:-8px}.ant-avatar-group-popover .ant-avatar+.ant-avatar{margin-left:3px}.ant-avatar-group-rtl .ant-avatar:not(:first-child){margin-right:-8px;margin-left:0}.ant-avatar-group-popover.ant-popover-rtl .ant-avatar+.ant-avatar{margin-right:3px;margin-left:0}/*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/back-top/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-back-top{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:fixed;right:100px;bottom:50px;z-index:10;width:40px;height:40px;cursor:pointer}.ant-back-top:empty{display:none}.ant-back-top-rtl{right:auto;left:100px;direction:rtl}.ant-back-top-content{width:40px;height:40px;overflow:hidden;color:#fff;text-align:center;background-color:#ffffff73;border-radius:20px;transition:all .3s}.ant-back-top-content:hover{background-color:#ffffffd9;transition:all .3s}.ant-back-top-icon{font-size:24px;line-height:40px}@media screen and (max-width: 768px){.ant-back-top{right:60px}}@media screen and (max-width: 480px){.ant-back-top{right:20px}}/*!**********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/badge/style/index.less ***! + \\**********************************************************************************************************************************************************************************************************************************************************/.ant-badge{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;line-height:1}.ant-badge-count{z-index:auto;min-width:20px;height:20px;padding:0 6px;color:#fff;font-weight:400;font-size:12px;line-height:20px;white-space:nowrap;text-align:center;background:#a61d24;border-radius:10px;box-shadow:0 0 0 1px #141414}.ant-badge-count a,.ant-badge-count a:hover{color:#fff}.ant-badge-count-sm{min-width:14px;height:14px;padding:0;font-size:12px;line-height:14px;border-radius:7px}.ant-badge-multiple-words{padding:0 8px}.ant-badge-dot{z-index:auto;width:6px;min-width:6px;height:6px;background:#a61d24;border-radius:100%;box-shadow:0 0 0 1px #141414}.ant-badge-dot.ant-scroll-number{transition:background 1.5s}.ant-badge-count,.ant-badge-dot,.ant-badge .ant-scroll-number-custom-component{position:absolute;top:0;right:0;transform:translate(50%,-50%);transform-origin:100% 0%}.ant-badge-count.anticon-spin,.ant-badge-dot.anticon-spin,.ant-badge .ant-scroll-number-custom-component.anticon-spin{animation:antBadgeLoadingCircle 1s infinite linear}.ant-badge-status{line-height:inherit;vertical-align:baseline}.ant-badge-status-dot{position:relative;top:-1px;display:inline-block;width:6px;height:6px;vertical-align:middle;border-radius:50%}.ant-badge-status-success{background-color:#49aa19}.ant-badge-status-processing{position:relative;background-color:#177ddc}.ant-badge-status-processing:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #177ddc;border-radius:50%;animation:antStatusProcessing 1.2s infinite ease-in-out;content:""}.ant-badge-status-default{background-color:#d9d9d9}.ant-badge-status-error{background-color:#a61d24}.ant-badge-status-warning{background-color:#d89614}.ant-badge-status-pink,.ant-badge-status-magenta{background:#cb2b83}.ant-badge-status-red{background:#d32029}.ant-badge-status-volcano{background:#d84a1b}.ant-badge-status-orange{background:#d87a16}.ant-badge-status-yellow{background:#d8bd14}.ant-badge-status-gold{background:#d89614}.ant-badge-status-cyan{background:#13a8a8}.ant-badge-status-lime{background:#8bbb11}.ant-badge-status-green{background:#49aa19}.ant-badge-status-blue{background:#177ddc}.ant-badge-status-geekblue{background:#2b4acb}.ant-badge-status-purple{background:#642ab5}.ant-badge-status-text{margin-left:8px;color:#ffffffd9;font-size:14px}.ant-badge-zoom-appear,.ant-badge-zoom-enter{animation:antZoomBadgeIn .3s cubic-bezier(.12,.4,.29,1.46);animation-fill-mode:both}.ant-badge-zoom-leave{animation:antZoomBadgeOut .3s cubic-bezier(.71,-.46,.88,.6);animation-fill-mode:both}.ant-badge-not-a-wrapper .ant-badge-zoom-appear,.ant-badge-not-a-wrapper .ant-badge-zoom-enter{animation:antNoWrapperZoomBadgeIn .3s cubic-bezier(.12,.4,.29,1.46)}.ant-badge-not-a-wrapper .ant-badge-zoom-leave{animation:antNoWrapperZoomBadgeOut .3s cubic-bezier(.71,-.46,.88,.6)}.ant-badge-not-a-wrapper:not(.ant-badge-status){vertical-align:middle}.ant-badge-not-a-wrapper .ant-scroll-number-custom-component,.ant-badge-not-a-wrapper .ant-badge-count{transform:none}.ant-badge-not-a-wrapper .ant-scroll-number-custom-component,.ant-badge-not-a-wrapper .ant-scroll-number{position:relative;top:auto;display:block;transform-origin:50% 50%}@keyframes antStatusProcessing{0%{transform:scale(.8);opacity:.5}to{transform:scale(2.4);opacity:0}}.ant-scroll-number{overflow:hidden;direction:ltr}.ant-scroll-number-only{position:relative;display:inline-block;height:20px;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-transform-style:preserve-3d;-webkit-backface-visibility:hidden}.ant-scroll-number-only>p.ant-scroll-number-only-unit{height:20px;margin:0;-webkit-transform-style:preserve-3d;-webkit-backface-visibility:hidden}.ant-scroll-number-symbol{vertical-align:top}@keyframes antZoomBadgeIn{0%{transform:scale(0) translate(50%,-50%);opacity:0}to{transform:scale(1) translate(50%,-50%)}}@keyframes antZoomBadgeOut{0%{transform:scale(1) translate(50%,-50%)}to{transform:scale(0) translate(50%,-50%);opacity:0}}@keyframes antNoWrapperZoomBadgeIn{0%{transform:scale(0);opacity:0}to{transform:scale(1)}}@keyframes antNoWrapperZoomBadgeOut{0%{transform:scale(1)}to{transform:scale(0);opacity:0}}@keyframes antBadgeLoadingCircle{0%{transform-origin:50%}to{transform:translate(50%,-50%) rotate(360deg);transform-origin:50%}}.ant-ribbon-wrapper{position:relative}.ant-ribbon{box-sizing:border-box;margin:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:8px;height:22px;padding:0 8px;color:#fff;line-height:22px;white-space:nowrap;background-color:#177ddc;border-radius:2px}.ant-ribbon-text{color:#fff}.ant-ribbon-corner{position:absolute;top:100%;width:8px;height:8px;color:currentcolor;border:4px solid;transform:scaleY(.75);transform-origin:top}.ant-ribbon-corner:after{position:absolute;top:-4px;left:-4px;width:inherit;height:inherit;color:#00000040;border:inherit;content:""}.ant-ribbon-color-pink,.ant-ribbon-color-magenta{color:#cb2b83;background:#cb2b83}.ant-ribbon-color-red{color:#d32029;background:#d32029}.ant-ribbon-color-volcano{color:#d84a1b;background:#d84a1b}.ant-ribbon-color-orange{color:#d87a16;background:#d87a16}.ant-ribbon-color-yellow{color:#d8bd14;background:#d8bd14}.ant-ribbon-color-gold{color:#d89614;background:#d89614}.ant-ribbon-color-cyan{color:#13a8a8;background:#13a8a8}.ant-ribbon-color-lime{color:#8bbb11;background:#8bbb11}.ant-ribbon-color-green{color:#49aa19;background:#49aa19}.ant-ribbon-color-blue{color:#177ddc;background:#177ddc}.ant-ribbon-color-geekblue{color:#2b4acb;background:#2b4acb}.ant-ribbon-color-purple{color:#642ab5;background:#642ab5}.ant-ribbon.ant-ribbon-placement-end{right:-8px;border-bottom-right-radius:0}.ant-ribbon.ant-ribbon-placement-end .ant-ribbon-corner{right:0;border-color:currentcolor transparent transparent currentcolor}.ant-ribbon.ant-ribbon-placement-start{left:-8px;border-bottom-left-radius:0}.ant-ribbon.ant-ribbon-placement-start .ant-ribbon-corner{left:0;border-color:currentcolor currentcolor transparent transparent}.ant-badge-rtl{direction:rtl}.ant-badge-rtl .ant-badge:not(.ant-badge-not-a-wrapper) .ant-badge-count,.ant-badge-rtl .ant-badge:not(.ant-badge-not-a-wrapper) .ant-badge-dot,.ant-badge-rtl .ant-badge:not(.ant-badge-not-a-wrapper) .ant-scroll-number-custom-component{right:auto;left:0;direction:ltr;transform:translate(-50%,-50%);transform-origin:0% 0%}.ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-scroll-number-custom-component{right:auto;left:0;transform:translate(-50%,-50%);transform-origin:0% 0%}.ant-badge-rtl .ant-badge-status-text{margin-right:8px;margin-left:0}.ant-ribbon-rtl{direction:rtl}.ant-ribbon-rtl.ant-ribbon-placement-end{right:unset;left:-8px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.ant-ribbon-rtl.ant-ribbon-placement-end .ant-ribbon-corner{right:unset;left:0;border-color:currentcolor currentcolor transparent transparent}.ant-ribbon-rtl.ant-ribbon-placement-end .ant-ribbon-corner:after{border-color:currentcolor currentcolor transparent transparent}.ant-ribbon-rtl.ant-ribbon-placement-start{right:-8px;left:unset;border-bottom-right-radius:0;border-bottom-left-radius:2px}.ant-ribbon-rtl.ant-ribbon-placement-start .ant-ribbon-corner{right:0;left:unset;border-color:currentcolor transparent transparent currentcolor}.ant-ribbon-rtl.ant-ribbon-placement-start .ant-ribbon-corner:after{border-color:currentcolor transparent transparent currentcolor}/*!***************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/breadcrumb/style/index.less ***! + \\***************************************************************************************************************************************************************************************************************************************************************/.ant-breadcrumb{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";color:#ffffff73;font-size:14px}.ant-breadcrumb .anticon{font-size:14px}.ant-breadcrumb a{color:#ffffff73;transition:color .3s}.ant-breadcrumb a:hover{color:#165996}.ant-breadcrumb>span:last-child{color:#ffffffd9}.ant-breadcrumb>span:last-child a{color:#ffffffd9}.ant-breadcrumb>span:last-child .ant-breadcrumb-separator{display:none}.ant-breadcrumb-separator{margin:0 8px;color:#ffffff73}.ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-link>.anticon+a{margin-left:4px}.ant-breadcrumb-overlay-link>.anticon{margin-left:4px}.ant-breadcrumb-rtl{direction:rtl}.ant-breadcrumb-rtl:before{display:table;content:""}.ant-breadcrumb-rtl:after{display:table;clear:both;content:""}.ant-breadcrumb-rtl>span{float:right}.ant-breadcrumb-rtl .ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-rtl .ant-breadcrumb-link>.anticon+a{margin-right:4px;margin-left:0}.ant-breadcrumb-rtl .ant-breadcrumb-overlay-link>.anticon{margin-right:4px;margin-left:0}/*!*********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/menu/style/index.less ***! + \\*********************************************************************************************************************************************************************************************************************************************************/.ant-menu-item-danger.ant-menu-item,.ant-menu-item-danger.ant-menu-item:hover,.ant-menu-item-danger.ant-menu-item-active{color:#a61d24}.ant-menu-item-danger.ant-menu-item:active{background:#2a1215}.ant-menu-item-danger.ant-menu-item-selected{color:#a61d24}.ant-menu-item-danger.ant-menu-item-selected>a,.ant-menu-item-danger.ant-menu-item-selected>a:hover{color:#a61d24}.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-danger.ant-menu-item-selected{background-color:#2a1215}.ant-menu-inline .ant-menu-item-danger.ant-menu-item:after{border-right-color:#a61d24}.ant-menu-dark .ant-menu-item-danger.ant-menu-item,.ant-menu-dark .ant-menu-item-danger.ant-menu-item:hover,.ant-menu-dark .ant-menu-item-danger.ant-menu-item>a{color:#a61d24}.ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-danger.ant-menu-item-selected{color:#fff;background-color:#a61d24}.ant-menu{box-sizing:border-box;margin:0;font-variant:tabular-nums;line-height:1.5715;font-feature-settings:"tnum";padding:0;color:#ffffffd9;font-size:14px;line-height:0;text-align:left;list-style:none;background:#141414;outline:none;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003;transition:background .3s,width .3s cubic-bezier(.2,0,0,1) 0s}.ant-menu:before{display:table;content:""}.ant-menu:after{display:table;clear:both;content:""}.ant-menu.ant-menu-root:focus-visible{box-shadow:0 0 0 2px #11263c}.ant-menu ul,.ant-menu ol{margin:0;padding:0;list-style:none}.ant-menu-overflow{display:flex}.ant-menu-overflow-item{flex:none}.ant-menu-hidden,.ant-menu-submenu-hidden{display:none}.ant-menu-item-group-title{height:1.5715;padding:8px 16px;color:#ffffff73;font-size:14px;line-height:1.5715;transition:all .3s}.ant-menu-horizontal .ant-menu-submenu{transition:border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu,.ant-menu-submenu-inline{transition:border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1),padding .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-selected{color:#177ddc}.ant-menu-item:active,.ant-menu-submenu-title:active{background:#111b26}.ant-menu-submenu .ant-menu-sub{cursor:initial;transition:background .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-title-content{transition:color .3s}.ant-menu-item a{color:#ffffffd9}.ant-menu-item a:hover{color:#177ddc}.ant-menu-item a:before{position:absolute;top:0;right:0;bottom:0;left:0;background-color:transparent;content:""}.ant-menu-item>.ant-badge a{color:#ffffffd9}.ant-menu-item>.ant-badge a:hover{color:#177ddc}.ant-menu-item-divider{overflow:hidden;line-height:0;border-color:#303030;border-style:solid;border-width:1px 0 0}.ant-menu-item-divider-dashed{border-style:dashed}.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu{margin-top:-1px}.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-submenu .ant-menu-submenu-title:hover{background-color:transparent}.ant-menu-item-selected,.ant-menu-item-selected a,.ant-menu-item-selected a:hover{color:#177ddc}.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{background-color:#111b26}.ant-menu-inline,.ant-menu-vertical,.ant-menu-vertical-left{border-right:1px solid #303030}.ant-menu-vertical-right{border-left:1px solid #303030}.ant-menu-vertical.ant-menu-sub,.ant-menu-vertical-left.ant-menu-sub,.ant-menu-vertical-right.ant-menu-sub{min-width:160px;max-height:calc(100vh - 100px);padding:0;overflow:hidden;border-right:0}.ant-menu-vertical.ant-menu-sub:not([class*="-active"]),.ant-menu-vertical-left.ant-menu-sub:not([class*="-active"]),.ant-menu-vertical-right.ant-menu-sub:not([class*="-active"]){overflow-x:hidden;overflow-y:auto}.ant-menu-vertical.ant-menu-sub .ant-menu-item,.ant-menu-vertical-left.ant-menu-sub .ant-menu-item,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item{left:0;margin-left:0;border-right:0}.ant-menu-vertical.ant-menu-sub .ant-menu-item:after,.ant-menu-vertical-left.ant-menu-sub .ant-menu-item:after,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item:after{border-right:0}.ant-menu-vertical.ant-menu-sub>.ant-menu-item,.ant-menu-vertical-left.ant-menu-sub>.ant-menu-item,.ant-menu-vertical-right.ant-menu-sub>.ant-menu-item,.ant-menu-vertical.ant-menu-sub>.ant-menu-submenu,.ant-menu-vertical-left.ant-menu-sub>.ant-menu-submenu,.ant-menu-vertical-right.ant-menu-sub>.ant-menu-submenu{transform-origin:0 0}.ant-menu-horizontal.ant-menu-sub{min-width:114px}.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu-title{transition:border-color .3s,background .3s}.ant-menu-item,.ant-menu-submenu-title{position:relative;display:block;margin:0;padding:0 20px;white-space:nowrap;cursor:pointer;transition:border-color .3s,background .3s,padding .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-item .ant-menu-item-icon,.ant-menu-submenu-title .ant-menu-item-icon,.ant-menu-item .anticon,.ant-menu-submenu-title .anticon{min-width:14px;font-size:14px;transition:font-size .15s cubic-bezier(.215,.61,.355,1),margin .3s cubic-bezier(.645,.045,.355,1),color .3s}.ant-menu-item .ant-menu-item-icon+span,.ant-menu-submenu-title .ant-menu-item-icon+span,.ant-menu-item .anticon+span,.ant-menu-submenu-title .anticon+span{margin-left:10px;opacity:1;transition:opacity .3s cubic-bezier(.645,.045,.355,1),margin .3s,color .3s}.ant-menu-item .ant-menu-item-icon.svg,.ant-menu-submenu-title .ant-menu-item-icon.svg{vertical-align:-.125em}.ant-menu-item.ant-menu-item-only-child>.anticon,.ant-menu-submenu-title.ant-menu-item-only-child>.anticon,.ant-menu-item.ant-menu-item-only-child>.ant-menu-item-icon,.ant-menu-submenu-title.ant-menu-item-only-child>.ant-menu-item-icon{margin-right:0}.ant-menu-item:focus-visible,.ant-menu-submenu-title:focus-visible{box-shadow:0 0 0 2px #11263c}.ant-menu>.ant-menu-item-divider{margin:1px 0;padding:0}.ant-menu-submenu-popup{position:absolute;z-index:1050;background:transparent;border-radius:2px;box-shadow:none;transform-origin:0 0}.ant-menu-submenu-popup:before{position:absolute;top:-7px;right:0;bottom:0;left:0;z-index:-1;width:100%;height:100%;opacity:.0001;content:" "}.ant-menu-submenu-placement-rightTop:before{top:0;left:-7px}.ant-menu-submenu>.ant-menu{background-color:#141414;border-radius:2px}.ant-menu-submenu>.ant-menu-submenu-title:after{transition:transform .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-popup>.ant-menu{background-color:#1f1f1f}.ant-menu-submenu-expand-icon,.ant-menu-submenu-arrow{position:absolute;top:50%;right:16px;width:10px;color:#ffffffd9;transform:translateY(-50%);transition:transform .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-arrow:before,.ant-menu-submenu-arrow:after{position:absolute;width:6px;height:1.5px;background-color:currentcolor;border-radius:2px;transition:background .3s cubic-bezier(.645,.045,.355,1),transform .3s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1),color .3s cubic-bezier(.645,.045,.355,1);content:""}.ant-menu-submenu-arrow:before{transform:rotate(45deg) translateY(-2.5px)}.ant-menu-submenu-arrow:after{transform:rotate(-45deg) translateY(2.5px)}.ant-menu-submenu:hover>.ant-menu-submenu-title>.ant-menu-submenu-expand-icon,.ant-menu-submenu:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow{color:#177ddc}.ant-menu-inline-collapsed .ant-menu-submenu-arrow:before,.ant-menu-submenu-inline .ant-menu-submenu-arrow:before{transform:rotate(-45deg) translate(2.5px)}.ant-menu-inline-collapsed .ant-menu-submenu-arrow:after,.ant-menu-submenu-inline .ant-menu-submenu-arrow:after{transform:rotate(45deg) translate(-2.5px)}.ant-menu-submenu-horizontal .ant-menu-submenu-arrow{display:none}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title>.ant-menu-submenu-arrow{transform:translateY(-2px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after{transform:rotate(-45deg) translate(-2.5px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{transform:rotate(45deg) translate(2.5px)}.ant-menu-vertical .ant-menu-submenu-selected,.ant-menu-vertical-left .ant-menu-submenu-selected,.ant-menu-vertical-right .ant-menu-submenu-selected{color:#177ddc}.ant-menu-horizontal{line-height:46px;border:0;border-bottom:1px solid #303030;box-shadow:none}.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu{margin-top:-1px;margin-bottom:0;padding:0 20px}.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item:hover,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu:hover,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-active,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-active,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-open,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-selected{color:#177ddc}.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item:hover:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu:hover:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-active:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-active:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-open:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-open:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-selected:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-selected:after{border-bottom:2px solid #177ddc}.ant-menu-horizontal>.ant-menu-item,.ant-menu-horizontal>.ant-menu-submenu{position:relative;top:1px;display:inline-block;vertical-align:bottom}.ant-menu-horizontal>.ant-menu-item:after,.ant-menu-horizontal>.ant-menu-submenu:after{position:absolute;right:20px;bottom:0;left:20px;border-bottom:2px solid transparent;transition:border-color .3s cubic-bezier(.645,.045,.355,1);content:""}.ant-menu-horizontal>.ant-menu-submenu>.ant-menu-submenu-title{padding:0}.ant-menu-horizontal>.ant-menu-item a{color:#ffffffd9}.ant-menu-horizontal>.ant-menu-item a:hover{color:#177ddc}.ant-menu-horizontal>.ant-menu-item a:before{bottom:-2px}.ant-menu-horizontal>.ant-menu-item-selected a{color:#177ddc}.ant-menu-horizontal:after{display:block;clear:both;height:0;content:" "}.ant-menu-vertical .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item,.ant-menu-inline .ant-menu-item{position:relative}.ant-menu-vertical .ant-menu-item:after,.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-vertical-right .ant-menu-item:after,.ant-menu-inline .ant-menu-item:after{position:absolute;top:0;right:0;bottom:0;border-right:3px solid #177ddc;transform:scaleY(.0001);opacity:0;transition:transform .15s cubic-bezier(.215,.61,.355,1),opacity .15s cubic-bezier(.215,.61,.355,1);content:""}.ant-menu-vertical .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item,.ant-menu-inline .ant-menu-item,.ant-menu-vertical .ant-menu-submenu-title,.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-vertical-right .ant-menu-submenu-title,.ant-menu-inline .ant-menu-submenu-title{height:40px;margin-top:4px;margin-bottom:4px;padding:0 16px;overflow:hidden;line-height:40px;text-overflow:ellipsis}.ant-menu-vertical .ant-menu-submenu,.ant-menu-vertical-left .ant-menu-submenu,.ant-menu-vertical-right .ant-menu-submenu,.ant-menu-inline .ant-menu-submenu{padding-bottom:.02px}.ant-menu-vertical .ant-menu-item:not(:last-child),.ant-menu-vertical-left .ant-menu-item:not(:last-child),.ant-menu-vertical-right .ant-menu-item:not(:last-child),.ant-menu-inline .ant-menu-item:not(:last-child){margin-bottom:8px}.ant-menu-vertical>.ant-menu-item,.ant-menu-vertical-left>.ant-menu-item,.ant-menu-vertical-right>.ant-menu-item,.ant-menu-inline>.ant-menu-item,.ant-menu-vertical>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical-left>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical-right>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-inline>.ant-menu-submenu>.ant-menu-submenu-title{height:40px;line-height:40px}.ant-menu-vertical .ant-menu-item-group-list .ant-menu-submenu-title,.ant-menu-vertical .ant-menu-submenu-title{padding-right:34px}.ant-menu-inline{width:100%}.ant-menu-inline .ant-menu-selected:after,.ant-menu-inline .ant-menu-item-selected:after{transform:scaleY(1);opacity:1;transition:transform .15s cubic-bezier(.645,.045,.355,1),opacity .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title{width:calc(100% + 1px)}.ant-menu-inline .ant-menu-item-group-list .ant-menu-submenu-title,.ant-menu-inline .ant-menu-submenu-title{padding-right:34px}.ant-menu-inline.ant-menu-root .ant-menu-item,.ant-menu-inline.ant-menu-root .ant-menu-submenu-title{display:flex;align-items:center;transition:border-color .3s,background .3s,padding .1s cubic-bezier(.215,.61,.355,1)}.ant-menu-inline.ant-menu-root .ant-menu-item>.ant-menu-title-content,.ant-menu-inline.ant-menu-root .ant-menu-submenu-title>.ant-menu-title-content{flex:auto;min-width:0;overflow:hidden;text-overflow:ellipsis}.ant-menu-inline.ant-menu-root .ant-menu-item>*,.ant-menu-inline.ant-menu-root .ant-menu-submenu-title>*{flex:none}.ant-menu.ant-menu-inline-collapsed{width:80px}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title{left:0;padding:0 calc(50% - 8px);text-overflow:clip}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .ant-menu-submenu-arrow,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .ant-menu-submenu-arrow,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-submenu-arrow{opacity:0}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .anticon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .anticon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .anticon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .anticon{margin:0;font-size:16px;line-height:40px}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .anticon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .anticon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .anticon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .anticon+span{display:inline-block;opacity:0}.ant-menu.ant-menu-inline-collapsed .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed .anticon{display:inline-block}.ant-menu.ant-menu-inline-collapsed-tooltip{pointer-events:none}.ant-menu.ant-menu-inline-collapsed-tooltip .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed-tooltip .anticon{display:none}.ant-menu.ant-menu-inline-collapsed-tooltip a{color:#ffffffd9}.ant-menu.ant-menu-inline-collapsed .ant-menu-item-group-title{padding-right:4px;padding-left:4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-menu-item-group-list{margin:0;padding:0}.ant-menu-item-group-list .ant-menu-item,.ant-menu-item-group-list .ant-menu-submenu-title{padding:0 16px 0 28px}.ant-menu-root.ant-menu-vertical,.ant-menu-root.ant-menu-vertical-left,.ant-menu-root.ant-menu-vertical-right,.ant-menu-root.ant-menu-inline{box-shadow:none}.ant-menu-root.ant-menu-inline-collapsed .ant-menu-item>.ant-menu-inline-collapsed-noicon,.ant-menu-root.ant-menu-inline-collapsed .ant-menu-submenu .ant-menu-submenu-title>.ant-menu-inline-collapsed-noicon{font-size:16px;text-align:center}.ant-menu-sub.ant-menu-inline{padding:0;background:rgba(255,255,255,.04);border:0;border-radius:0;box-shadow:none}.ant-menu-sub.ant-menu-inline>.ant-menu-item,.ant-menu-sub.ant-menu-inline>.ant-menu-submenu>.ant-menu-submenu-title{height:40px;line-height:40px;list-style-position:inside;list-style-type:disc}.ant-menu-sub.ant-menu-inline .ant-menu-item-group-title{padding-left:32px}.ant-menu-item-disabled,.ant-menu-submenu-disabled{color:#ffffff4d!important;background:none;cursor:not-allowed}.ant-menu-item-disabled:after,.ant-menu-submenu-disabled:after{border-color:transparent!important}.ant-menu-item-disabled a,.ant-menu-submenu-disabled a{color:#ffffff4d!important;pointer-events:none}.ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-submenu-disabled>.ant-menu-submenu-title{color:#ffffff4d!important;cursor:not-allowed}.ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after{background:rgba(255,255,255,.3)!important}.ant-layout-header .ant-menu{line-height:inherit}.ant-menu-inline-collapsed-tooltip a,.ant-menu-inline-collapsed-tooltip a:hover{color:#fff}.ant-menu-light .ant-menu-item:hover,.ant-menu-light .ant-menu-item-active,.ant-menu-light .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open,.ant-menu-light .ant-menu-submenu-active,.ant-menu-light .ant-menu-submenu-title:hover{color:#177ddc}.ant-menu.ant-menu-root:focus-visible{box-shadow:0 0 0 2px #388ed3}.ant-menu-dark .ant-menu-item:focus-visible,.ant-menu-dark .ant-menu-submenu-title:focus-visible{box-shadow:0 0 0 2px #388ed3}.ant-menu.ant-menu-dark,.ant-menu-dark .ant-menu-sub,.ant-menu.ant-menu-dark .ant-menu-sub{color:#ffffffa6;background:#1f1f1f}.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow{opacity:.45;transition:all .3s}.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:before{background:#fff}.ant-menu-dark.ant-menu-submenu-popup{background:transparent}.ant-menu-dark .ant-menu-inline.ant-menu-sub{background:#141414}.ant-menu-dark.ant-menu-horizontal{border-bottom:0}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu{top:0;margin-top:0;padding:0 20px;border-color:#1f1f1f;border-bottom:0}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item:hover{background-color:#177ddc}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item>a:before{bottom:0}.ant-menu-dark .ant-menu-item,.ant-menu-dark .ant-menu-item-group-title,.ant-menu-dark .ant-menu-item>a,.ant-menu-dark .ant-menu-item>span>a{color:#ffffffa6}.ant-menu-dark.ant-menu-inline,.ant-menu-dark.ant-menu-vertical,.ant-menu-dark.ant-menu-vertical-left,.ant-menu-dark.ant-menu-vertical-right{border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item,.ant-menu-dark.ant-menu-vertical .ant-menu-item,.ant-menu-dark.ant-menu-vertical-left .ant-menu-item,.ant-menu-dark.ant-menu-vertical-right .ant-menu-item{left:0;margin-left:0;border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical-right .ant-menu-item:after{border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item,.ant-menu-dark.ant-menu-inline .ant-menu-submenu-title{width:100%}.ant-menu-dark .ant-menu-item:hover,.ant-menu-dark .ant-menu-item-active,.ant-menu-dark .ant-menu-submenu-active,.ant-menu-dark .ant-menu-submenu-open,.ant-menu-dark .ant-menu-submenu-selected,.ant-menu-dark .ant-menu-submenu-title:hover{color:#fff;background-color:transparent}.ant-menu-dark .ant-menu-item:hover>a,.ant-menu-dark .ant-menu-item-active>a,.ant-menu-dark .ant-menu-submenu-active>a,.ant-menu-dark .ant-menu-submenu-open>a,.ant-menu-dark .ant-menu-submenu-selected>a,.ant-menu-dark .ant-menu-submenu-title:hover>a,.ant-menu-dark .ant-menu-item:hover>span>a,.ant-menu-dark .ant-menu-item-active>span>a,.ant-menu-dark .ant-menu-submenu-active>span>a,.ant-menu-dark .ant-menu-submenu-open>span>a,.ant-menu-dark .ant-menu-submenu-selected>span>a,.ant-menu-dark .ant-menu-submenu-title:hover>span>a{color:#fff}.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow{opacity:1}.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:#fff}.ant-menu-dark .ant-menu-item:hover{background-color:transparent}.ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected{background-color:#177ddc}.ant-menu-dark .ant-menu-item-selected{color:#fff;border-right:0}.ant-menu-dark .ant-menu-item-selected:after{border-right:0}.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>span>a,.ant-menu-dark .ant-menu-item-selected>a:hover,.ant-menu-dark .ant-menu-item-selected>span>a:hover{color:#fff}.ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon,.ant-menu-dark .ant-menu-item-selected .anticon{color:#fff}.ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon+span,.ant-menu-dark .ant-menu-item-selected .anticon+span{color:#fff}.ant-menu.ant-menu-dark .ant-menu-item-selected,.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected{background-color:#177ddc}.ant-menu-dark .ant-menu-item-disabled,.ant-menu-dark .ant-menu-submenu-disabled,.ant-menu-dark .ant-menu-item-disabled>a,.ant-menu-dark .ant-menu-submenu-disabled>a,.ant-menu-dark .ant-menu-item-disabled>span>a,.ant-menu-dark .ant-menu-submenu-disabled>span>a{color:#ffffff4d!important;opacity:.8}.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title{color:#ffffff4d!important}.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after{background:rgba(255,255,255,.3)!important}.ant-menu.ant-menu-rtl{direction:rtl;text-align:right}.ant-menu-rtl .ant-menu-item-group-title{text-align:right}.ant-menu-rtl.ant-menu-inline,.ant-menu-rtl.ant-menu-vertical{border-right:none;border-left:1px solid #303030}.ant-menu-rtl.ant-menu-dark.ant-menu-inline,.ant-menu-rtl.ant-menu-dark.ant-menu-vertical{border-left:none}.ant-menu-rtl.ant-menu-vertical.ant-menu-sub>.ant-menu-item,.ant-menu-rtl.ant-menu-vertical-left.ant-menu-sub>.ant-menu-item,.ant-menu-rtl.ant-menu-vertical-right.ant-menu-sub>.ant-menu-item,.ant-menu-rtl.ant-menu-vertical.ant-menu-sub>.ant-menu-submenu,.ant-menu-rtl.ant-menu-vertical-left.ant-menu-sub>.ant-menu-submenu,.ant-menu-rtl.ant-menu-vertical-right.ant-menu-sub>.ant-menu-submenu{transform-origin:top right}.ant-menu-rtl .ant-menu-item .ant-menu-item-icon,.ant-menu-rtl .ant-menu-submenu-title .ant-menu-item-icon,.ant-menu-rtl .ant-menu-item .anticon,.ant-menu-rtl .ant-menu-submenu-title .anticon{margin-right:auto;margin-left:10px}.ant-menu-rtl .ant-menu-item.ant-menu-item-only-child>.ant-menu-item-icon,.ant-menu-rtl .ant-menu-submenu-title.ant-menu-item-only-child>.ant-menu-item-icon,.ant-menu-rtl .ant-menu-item.ant-menu-item-only-child>.anticon,.ant-menu-rtl .ant-menu-submenu-title.ant-menu-item-only-child>.anticon{margin-left:0}.ant-menu-submenu-rtl.ant-menu-submenu-popup{transform-origin:100% 0}.ant-menu-rtl .ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-rtl .ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-rtl .ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-rtl .ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow{right:auto;left:16px}.ant-menu-rtl .ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-rtl .ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-rtl .ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:before{transform:rotate(-45deg) translateY(-2px)}.ant-menu-rtl .ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-rtl .ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-rtl .ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:after{transform:rotate(45deg) translateY(2px)}.ant-menu-rtl.ant-menu-vertical .ant-menu-item:after,.ant-menu-rtl.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-rtl.ant-menu-vertical-right .ant-menu-item:after,.ant-menu-rtl.ant-menu-inline .ant-menu-item:after{right:auto;left:0}.ant-menu-rtl.ant-menu-vertical .ant-menu-item,.ant-menu-rtl.ant-menu-vertical-left .ant-menu-item,.ant-menu-rtl.ant-menu-vertical-right .ant-menu-item,.ant-menu-rtl.ant-menu-inline .ant-menu-item,.ant-menu-rtl.ant-menu-vertical .ant-menu-submenu-title,.ant-menu-rtl.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-rtl.ant-menu-vertical-right .ant-menu-submenu-title,.ant-menu-rtl.ant-menu-inline .ant-menu-submenu-title{text-align:right}.ant-menu-rtl.ant-menu-inline .ant-menu-submenu-title{padding-right:0;padding-left:34px}.ant-menu-rtl.ant-menu-vertical .ant-menu-submenu-title{padding-right:16px;padding-left:34px}.ant-menu-rtl.ant-menu-inline-collapsed.ant-menu-vertical .ant-menu-submenu-title{padding:0 calc(50% - 8px)}.ant-menu-rtl .ant-menu-item-group-list .ant-menu-item,.ant-menu-rtl .ant-menu-item-group-list .ant-menu-submenu-title{padding:0 28px 0 16px}.ant-menu-sub.ant-menu-inline{border:0}.ant-menu-rtl.ant-menu-sub.ant-menu-inline .ant-menu-item-group-title{padding-right:32px;padding-left:0}/*!************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/tooltip/style/index.less ***! + \\************************************************************************************************************************************************************************************************************************************************************/.ant-tooltip{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;z-index:1070;display:block;width:-moz-max-content;width:max-content;max-width:250px;visibility:visible}.ant-tooltip-hidden{display:none}.ant-tooltip-placement-top,.ant-tooltip-placement-topLeft,.ant-tooltip-placement-topRight{padding-bottom:8px}.ant-tooltip-placement-right,.ant-tooltip-placement-rightTop,.ant-tooltip-placement-rightBottom{padding-left:8px}.ant-tooltip-placement-bottom,.ant-tooltip-placement-bottomLeft,.ant-tooltip-placement-bottomRight{padding-top:8px}.ant-tooltip-placement-left,.ant-tooltip-placement-leftTop,.ant-tooltip-placement-leftBottom{padding-right:8px}.ant-tooltip-inner{min-width:30px;min-height:32px;padding:6px 8px;color:#fff;text-align:left;text-decoration:none;word-wrap:break-word;background-color:#434343;border-radius:2px;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003}.ant-tooltip-arrow{position:absolute;display:block;width:13.07106781px;height:13.07106781px;overflow:hidden;background:transparent;pointer-events:none}.ant-tooltip-arrow-content{position:absolute;top:0;right:0;bottom:0;left:0;display:block;width:5px;height:5px;margin:auto;background-color:#434343;content:"";pointer-events:auto}.ant-tooltip-placement-top .ant-tooltip-arrow,.ant-tooltip-placement-topLeft .ant-tooltip-arrow,.ant-tooltip-placement-topRight .ant-tooltip-arrow{bottom:-5.07106781px}.ant-tooltip-placement-top .ant-tooltip-arrow-content,.ant-tooltip-placement-topLeft .ant-tooltip-arrow-content,.ant-tooltip-placement-topRight .ant-tooltip-arrow-content{box-shadow:3px 3px 7px #00000012;transform:translateY(-6.53553391px) rotate(45deg)}.ant-tooltip-placement-top .ant-tooltip-arrow{left:50%;transform:translate(-50%)}.ant-tooltip-placement-topLeft .ant-tooltip-arrow{left:13px}.ant-tooltip-placement-topRight .ant-tooltip-arrow{right:13px}.ant-tooltip-placement-right .ant-tooltip-arrow,.ant-tooltip-placement-rightTop .ant-tooltip-arrow,.ant-tooltip-placement-rightBottom .ant-tooltip-arrow{left:-5.07106781px}.ant-tooltip-placement-right .ant-tooltip-arrow-content,.ant-tooltip-placement-rightTop .ant-tooltip-arrow-content,.ant-tooltip-placement-rightBottom .ant-tooltip-arrow-content{box-shadow:-3px 3px 7px #00000012;transform:translate(6.53553391px) rotate(45deg)}.ant-tooltip-placement-right .ant-tooltip-arrow{top:50%;transform:translateY(-50%)}.ant-tooltip-placement-rightTop .ant-tooltip-arrow{top:5px}.ant-tooltip-placement-rightBottom .ant-tooltip-arrow{bottom:5px}.ant-tooltip-placement-left .ant-tooltip-arrow,.ant-tooltip-placement-leftTop .ant-tooltip-arrow,.ant-tooltip-placement-leftBottom .ant-tooltip-arrow{right:-5.07106781px}.ant-tooltip-placement-left .ant-tooltip-arrow-content,.ant-tooltip-placement-leftTop .ant-tooltip-arrow-content,.ant-tooltip-placement-leftBottom .ant-tooltip-arrow-content{box-shadow:3px -3px 7px #00000012;transform:translate(-6.53553391px) rotate(45deg)}.ant-tooltip-placement-left .ant-tooltip-arrow{top:50%;transform:translateY(-50%)}.ant-tooltip-placement-leftTop .ant-tooltip-arrow{top:5px}.ant-tooltip-placement-leftBottom .ant-tooltip-arrow{bottom:5px}.ant-tooltip-placement-bottom .ant-tooltip-arrow,.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow,.ant-tooltip-placement-bottomRight .ant-tooltip-arrow{top:-5.07106781px}.ant-tooltip-placement-bottom .ant-tooltip-arrow-content,.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow-content,.ant-tooltip-placement-bottomRight .ant-tooltip-arrow-content{box-shadow:-3px -3px 7px #00000012;transform:translateY(6.53553391px) rotate(45deg)}.ant-tooltip-placement-bottom .ant-tooltip-arrow{left:50%;transform:translate(-50%)}.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow{left:13px}.ant-tooltip-placement-bottomRight .ant-tooltip-arrow{right:13px}.ant-tooltip-pink .ant-tooltip-inner,.ant-tooltip-pink .ant-tooltip-arrow-content,.ant-tooltip-magenta .ant-tooltip-inner,.ant-tooltip-magenta .ant-tooltip-arrow-content{background-color:#cb2b83}.ant-tooltip-red .ant-tooltip-inner,.ant-tooltip-red .ant-tooltip-arrow-content{background-color:#d32029}.ant-tooltip-volcano .ant-tooltip-inner,.ant-tooltip-volcano .ant-tooltip-arrow-content{background-color:#d84a1b}.ant-tooltip-orange .ant-tooltip-inner,.ant-tooltip-orange .ant-tooltip-arrow-content{background-color:#d87a16}.ant-tooltip-yellow .ant-tooltip-inner,.ant-tooltip-yellow .ant-tooltip-arrow-content{background-color:#d8bd14}.ant-tooltip-gold .ant-tooltip-inner,.ant-tooltip-gold .ant-tooltip-arrow-content{background-color:#d89614}.ant-tooltip-cyan .ant-tooltip-inner,.ant-tooltip-cyan .ant-tooltip-arrow-content{background-color:#13a8a8}.ant-tooltip-lime .ant-tooltip-inner,.ant-tooltip-lime .ant-tooltip-arrow-content{background-color:#8bbb11}.ant-tooltip-green .ant-tooltip-inner,.ant-tooltip-green .ant-tooltip-arrow-content{background-color:#49aa19}.ant-tooltip-blue .ant-tooltip-inner,.ant-tooltip-blue .ant-tooltip-arrow-content{background-color:#177ddc}.ant-tooltip-geekblue .ant-tooltip-inner,.ant-tooltip-geekblue .ant-tooltip-arrow-content{background-color:#2b4acb}.ant-tooltip-purple .ant-tooltip-inner,.ant-tooltip-purple .ant-tooltip-arrow-content{background-color:#642ab5}.ant-tooltip-rtl{direction:rtl}.ant-tooltip-rtl .ant-tooltip-inner{text-align:right}/*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/dropdown/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-dropdown-menu-item.ant-dropdown-menu-item-danger{color:#a61d24}.ant-dropdown-menu-item.ant-dropdown-menu-item-danger:hover{color:#fff;background-color:#a61d24}.ant-dropdown{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;display:block}.ant-dropdown:before{position:absolute;top:-4px;right:0;bottom:-4px;left:-7px;z-index:-9999;opacity:.0001;content:" "}.ant-dropdown-wrap{position:relative}.ant-dropdown-wrap .ant-btn>.anticon-down{font-size:10px}.ant-dropdown-wrap .anticon-down:before{transition:transform .2s}.ant-dropdown-wrap-open .anticon-down:before{transform:rotate(180deg)}.ant-dropdown-hidden,.ant-dropdown-menu-hidden,.ant-dropdown-menu-submenu-hidden{display:none}.ant-dropdown-show-arrow.ant-dropdown-placement-topCenter,.ant-dropdown-show-arrow.ant-dropdown-placement-topLeft,.ant-dropdown-show-arrow.ant-dropdown-placement-topRight{padding-bottom:10px}.ant-dropdown-show-arrow.ant-dropdown-placement-bottomCenter,.ant-dropdown-show-arrow.ant-dropdown-placement-bottomLeft,.ant-dropdown-show-arrow.ant-dropdown-placement-bottomRight{padding-top:10px}.ant-dropdown-arrow{position:absolute;z-index:1;display:block;width:8.48528137px;height:8.48528137px;background:transparent;border-style:solid;border-width:4.24264069px;transform:rotate(45deg)}.ant-dropdown-placement-topCenter>.ant-dropdown-arrow,.ant-dropdown-placement-topLeft>.ant-dropdown-arrow,.ant-dropdown-placement-topRight>.ant-dropdown-arrow{bottom:6.2px;border-color:transparent #1f1f1f #1f1f1f transparent;box-shadow:3px 3px 7px #00000012}.ant-dropdown-placement-topCenter>.ant-dropdown-arrow{left:50%;transform:translate(-50%) rotate(45deg)}.ant-dropdown-placement-topLeft>.ant-dropdown-arrow{left:16px}.ant-dropdown-placement-topRight>.ant-dropdown-arrow{right:16px}.ant-dropdown-placement-bottomCenter>.ant-dropdown-arrow,.ant-dropdown-placement-bottomLeft>.ant-dropdown-arrow,.ant-dropdown-placement-bottomRight>.ant-dropdown-arrow{top:6px;border-color:#1f1f1f transparent transparent #1f1f1f;box-shadow:-2px -2px 5px #0000000f}.ant-dropdown-placement-bottomCenter>.ant-dropdown-arrow{left:50%;transform:translate(-50%) rotate(45deg)}.ant-dropdown-placement-bottomLeft>.ant-dropdown-arrow{left:16px}.ant-dropdown-placement-bottomRight>.ant-dropdown-arrow{right:16px}.ant-dropdown-menu{position:relative;margin:0;padding:4px 0;text-align:left;list-style-type:none;background-color:#1f1f1f;background-clip:padding-box;border-radius:2px;outline:none;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003}.ant-dropdown-menu-item-group-title{padding:5px 12px;color:#ffffff73;transition:all .3s}.ant-dropdown-menu-submenu-popup{position:absolute;z-index:1050;background:transparent;box-shadow:none;transform-origin:0 0}.ant-dropdown-menu-submenu-popup ul,.ant-dropdown-menu-submenu-popup li{list-style:none}.ant-dropdown-menu-submenu-popup ul{margin-right:.3em;margin-left:.3em}.ant-dropdown-menu-item{position:relative;display:flex;align-items:center}.ant-dropdown-menu-item-icon{min-width:12px;margin-right:8px;font-size:12px}.ant-dropdown-menu-title-content{flex:auto;white-space:nowrap}.ant-dropdown-menu-title-content>a{color:inherit;transition:all .3s}.ant-dropdown-menu-title-content>a:hover{color:inherit}.ant-dropdown-menu-title-content>a:after{position:absolute;top:0;right:0;bottom:0;left:0;content:""}.ant-dropdown-menu-item,.ant-dropdown-menu-submenu-title{clear:both;margin:0;padding:5px 12px;color:#ffffffd9;font-weight:400;font-size:14px;line-height:22px;cursor:pointer;transition:all .3s}.ant-dropdown-menu-item-selected,.ant-dropdown-menu-submenu-title-selected{color:#177ddc;background-color:#111b26}.ant-dropdown-menu-item:hover,.ant-dropdown-menu-submenu-title:hover{background-color:#ffffff14}.ant-dropdown-menu-item-disabled,.ant-dropdown-menu-submenu-title-disabled{color:#ffffff4d;cursor:not-allowed}.ant-dropdown-menu-item-disabled:hover,.ant-dropdown-menu-submenu-title-disabled:hover{color:#ffffff4d;background-color:transparent;cursor:not-allowed}.ant-dropdown-menu-item-disabled a,.ant-dropdown-menu-submenu-title-disabled a{pointer-events:none}.ant-dropdown-menu-item-divider,.ant-dropdown-menu-submenu-title-divider{height:1px;margin:4px 0;overflow:hidden;line-height:0;background-color:#303030}.ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon,.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon{position:absolute;right:8px}.ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon,.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon{margin-right:0!important;color:#ffffff73;font-size:10px;font-style:normal}.ant-dropdown-menu-item-group-list{margin:0 8px;padding:0;list-style:none}.ant-dropdown-menu-submenu-title{padding-right:24px}.ant-dropdown-menu-submenu-vertical{position:relative}.ant-dropdown-menu-submenu-vertical>.ant-dropdown-menu{position:absolute;top:0;left:100%;min-width:100%;margin-left:4px;transform-origin:0 0}.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title,.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow-icon{color:#ffffff4d;background-color:transparent;cursor:not-allowed}.ant-dropdown-menu-submenu-selected .ant-dropdown-menu-submenu-title{color:#177ddc}.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomRight,.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomRight{animation-name:antSlideUpIn}.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topLeft,.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topLeft,.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topCenter,.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topCenter,.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topRight,.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topRight{animation-name:antSlideDownIn}.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomRight{animation-name:antSlideUpOut}.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topLeft,.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topCenter,.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topRight{animation-name:antSlideDownOut}.ant-dropdown-trigger>.anticon.anticon-down,.ant-dropdown-link>.anticon.anticon-down,.ant-dropdown-button>.anticon.anticon-down{font-size:10px;vertical-align:baseline}.ant-dropdown-button{white-space:nowrap}.ant-dropdown-button.ant-btn-group>.ant-btn-loading,.ant-dropdown-button.ant-btn-group>.ant-btn-loading+.ant-btn{cursor:default;pointer-events:none}.ant-dropdown-button.ant-btn-group>.ant-btn-loading+.ant-btn:before{display:block}.ant-dropdown-button.ant-btn-group>.ant-btn:last-child:not(:first-child):not(.ant-btn-icon-only){padding-right:8px;padding-left:8px}.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu{background:#1f1f1f}.ant-dropdown-menu-dark .ant-dropdown-menu-item,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a,.ant-dropdown-menu-dark .ant-dropdown-menu-item>.anticon+span>a{color:#ffffffa6}.ant-dropdown-menu-dark .ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-item>.anticon+span>a .ant-dropdown-menu-submenu-arrow:after{color:#ffffffa6}.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item>.anticon+span>a:hover{color:#fff;background:transparent}.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected,.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected>a{color:#fff;background:#177ddc}.ant-dropdown-rtl{direction:rtl}.ant-dropdown-rtl.ant-dropdown:before{right:-7px;left:0}.ant-dropdown-menu.ant-dropdown-menu-rtl,.ant-dropdown-rtl .ant-dropdown-menu-item-group-title,.ant-dropdown-menu-submenu-rtl .ant-dropdown-menu-item-group-title{direction:rtl;text-align:right}.ant-dropdown-menu-submenu-popup.ant-dropdown-menu-submenu-rtl{transform-origin:100% 0}.ant-dropdown-rtl .ant-dropdown-menu-submenu-popup ul,.ant-dropdown-rtl .ant-dropdown-menu-submenu-popup li,.ant-dropdown-rtl .ant-dropdown-menu-item,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title{text-align:right}.ant-dropdown-rtl .ant-dropdown-menu-item>.anticon:first-child,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title>.anticon:first-child,.ant-dropdown-rtl .ant-dropdown-menu-item>span>.anticon:first-child,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title>span>.anticon:first-child{margin-right:0;margin-left:8px}.ant-dropdown-rtl .ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon{right:auto;left:8px}.ant-dropdown-rtl .ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon{margin-left:0!important;transform:scaleX(-1)}.ant-dropdown-rtl .ant-dropdown-menu-submenu-title{padding-right:12px;padding-left:24px}.ant-dropdown-rtl .ant-dropdown-menu-submenu-vertical>.ant-dropdown-menu{right:100%;left:0;margin-right:4px;margin-left:0}/*!***********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/button/style/index.less ***! + \\***********************************************************************************************************************************************************************************************************************************************************/.ant-btn{line-height:1.5715;position:relative;display:inline-block;font-weight:400;white-space:nowrap;text-align:center;background-image:none;border:1px solid transparent;box-shadow:0 2px #00000004;cursor:pointer;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;user-select:none;touch-action:manipulation;height:32px;padding:4px 15px;font-size:14px;border-radius:2px;color:#ffffffd9;border-color:#434343;background:transparent}.ant-btn>.anticon{line-height:1}.ant-btn,.ant-btn:active,.ant-btn:focus{outline:0}.ant-btn:not([disabled]):hover{text-decoration:none}.ant-btn:not([disabled]):active{outline:0;box-shadow:none}.ant-btn[disabled]{cursor:not-allowed}.ant-btn[disabled]>*{pointer-events:none}.ant-btn-lg{height:40px;padding:6.4px 15px;font-size:16px;border-radius:2px}.ant-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:2px}.ant-btn>a:only-child{color:currentcolor}.ant-btn>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn:hover,.ant-btn:focus{color:#165996;border-color:#165996;background:transparent}.ant-btn:hover>a:only-child,.ant-btn:focus>a:only-child{color:currentcolor}.ant-btn:hover>a:only-child:after,.ant-btn:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn:active{color:#388ed3;border-color:#388ed3;background:transparent}.ant-btn:active>a:only-child{color:currentcolor}.ant-btn:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn[disabled],.ant-btn[disabled]:hover,.ant-btn[disabled]:focus,.ant-btn[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn[disabled]>a:only-child,.ant-btn[disabled]:hover>a:only-child,.ant-btn[disabled]:focus>a:only-child,.ant-btn[disabled]:active>a:only-child{color:currentcolor}.ant-btn[disabled]>a:only-child:after,.ant-btn[disabled]:hover>a:only-child:after,.ant-btn[disabled]:focus>a:only-child:after,.ant-btn[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn:hover,.ant-btn:focus,.ant-btn:active{text-decoration:none;background:transparent}.ant-btn>span{display:inline-block}.ant-btn-primary{color:#fff;border-color:#177ddc;background:#177ddc;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px #0000000b}.ant-btn-primary>a:only-child{color:currentcolor}.ant-btn-primary>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-primary:hover,.ant-btn-primary:focus{color:#fff;border-color:#095cb5;background:#095cb5}.ant-btn-primary:hover>a:only-child,.ant-btn-primary:focus>a:only-child{color:currentcolor}.ant-btn-primary:hover>a:only-child:after,.ant-btn-primary:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-primary:active{color:#fff;border-color:#3c9be8;background:#3c9be8}.ant-btn-primary:active>a:only-child{color:currentcolor}.ant-btn-primary:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-primary[disabled],.ant-btn-primary[disabled]:hover,.ant-btn-primary[disabled]:focus,.ant-btn-primary[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-primary[disabled]>a:only-child,.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-primary[disabled]:active>a:only-child{color:currentcolor}.ant-btn-primary[disabled]>a:only-child:after,.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-primary[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child){border-right-color:#165996;border-left-color:#165996}.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child):disabled{border-color:#434343}.ant-btn-group .ant-btn-primary:first-child:not(:last-child){border-right-color:#165996}.ant-btn-group .ant-btn-primary:first-child:not(:last-child)[disabled]{border-right-color:#434343}.ant-btn-group .ant-btn-primary:last-child:not(:first-child),.ant-btn-group .ant-btn-primary+.ant-btn-primary{border-left-color:#165996}.ant-btn-group .ant-btn-primary:last-child:not(:first-child)[disabled],.ant-btn-group .ant-btn-primary+.ant-btn-primary[disabled]{border-left-color:#434343}.ant-btn-ghost{color:#ffffffd9;border-color:#434343;background:transparent}.ant-btn-ghost>a:only-child{color:currentcolor}.ant-btn-ghost>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-ghost:hover,.ant-btn-ghost:focus{color:#165996;border-color:#165996;background:transparent}.ant-btn-ghost:hover>a:only-child,.ant-btn-ghost:focus>a:only-child{color:currentcolor}.ant-btn-ghost:hover>a:only-child:after,.ant-btn-ghost:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-ghost:active{color:#388ed3;border-color:#388ed3;background:transparent}.ant-btn-ghost:active>a:only-child{color:currentcolor}.ant-btn-ghost:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-ghost[disabled],.ant-btn-ghost[disabled]:hover,.ant-btn-ghost[disabled]:focus,.ant-btn-ghost[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-ghost[disabled]>a:only-child,.ant-btn-ghost[disabled]:hover>a:only-child,.ant-btn-ghost[disabled]:focus>a:only-child,.ant-btn-ghost[disabled]:active>a:only-child{color:currentcolor}.ant-btn-ghost[disabled]>a:only-child:after,.ant-btn-ghost[disabled]:hover>a:only-child:after,.ant-btn-ghost[disabled]:focus>a:only-child:after,.ant-btn-ghost[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dashed{color:#ffffffd9;border-color:#434343;background:transparent;border-style:dashed}.ant-btn-dashed>a:only-child{color:currentcolor}.ant-btn-dashed>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dashed:hover,.ant-btn-dashed:focus{color:#165996;border-color:#165996;background:transparent}.ant-btn-dashed:hover>a:only-child,.ant-btn-dashed:focus>a:only-child{color:currentcolor}.ant-btn-dashed:hover>a:only-child:after,.ant-btn-dashed:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dashed:active{color:#388ed3;border-color:#388ed3;background:transparent}.ant-btn-dashed:active>a:only-child{color:currentcolor}.ant-btn-dashed:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dashed[disabled],.ant-btn-dashed[disabled]:hover,.ant-btn-dashed[disabled]:focus,.ant-btn-dashed[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-dashed[disabled]>a:only-child,.ant-btn-dashed[disabled]:hover>a:only-child,.ant-btn-dashed[disabled]:focus>a:only-child,.ant-btn-dashed[disabled]:active>a:only-child{color:currentcolor}.ant-btn-dashed[disabled]>a:only-child:after,.ant-btn-dashed[disabled]:hover>a:only-child:after,.ant-btn-dashed[disabled]:focus>a:only-child:after,.ant-btn-dashed[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-danger{color:#fff;border-color:#a61d24;background:#a61d24;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px #0000000b}.ant-btn-danger>a:only-child{color:currentcolor}.ant-btn-danger>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-danger:hover,.ant-btn-danger:focus{color:#fff;border-color:#800f19;background:#800f19}.ant-btn-danger:hover>a:only-child,.ant-btn-danger:focus>a:only-child{color:currentcolor}.ant-btn-danger:hover>a:only-child:after,.ant-btn-danger:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-danger:active{color:#fff;border-color:#b33b3d;background:#b33b3d}.ant-btn-danger:active>a:only-child{color:currentcolor}.ant-btn-danger:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-danger[disabled],.ant-btn-danger[disabled]:hover,.ant-btn-danger[disabled]:focus,.ant-btn-danger[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-danger[disabled]>a:only-child,.ant-btn-danger[disabled]:hover>a:only-child,.ant-btn-danger[disabled]:focus>a:only-child,.ant-btn-danger[disabled]:active>a:only-child{color:currentcolor}.ant-btn-danger[disabled]>a:only-child:after,.ant-btn-danger[disabled]:hover>a:only-child:after,.ant-btn-danger[disabled]:focus>a:only-child:after,.ant-btn-danger[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-link{color:#177ddc;border-color:transparent;background:transparent;box-shadow:none}.ant-btn-link>a:only-child{color:currentcolor}.ant-btn-link>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-link:hover,.ant-btn-link:focus{color:#165996;border-color:#165996;background:transparent}.ant-btn-link:hover>a:only-child,.ant-btn-link:focus>a:only-child{color:currentcolor}.ant-btn-link:hover>a:only-child:after,.ant-btn-link:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-link:active{color:#388ed3;border-color:#388ed3;background:transparent}.ant-btn-link:active>a:only-child{color:currentcolor}.ant-btn-link:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-link[disabled],.ant-btn-link[disabled]:hover,.ant-btn-link[disabled]:focus,.ant-btn-link[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-link:hover{background:transparent}.ant-btn-link:hover,.ant-btn-link:focus,.ant-btn-link:active{border-color:transparent}.ant-btn-link[disabled],.ant-btn-link[disabled]:hover,.ant-btn-link[disabled]:focus,.ant-btn-link[disabled]:active{color:#ffffff4d;border-color:transparent;background:transparent;text-shadow:none;box-shadow:none}.ant-btn-link[disabled]>a:only-child,.ant-btn-link[disabled]:hover>a:only-child,.ant-btn-link[disabled]:focus>a:only-child,.ant-btn-link[disabled]:active>a:only-child{color:currentcolor}.ant-btn-link[disabled]>a:only-child:after,.ant-btn-link[disabled]:hover>a:only-child:after,.ant-btn-link[disabled]:focus>a:only-child:after,.ant-btn-link[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-text{color:#ffffffd9;border-color:transparent;background:transparent;box-shadow:none}.ant-btn-text>a:only-child{color:currentcolor}.ant-btn-text>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-text:hover,.ant-btn-text:focus{color:#165996;border-color:#165996;background:transparent}.ant-btn-text:hover>a:only-child,.ant-btn-text:focus>a:only-child{color:currentcolor}.ant-btn-text:hover>a:only-child:after,.ant-btn-text:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-text:active{color:#388ed3;border-color:#388ed3;background:transparent}.ant-btn-text:active>a:only-child{color:currentcolor}.ant-btn-text:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-text[disabled],.ant-btn-text[disabled]:hover,.ant-btn-text[disabled]:focus,.ant-btn-text[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-text:hover,.ant-btn-text:focus{color:#ffffffd9;background:rgba(255,255,255,.03);border-color:transparent}.ant-btn-text:active{color:#ffffffd9;background:rgba(255,255,255,.04);border-color:transparent}.ant-btn-text[disabled],.ant-btn-text[disabled]:hover,.ant-btn-text[disabled]:focus,.ant-btn-text[disabled]:active{color:#ffffff4d;border-color:transparent;background:transparent;text-shadow:none;box-shadow:none}.ant-btn-text[disabled]>a:only-child,.ant-btn-text[disabled]:hover>a:only-child,.ant-btn-text[disabled]:focus>a:only-child,.ant-btn-text[disabled]:active>a:only-child{color:currentcolor}.ant-btn-text[disabled]>a:only-child:after,.ant-btn-text[disabled]:hover>a:only-child:after,.ant-btn-text[disabled]:focus>a:only-child:after,.ant-btn-text[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous{color:#a61d24;border-color:#a61d24;background:transparent}.ant-btn-dangerous>a:only-child{color:currentcolor}.ant-btn-dangerous>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous:hover,.ant-btn-dangerous:focus{color:#800f19;border-color:#800f19;background:transparent}.ant-btn-dangerous:hover>a:only-child,.ant-btn-dangerous:focus>a:only-child{color:currentcolor}.ant-btn-dangerous:hover>a:only-child:after,.ant-btn-dangerous:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous:active{color:#b33b3d;border-color:#b33b3d;background:transparent}.ant-btn-dangerous:active>a:only-child{color:currentcolor}.ant-btn-dangerous:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous[disabled],.ant-btn-dangerous[disabled]:hover,.ant-btn-dangerous[disabled]:focus,.ant-btn-dangerous[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-dangerous[disabled]>a:only-child,.ant-btn-dangerous[disabled]:hover>a:only-child,.ant-btn-dangerous[disabled]:focus>a:only-child,.ant-btn-dangerous[disabled]:active>a:only-child{color:currentcolor}.ant-btn-dangerous[disabled]>a:only-child:after,.ant-btn-dangerous[disabled]:hover>a:only-child:after,.ant-btn-dangerous[disabled]:focus>a:only-child:after,.ant-btn-dangerous[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-primary{color:#fff;border-color:#a61d24;background:#a61d24;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px #0000000b}.ant-btn-dangerous.ant-btn-primary>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-primary>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-primary:hover,.ant-btn-dangerous.ant-btn-primary:focus{color:#fff;border-color:#800f19;background:#800f19}.ant-btn-dangerous.ant-btn-primary:hover>a:only-child,.ant-btn-dangerous.ant-btn-primary:focus>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-primary:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-primary:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-primary:active{color:#fff;border-color:#b33b3d;background:#b33b3d}.ant-btn-dangerous.ant-btn-primary:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-primary:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-primary[disabled],.ant-btn-dangerous.ant-btn-primary[disabled]:hover,.ant-btn-dangerous.ant-btn-primary[disabled]:focus,.ant-btn-dangerous.ant-btn-primary[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-dangerous.ant-btn-primary[disabled]>a:only-child,.ant-btn-dangerous.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-dangerous.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-dangerous.ant-btn-primary[disabled]:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-primary[disabled]>a:only-child:after,.ant-btn-dangerous.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-dangerous.ant-btn-primary[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-link{color:#a61d24;border-color:transparent;background:transparent;box-shadow:none}.ant-btn-dangerous.ant-btn-link>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-link>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-link:hover,.ant-btn-dangerous.ant-btn-link:focus{color:#165996;border-color:#165996;background:transparent}.ant-btn-dangerous.ant-btn-link:active{color:#388ed3;border-color:#388ed3;background:transparent}.ant-btn-dangerous.ant-btn-link[disabled],.ant-btn-dangerous.ant-btn-link[disabled]:hover,.ant-btn-dangerous.ant-btn-link[disabled]:focus,.ant-btn-dangerous.ant-btn-link[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-dangerous.ant-btn-link:hover,.ant-btn-dangerous.ant-btn-link:focus{color:#800f19;border-color:transparent;background:transparent}.ant-btn-dangerous.ant-btn-link:hover>a:only-child,.ant-btn-dangerous.ant-btn-link:focus>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-link:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-link:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-link:active{color:#b33b3d;border-color:transparent;background:transparent}.ant-btn-dangerous.ant-btn-link:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-link:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-link[disabled],.ant-btn-dangerous.ant-btn-link[disabled]:hover,.ant-btn-dangerous.ant-btn-link[disabled]:focus,.ant-btn-dangerous.ant-btn-link[disabled]:active{color:#ffffff4d;border-color:transparent;background:transparent;text-shadow:none;box-shadow:none}.ant-btn-dangerous.ant-btn-link[disabled]>a:only-child,.ant-btn-dangerous.ant-btn-link[disabled]:hover>a:only-child,.ant-btn-dangerous.ant-btn-link[disabled]:focus>a:only-child,.ant-btn-dangerous.ant-btn-link[disabled]:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-link[disabled]>a:only-child:after,.ant-btn-dangerous.ant-btn-link[disabled]:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-link[disabled]:focus>a:only-child:after,.ant-btn-dangerous.ant-btn-link[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-text{color:#a61d24;border-color:transparent;background:transparent;box-shadow:none}.ant-btn-dangerous.ant-btn-text>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-text>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-text:hover,.ant-btn-dangerous.ant-btn-text:focus{color:#165996;border-color:#165996;background:transparent}.ant-btn-dangerous.ant-btn-text:active{color:#388ed3;border-color:#388ed3;background:transparent}.ant-btn-dangerous.ant-btn-text[disabled],.ant-btn-dangerous.ant-btn-text[disabled]:hover,.ant-btn-dangerous.ant-btn-text[disabled]:focus,.ant-btn-dangerous.ant-btn-text[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-dangerous.ant-btn-text:hover,.ant-btn-dangerous.ant-btn-text:focus{color:#800f19;border-color:transparent;background:rgba(255,255,255,.03)}.ant-btn-dangerous.ant-btn-text:hover>a:only-child,.ant-btn-dangerous.ant-btn-text:focus>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-text:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-text:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-text:active{color:#b33b3d;border-color:transparent;background:rgba(255,255,255,.04)}.ant-btn-dangerous.ant-btn-text:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-text:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dangerous.ant-btn-text[disabled],.ant-btn-dangerous.ant-btn-text[disabled]:hover,.ant-btn-dangerous.ant-btn-text[disabled]:focus,.ant-btn-dangerous.ant-btn-text[disabled]:active{color:#ffffff4d;border-color:transparent;background:transparent;text-shadow:none;box-shadow:none}.ant-btn-dangerous.ant-btn-text[disabled]>a:only-child,.ant-btn-dangerous.ant-btn-text[disabled]:hover>a:only-child,.ant-btn-dangerous.ant-btn-text[disabled]:focus>a:only-child,.ant-btn-dangerous.ant-btn-text[disabled]:active>a:only-child{color:currentcolor}.ant-btn-dangerous.ant-btn-text[disabled]>a:only-child:after,.ant-btn-dangerous.ant-btn-text[disabled]:hover>a:only-child:after,.ant-btn-dangerous.ant-btn-text[disabled]:focus>a:only-child:after,.ant-btn-dangerous.ant-btn-text[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-icon-only{width:32px;height:32px;padding:2.4px 0;font-size:16px;border-radius:2px;vertical-align:-3px}.ant-btn-icon-only>*{font-size:16px}.ant-btn-icon-only.ant-btn-lg{width:40px;height:40px;padding:4.9px 0;font-size:18px;border-radius:2px}.ant-btn-icon-only.ant-btn-lg>*{font-size:18px}.ant-btn-icon-only.ant-btn-sm{width:24px;height:24px;padding:0;font-size:14px;border-radius:2px}.ant-btn-icon-only.ant-btn-sm>*{font-size:14px}.ant-btn-icon-only>.anticon{display:flex;justify-content:center}a.ant-btn-icon-only{vertical-align:-1px}a.ant-btn-icon-only>.anticon{display:inline}.ant-btn-round{height:32px;padding:4px 16px;font-size:14px;border-radius:32px}.ant-btn-round.ant-btn-lg{height:40px;padding:6.4px 20px;font-size:16px;border-radius:40px}.ant-btn-round.ant-btn-sm{height:24px;padding:0 12px;font-size:14px;border-radius:24px}.ant-btn-round.ant-btn-icon-only{width:auto}.ant-btn-circle{min-width:32px;padding-right:0;padding-left:0;text-align:center;border-radius:50%}.ant-btn-circle.ant-btn-lg{min-width:40px;border-radius:50%}.ant-btn-circle.ant-btn-sm{min-width:24px;border-radius:50%}.ant-btn:before{position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:1;display:none;background:#141414;border-radius:inherit;opacity:.35;transition:opacity .2s;content:"";pointer-events:none}.ant-btn .anticon{transition:margin-left .3s cubic-bezier(.645,.045,.355,1)}.ant-btn .anticon.anticon-plus>svg,.ant-btn .anticon.anticon-minus>svg{shape-rendering:optimizespeed}.ant-btn.ant-btn-loading{position:relative;cursor:default}.ant-btn.ant-btn-loading:before{display:block}.ant-btn>.ant-btn-loading-icon{transition:width .3s cubic-bezier(.645,.045,.355,1),opacity .3s cubic-bezier(.645,.045,.355,1)}.ant-btn>.ant-btn-loading-icon .anticon{padding-right:8px;animation:none}.ant-btn>.ant-btn-loading-icon .anticon svg{animation:loadingCircle 1s infinite linear}.ant-btn>.ant-btn-loading-icon:only-child .anticon{padding-right:0}.ant-btn-group{position:relative;display:inline-flex}.ant-btn-group>.ant-btn,.ant-btn-group>span>.ant-btn{position:relative}.ant-btn-group>.ant-btn:hover,.ant-btn-group>span>.ant-btn:hover,.ant-btn-group>.ant-btn:focus,.ant-btn-group>span>.ant-btn:focus,.ant-btn-group>.ant-btn:active,.ant-btn-group>span>.ant-btn:active{z-index:2}.ant-btn-group>.ant-btn[disabled],.ant-btn-group>span>.ant-btn[disabled]{z-index:0}.ant-btn-group .ant-btn-icon-only{font-size:14px}.ant-btn-group-lg>.ant-btn,.ant-btn-group-lg>span>.ant-btn{height:40px;padding:6.4px 15px;font-size:16px;border-radius:0}.ant-btn-group-lg .ant-btn.ant-btn-icon-only{width:40px;height:40px;padding-right:0;padding-left:0}.ant-btn-group-sm>.ant-btn,.ant-btn-group-sm>span>.ant-btn{height:24px;padding:0 7px;font-size:14px;border-radius:0}.ant-btn-group-sm>.ant-btn>.anticon,.ant-btn-group-sm>span>.ant-btn>.anticon{font-size:14px}.ant-btn-group-sm .ant-btn.ant-btn-icon-only{width:24px;height:24px;padding-right:0;padding-left:0}.ant-btn-group .ant-btn+.ant-btn,.ant-btn+.ant-btn-group,.ant-btn-group span+.ant-btn,.ant-btn-group .ant-btn+span,.ant-btn-group>span+span,.ant-btn-group+.ant-btn,.ant-btn-group+.ant-btn-group{margin-left:-1px}.ant-btn-group .ant-btn-primary+.ant-btn:not(.ant-btn-primary):not([disabled]){border-left-color:transparent}.ant-btn-group .ant-btn{border-radius:0}.ant-btn-group>.ant-btn:first-child,.ant-btn-group>span:first-child>.ant-btn{margin-left:0}.ant-btn-group>.ant-btn:only-child{border-radius:2px}.ant-btn-group>span:only-child>.ant-btn{border-radius:2px}.ant-btn-group>.ant-btn:first-child:not(:last-child),.ant-btn-group>span:first-child:not(:last-child)>.ant-btn{border-top-left-radius:2px;border-bottom-left-radius:2px}.ant-btn-group>.ant-btn:last-child:not(:first-child),.ant-btn-group>span:last-child:not(:first-child)>.ant-btn{border-top-right-radius:2px;border-bottom-right-radius:2px}.ant-btn-group-sm>.ant-btn:only-child{border-radius:2px}.ant-btn-group-sm>span:only-child>.ant-btn{border-radius:2px}.ant-btn-group-sm>.ant-btn:first-child:not(:last-child),.ant-btn-group-sm>span:first-child:not(:last-child)>.ant-btn{border-top-left-radius:2px;border-bottom-left-radius:2px}.ant-btn-group-sm>.ant-btn:last-child:not(:first-child),.ant-btn-group-sm>span:last-child:not(:first-child)>.ant-btn{border-top-right-radius:2px;border-bottom-right-radius:2px}.ant-btn-group>.ant-btn-group{float:left}.ant-btn-group>.ant-btn-group:not(:first-child):not(:last-child)>.ant-btn{border-radius:0}.ant-btn-group>.ant-btn-group:first-child:not(:last-child)>.ant-btn:last-child{padding-right:8px;border-top-right-radius:0;border-bottom-right-radius:0}.ant-btn-group>.ant-btn-group:last-child:not(:first-child)>.ant-btn:first-child{padding-left:8px;border-top-left-radius:0;border-bottom-left-radius:0}.ant-btn-rtl.ant-btn-group .ant-btn+.ant-btn,.ant-btn-rtl.ant-btn+.ant-btn-group,.ant-btn-rtl.ant-btn-group span+.ant-btn,.ant-btn-rtl.ant-btn-group .ant-btn+span,.ant-btn-rtl.ant-btn-group>span+span,.ant-btn-rtl.ant-btn-group+.ant-btn,.ant-btn-rtl.ant-btn-group+.ant-btn-group,.ant-btn-group-rtl.ant-btn-group .ant-btn+.ant-btn,.ant-btn-group-rtl.ant-btn+.ant-btn-group,.ant-btn-group-rtl.ant-btn-group span+.ant-btn,.ant-btn-group-rtl.ant-btn-group .ant-btn+span,.ant-btn-group-rtl.ant-btn-group>span+span,.ant-btn-group-rtl.ant-btn-group+.ant-btn,.ant-btn-group-rtl.ant-btn-group+.ant-btn-group{margin-right:-1px;margin-left:auto}.ant-btn-group.ant-btn-group-rtl{direction:rtl}.ant-btn-group-rtl.ant-btn-group>.ant-btn:first-child:not(:last-child),.ant-btn-group-rtl.ant-btn-group>span:first-child:not(:last-child)>.ant-btn{border-radius:0 2px 2px 0}.ant-btn-group-rtl.ant-btn-group>.ant-btn:last-child:not(:first-child),.ant-btn-group-rtl.ant-btn-group>span:last-child:not(:first-child)>.ant-btn{border-radius:2px 0 0 2px}.ant-btn-group-rtl.ant-btn-group-sm>.ant-btn:first-child:not(:last-child),.ant-btn-group-rtl.ant-btn-group-sm>span:first-child:not(:last-child)>.ant-btn{border-radius:0 2px 2px 0}.ant-btn-group-rtl.ant-btn-group-sm>.ant-btn:last-child:not(:first-child),.ant-btn-group-rtl.ant-btn-group-sm>span:last-child:not(:first-child)>.ant-btn{border-radius:2px 0 0 2px}.ant-btn:focus>span,.ant-btn:active>span{position:relative}.ant-btn>.anticon+span,.ant-btn>span+.anticon{margin-left:8px}.ant-btn.ant-btn-background-ghost{color:#ffffffd9;border-color:#ffffff40}.ant-btn.ant-btn-background-ghost,.ant-btn.ant-btn-background-ghost:hover,.ant-btn.ant-btn-background-ghost:active,.ant-btn.ant-btn-background-ghost:focus{background:transparent}.ant-btn.ant-btn-background-ghost:hover,.ant-btn.ant-btn-background-ghost:focus{color:#3c9be8;border-color:#3c9be8}.ant-btn.ant-btn-background-ghost:active{color:#095cb5;border-color:#095cb5}.ant-btn.ant-btn-background-ghost[disabled]{color:#ffffff4d;background:transparent;border-color:#434343}.ant-btn-background-ghost.ant-btn-primary{color:#177ddc;border-color:#177ddc;text-shadow:none}.ant-btn-background-ghost.ant-btn-primary>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-primary>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-primary:hover,.ant-btn-background-ghost.ant-btn-primary:focus{color:#095cb5;border-color:#095cb5}.ant-btn-background-ghost.ant-btn-primary:hover>a:only-child,.ant-btn-background-ghost.ant-btn-primary:focus>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-primary:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-primary:active{color:#3c9be8;border-color:#3c9be8}.ant-btn-background-ghost.ant-btn-primary:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-primary:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-primary[disabled],.ant-btn-background-ghost.ant-btn-primary[disabled]:hover,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus,.ant-btn-background-ghost.ant-btn-primary[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-primary[disabled]>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-primary[disabled]>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger{color:#a61d24;border-color:#a61d24;text-shadow:none}.ant-btn-background-ghost.ant-btn-danger>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-danger>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger:hover,.ant-btn-background-ghost.ant-btn-danger:focus{color:#800f19;border-color:#800f19}.ant-btn-background-ghost.ant-btn-danger:hover>a:only-child,.ant-btn-background-ghost.ant-btn-danger:focus>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-danger:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger:active{color:#b33b3d;border-color:#b33b3d}.ant-btn-background-ghost.ant-btn-danger:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-danger:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger[disabled],.ant-btn-background-ghost.ant-btn-danger[disabled]:hover,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus,.ant-btn-background-ghost.ant-btn-danger[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-danger[disabled]>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-danger[disabled]>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous{color:#a61d24;border-color:#a61d24;text-shadow:none}.ant-btn-background-ghost.ant-btn-dangerous>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous:hover,.ant-btn-background-ghost.ant-btn-dangerous:focus{color:#800f19;border-color:#800f19}.ant-btn-background-ghost.ant-btn-dangerous:hover>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous:focus>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous:active{color:#b33b3d;border-color:#b33b3d}.ant-btn-background-ghost.ant-btn-dangerous:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous[disabled],.ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-dangerous[disabled]>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous[disabled]>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link{color:#a61d24;border-color:transparent;text-shadow:none}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus{color:#800f19;border-color:transparent}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active{color:#b33b3d;border-color:transparent}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled],.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active{color:#ffffff4d;border-color:#434343;background:rgba(255,255,255,.08);text-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active>a:only-child{color:currentcolor}.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-two-chinese-chars:first-letter{letter-spacing:.34em}.ant-btn-two-chinese-chars>*:not(.anticon){margin-right:-.34em;letter-spacing:.34em}.ant-btn.ant-btn-block{width:100%}.ant-btn:empty{display:inline-block;width:0;visibility:hidden;content:" "}a.ant-btn{padding-top:.01px!important;line-height:30px}a.ant-btn-lg{line-height:38px}a.ant-btn-sm{line-height:22px}.ant-btn-rtl{direction:rtl}.ant-btn-group-rtl.ant-btn-group .ant-btn-primary:last-child:not(:first-child),.ant-btn-group-rtl.ant-btn-group .ant-btn-primary+.ant-btn-primary{border-right-color:#165996;border-left-color:#434343}.ant-btn-group-rtl.ant-btn-group .ant-btn-primary:last-child:not(:first-child)[disabled],.ant-btn-group-rtl.ant-btn-group .ant-btn-primary+.ant-btn-primary[disabled]{border-right-color:#434343;border-left-color:#165996}.ant-btn-rtl.ant-btn>.ant-btn-loading-icon .anticon{padding-right:0;padding-left:8px}.ant-btn>.ant-btn-loading-icon:only-child .anticon{padding-right:0;padding-left:0}.ant-btn-rtl.ant-btn>.anticon+span,.ant-btn-rtl.ant-btn>span+.anticon{margin-right:8px;margin-left:0}/*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/calendar/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-picker-calendar{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";background:#141414}.ant-picker-calendar-header{display:flex;justify-content:flex-end;padding:12px 0}.ant-picker-calendar-header .ant-picker-calendar-year-select{min-width:80px}.ant-picker-calendar-header .ant-picker-calendar-month-select{min-width:70px;margin-left:8px}.ant-picker-calendar-header .ant-picker-calendar-mode-switch{margin-left:8px}.ant-picker-calendar .ant-picker-panel{background:#141414;border:0;border-top:1px solid #303030;border-radius:0}.ant-picker-calendar .ant-picker-panel .ant-picker-month-panel,.ant-picker-calendar .ant-picker-panel .ant-picker-date-panel{width:auto}.ant-picker-calendar .ant-picker-panel .ant-picker-body{padding:8px 0}.ant-picker-calendar .ant-picker-panel .ant-picker-content{width:100%}.ant-picker-calendar-mini{border-radius:2px}.ant-picker-calendar-mini .ant-picker-calendar-header{padding-right:8px;padding-left:8px}.ant-picker-calendar-mini .ant-picker-panel{border-radius:0 0 2px 2px}.ant-picker-calendar-mini .ant-picker-content{height:256px}.ant-picker-calendar-mini .ant-picker-content th{height:auto;padding:0;line-height:18px}.ant-picker-calendar-full .ant-picker-panel{display:block;width:100%;text-align:right;background:#141414;border:0}.ant-picker-calendar-full .ant-picker-panel .ant-picker-body th,.ant-picker-calendar-full .ant-picker-panel .ant-picker-body td{padding:0}.ant-picker-calendar-full .ant-picker-panel .ant-picker-body th{height:auto;padding:0 12px 5px 0;line-height:18px}.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell:before{display:none}.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell:hover .ant-picker-calendar-date{background:rgba(255,255,255,.08)}.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell .ant-picker-calendar-date-today:before{display:none}.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date-today,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date-today{background:#111b26}.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date .ant-picker-calendar-date-value,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date .ant-picker-calendar-date-value,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date-today .ant-picker-calendar-date-value,.ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date-today .ant-picker-calendar-date-value{color:#177ddc}.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date{display:block;width:auto;height:auto;margin:0 4px;padding:4px 8px 0;border:0;border-top:2px solid #303030;border-radius:0;transition:background .3s}.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-value{line-height:24px;transition:color .3s}.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-content{position:static;width:auto;height:86px;overflow-y:auto;color:#ffffffd9;line-height:1.5715;text-align:left}.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today{border-color:#177ddc}.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today .ant-picker-calendar-date-value{color:#ffffffd9}@media only screen and (max-width: 480px){.ant-picker-calendar-header{display:block}.ant-picker-calendar-header .ant-picker-calendar-year-select{width:50%}.ant-picker-calendar-header .ant-picker-calendar-month-select{width:calc(50% - 8px)}.ant-picker-calendar-header .ant-picker-calendar-mode-switch{width:100%;margin-top:8px;margin-left:0}.ant-picker-calendar-header .ant-picker-calendar-mode-switch>label{width:50%;text-align:center}}.ant-picker-calendar-rtl{direction:rtl}.ant-picker-calendar-rtl .ant-picker-calendar-header .ant-picker-calendar-month-select,.ant-picker-calendar-rtl .ant-picker-calendar-header .ant-picker-calendar-mode-switch{margin-right:8px;margin-left:0}.ant-picker-calendar-rtl.ant-picker-calendar-full .ant-picker-panel{text-align:left}.ant-picker-calendar-rtl.ant-picker-calendar-full .ant-picker-panel .ant-picker-body th{padding:0 0 5px 12px}.ant-picker-calendar-rtl.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-content{text-align:right}/*!**********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/radio/style/index.less ***! + \\**********************************************************************************************************************************************************************************************************************************************************/.ant-radio-group{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block;font-size:0}.ant-radio-group .ant-badge-count{z-index:1}.ant-radio-group>.ant-badge:not(:first-child)>.ant-radio-button-wrapper{border-left:none}.ant-radio-wrapper{box-sizing:border-box;margin:0 8px 0 0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-flex;align-items:baseline;cursor:pointer}.ant-radio-wrapper-disabled{cursor:not-allowed}.ant-radio-wrapper:after{display:inline-block;width:0;overflow:hidden;content:" "}.ant-radio{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;display:inline-block;outline:none;cursor:pointer}.ant-radio-wrapper:hover .ant-radio,.ant-radio:hover .ant-radio-inner,.ant-radio-input:focus+.ant-radio-inner{border-color:#177ddc}.ant-radio-input:focus+.ant-radio-inner{box-shadow:0 0 0 3px #111b26}.ant-radio-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #177ddc;border-radius:50%;visibility:hidden;animation:antRadioEffect .36s ease-in-out;animation-fill-mode:both;content:""}.ant-radio:hover:after,.ant-radio-wrapper:hover .ant-radio:after{visibility:visible}.ant-radio-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:transparent;border-color:#434343;border-style:solid;border-width:1px;border-radius:50%;transition:all .3s}.ant-radio-inner:after{position:absolute;top:50%;left:50%;display:block;width:16px;height:16px;margin-top:-8px;margin-left:-8px;background-color:#177ddc;border-top:0;border-left:0;border-radius:16px;transform:scale(0);opacity:0;transition:all .3s cubic-bezier(.78,.14,.15,.86);content:" "}.ant-radio-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;cursor:pointer;opacity:0}.ant-radio-checked .ant-radio-inner{border-color:#177ddc}.ant-radio-checked .ant-radio-inner:after{transform:scale(.5);opacity:1;transition:all .3s cubic-bezier(.78,.14,.15,.86)}.ant-radio-disabled{cursor:not-allowed}.ant-radio-disabled .ant-radio-inner{background-color:#ffffff14;border-color:#434343!important;cursor:not-allowed}.ant-radio-disabled .ant-radio-inner:after{background-color:#fff3}.ant-radio-disabled .ant-radio-input{cursor:not-allowed}.ant-radio-disabled+span{color:#ffffff4d;cursor:not-allowed}span.ant-radio+*{padding-right:8px;padding-left:8px}.ant-radio-button-wrapper{position:relative;display:inline-block;height:32px;margin:0;padding:0 15px;color:#ffffffd9;font-size:14px;line-height:30px;background:transparent;border:1px solid #434343;border-top-width:1.02px;border-left-width:0;cursor:pointer;transition:color .3s,background .3s,border-color .3s,box-shadow .3s}.ant-radio-button-wrapper a{color:#ffffffd9}.ant-radio-button-wrapper>.ant-radio-button{position:absolute;top:0;left:0;z-index:-1;width:100%;height:100%}.ant-radio-group-large .ant-radio-button-wrapper{height:40px;font-size:16px;line-height:38px}.ant-radio-group-small .ant-radio-button-wrapper{height:24px;padding:0 7px;line-height:22px}.ant-radio-button-wrapper:not(:first-child):before{position:absolute;top:-1px;left:-1px;display:block;box-sizing:content-box;width:1px;height:100%;padding:1px 0;background-color:#434343;transition:background-color .3s;content:""}.ant-radio-button-wrapper:first-child{border-left:1px solid #434343;border-radius:2px 0 0 2px}.ant-radio-button-wrapper:last-child{border-radius:0 2px 2px 0}.ant-radio-button-wrapper:first-child:last-child{border-radius:2px}.ant-radio-button-wrapper:hover{position:relative;color:#177ddc}.ant-radio-button-wrapper:focus-within{box-shadow:0 0 0 3px #111b26}.ant-radio-button-wrapper .ant-radio-inner,.ant-radio-button-wrapper input[type=checkbox],.ant-radio-button-wrapper input[type=radio]{width:0;height:0;opacity:0;pointer-events:none}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){z-index:1;color:#177ddc;background:transparent;border-color:#177ddc}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):before{background-color:#177ddc}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child{border-color:#177ddc}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#165996;border-color:#165996}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover:before{background-color:#165996}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#388ed3;border-color:#388ed3}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active:before{background-color:#388ed3}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{box-shadow:0 0 0 3px #111b26}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){color:#fff;background:#177ddc;border-color:#177ddc}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#fff;background:#165996;border-color:#165996}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#fff;background:#388ed3;border-color:#388ed3}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{box-shadow:0 0 0 3px #111b26}.ant-radio-button-wrapper-disabled{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;cursor:not-allowed}.ant-radio-button-wrapper-disabled:first-child,.ant-radio-button-wrapper-disabled:hover{color:#ffffff4d;background-color:#ffffff14;border-color:#434343}.ant-radio-button-wrapper-disabled:first-child{border-left-color:#434343}.ant-radio-button-wrapper-disabled.ant-radio-button-wrapper-checked{color:#ffffff4d;background-color:#fff3;border-color:#434343;box-shadow:none}@keyframes antRadioEffect{0%{transform:scale(1);opacity:.5}to{transform:scale(1.6);opacity:0}}.ant-radio-group.ant-radio-group-rtl{direction:rtl}.ant-radio-wrapper.ant-radio-wrapper-rtl{margin-right:0;margin-left:8px;direction:rtl}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl{border-right-width:0;border-left-width:1px}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:not(:first-child):before{right:-1px;left:0}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:first-child{border-right:1px solid #434343;border-radius:0 2px 2px 0}.ant-radio-button-wrapper-checked:not([class*=" ant-radio-button-wrapper-disabled"]).ant-radio-button-wrapper:first-child{border-right-color:#165996}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:last-child{border-radius:2px 0 0 2px}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper-disabled:first-child{border-right-color:#434343}/*!****************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/date-picker/style/index.less ***! + \\****************************************************************************************************************************************************************************************************************************************************************/.ant-picker{box-sizing:border-box;margin:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";padding:4px 11px;position:relative;display:inline-flex;align-items:center;background:transparent;border:1px solid #434343;border-radius:2px;transition:border .3s,box-shadow .3s}.ant-picker:hover,.ant-picker-focused{border-color:#165996;border-right-width:1px!important}.ant-picker-focused{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-picker.ant-picker-disabled{background:rgba(255,255,255,.08);border-color:#434343;cursor:not-allowed}.ant-picker.ant-picker-disabled .ant-picker-suffix{color:#ffffff4d}.ant-picker.ant-picker-borderless{background-color:transparent!important;border-color:transparent!important;box-shadow:none!important}.ant-picker-input{position:relative;display:inline-flex;align-items:center;width:100%}.ant-picker-input>input{position:relative;display:inline-block;width:100%;min-width:0;color:#ffffffd9;font-size:14px;line-height:1.5715;background-color:transparent;background-image:none;border:1px solid #434343;border-radius:2px;transition:all .3s;flex:auto;min-width:1px;height:auto;padding:0;background:transparent;border:0}.ant-picker-input>input::-moz-placeholder{opacity:1}.ant-picker-input>input::placeholder{color:#ffffff4d;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-picker-input>input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-picker-input>input:placeholder-shown{text-overflow:ellipsis}.ant-picker-input>input:hover{border-color:#165996;border-right-width:1px!important}.ant-picker-input>input:focus,.ant-picker-input>input-focused{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-picker-input>input-disabled{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-picker-input>input-disabled:hover{border-color:#434343;border-right-width:1px!important}.ant-picker-input>input[disabled]{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-picker-input>input[disabled]:hover{border-color:#434343;border-right-width:1px!important}.ant-picker-input>input-borderless,.ant-picker-input>input-borderless:hover,.ant-picker-input>input-borderless:focus,.ant-picker-input>input-borderless-focused,.ant-picker-input>input-borderless-disabled,.ant-picker-input>input-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-picker-input>input{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-picker-input>input-lg{padding:6.5px 11px;font-size:16px}.ant-picker-input>input-sm{padding:0 7px}.ant-picker-input>input:focus{box-shadow:none}.ant-picker-input>input[disabled]{background:transparent}.ant-picker-input:hover .ant-picker-clear{opacity:1}.ant-picker-input-placeholder>input{color:#ffffff4d}.ant-picker-large{padding:6.5px 11px}.ant-picker-large .ant-picker-input>input{font-size:16px}.ant-picker-small{padding:0 7px}.ant-picker-suffix{align-self:center;margin-left:4px;color:#ffffff4d;line-height:1;pointer-events:none}.ant-picker-suffix>*{vertical-align:top}.ant-picker-clear{position:absolute;top:50%;right:0;color:#ffffff4d;line-height:1;background:#141414;transform:translateY(-50%);cursor:pointer;opacity:0;transition:opacity .3s,color .3s}.ant-picker-clear>*{vertical-align:top}.ant-picker-clear:hover{color:#ffffff73}.ant-picker-separator{position:relative;display:inline-block;width:1em;height:16px;color:#ffffff4d;font-size:16px;vertical-align:top;cursor:default}.ant-picker-focused .ant-picker-separator{color:#ffffff73}.ant-picker-disabled .ant-picker-range-separator .ant-picker-separator{cursor:not-allowed}.ant-picker-range{position:relative;display:inline-flex}.ant-picker-range .ant-picker-clear{right:11px}.ant-picker-range:hover .ant-picker-clear{opacity:1}.ant-picker-range .ant-picker-active-bar{bottom:-1px;height:2px;margin-left:11px;background:#177ddc;opacity:0;transition:all .3s ease-out;pointer-events:none}.ant-picker-range.ant-picker-focused .ant-picker-active-bar{opacity:1}.ant-picker-range-separator{align-items:center;padding:0 8px;line-height:1}.ant-picker-range.ant-picker-small .ant-picker-clear{right:7px}.ant-picker-range.ant-picker-small .ant-picker-active-bar{margin-left:7px}.ant-picker-dropdown{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;z-index:1050}.ant-picker-dropdown-hidden{display:none}.ant-picker-dropdown-placement-bottomLeft .ant-picker-range-arrow{top:1.66666667px;display:block;transform:rotate(-45deg)}.ant-picker-dropdown-placement-topLeft .ant-picker-range-arrow{bottom:1.66666667px;display:block;transform:rotate(135deg)}.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-topLeft,.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-topRight,.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-topLeft,.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-topRight{animation-name:antSlideDownIn}.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-bottomLeft,.ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-bottomRight,.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-bottomLeft,.ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-bottomRight{animation-name:antSlideUpIn}.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-topLeft,.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-topRight{animation-name:antSlideDownOut}.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-bottomLeft,.ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-bottomRight{animation-name:antSlideUpOut}.ant-picker-dropdown-range{padding:6.66666667px 0}.ant-picker-dropdown-range-hidden{display:none}.ant-picker-dropdown .ant-picker-panel>.ant-picker-time-panel{padding-top:4px}.ant-picker-ranges{margin-bottom:0;padding:4px 12px;overflow:hidden;line-height:34px;text-align:left;list-style:none}.ant-picker-ranges>li{display:inline-block}.ant-picker-ranges .ant-picker-preset>.ant-tag-blue{color:#177ddc;background:#111b26;border-color:#153450;cursor:pointer}.ant-picker-ranges .ant-picker-ok{float:right;margin-left:8px}.ant-picker-range-wrapper{display:flex}.ant-picker-range-arrow{position:absolute;z-index:1;display:none;width:10px;height:10px;margin-left:16.5px;box-shadow:2px -2px 6px #0000000f;transition:left .3s ease-out}.ant-picker-range-arrow:after{position:absolute;top:1px;right:1px;width:10px;height:10px;border:5px solid #303030;border-color:#1f1f1f #1f1f1f transparent transparent;content:""}.ant-picker-panel-container{overflow:hidden;vertical-align:top;background:#1f1f1f;border-radius:2px;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003;transition:margin .3s}.ant-picker-panel-container .ant-picker-panels{display:inline-flex;flex-wrap:nowrap;direction:ltr}.ant-picker-panel-container .ant-picker-panel{vertical-align:top;background:transparent;border-width:0 0 1px 0;border-radius:0}.ant-picker-panel-container .ant-picker-panel .ant-picker-content,.ant-picker-panel-container .ant-picker-panel table{text-align:center}.ant-picker-panel-container .ant-picker-panel-focused{border-color:#303030}.ant-picker-panel{display:inline-flex;flex-direction:column;text-align:center;background:#1f1f1f;border:1px solid #303030;border-radius:2px;outline:none}.ant-picker-panel-focused{border-color:#177ddc}.ant-picker-decade-panel,.ant-picker-year-panel,.ant-picker-quarter-panel,.ant-picker-month-panel,.ant-picker-week-panel,.ant-picker-date-panel,.ant-picker-time-panel{display:flex;flex-direction:column;width:280px}.ant-picker-header{display:flex;padding:0 8px;color:#ffffffd9;border-bottom:1px solid #303030}.ant-picker-header>*{flex:none}.ant-picker-header button{padding:0;color:#ffffff4d;line-height:40px;background:transparent;border:0;cursor:pointer;transition:color .3s}.ant-picker-header>button{min-width:1.6em;font-size:14px}.ant-picker-header>button:hover{color:#ffffffd9}.ant-picker-header-view{flex:auto;font-weight:500;line-height:40px}.ant-picker-header-view button{color:inherit;font-weight:inherit}.ant-picker-header-view button:not(:first-child){margin-left:8px}.ant-picker-header-view button:hover{color:#177ddc}.ant-picker-prev-icon,.ant-picker-next-icon,.ant-picker-super-prev-icon,.ant-picker-super-next-icon{position:relative;display:inline-block;width:7px;height:7px}.ant-picker-prev-icon:before,.ant-picker-next-icon:before,.ant-picker-super-prev-icon:before,.ant-picker-super-next-icon:before{position:absolute;top:0;left:0;display:inline-block;width:7px;height:7px;border:0 solid currentcolor;border-width:1.5px 0 0 1.5px;content:""}.ant-picker-super-prev-icon:after,.ant-picker-super-next-icon:after{position:absolute;top:4px;left:4px;display:inline-block;width:7px;height:7px;border:0 solid currentcolor;border-width:1.5px 0 0 1.5px;content:""}.ant-picker-prev-icon,.ant-picker-super-prev-icon{transform:rotate(-45deg)}.ant-picker-next-icon,.ant-picker-super-next-icon{transform:rotate(135deg)}.ant-picker-content{width:100%;table-layout:fixed;border-collapse:collapse}.ant-picker-content th,.ant-picker-content td{position:relative;min-width:24px;font-weight:400}.ant-picker-content th{height:30px;color:#ffffffd9;line-height:30px}.ant-picker-cell{padding:3px 0;color:#ffffff4d;cursor:pointer}.ant-picker-cell-in-view{color:#ffffffd9}.ant-picker-cell:before{position:absolute;top:50%;right:0;left:0;z-index:1;height:24px;transform:translateY(-50%);transition:all .3s;content:""}.ant-picker-cell:hover:not(.ant-picker-cell-in-view) .ant-picker-cell-inner,.ant-picker-cell:hover:not(.ant-picker-cell-selected):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end):not(.ant-picker-cell-range-hover-start):not(.ant-picker-cell-range-hover-end) .ant-picker-cell-inner{background:rgba(255,255,255,.08)}.ant-picker-cell-in-view.ant-picker-cell-today .ant-picker-cell-inner:before{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;border:1px solid #177ddc;border-radius:2px;content:""}.ant-picker-cell-in-view.ant-picker-cell-in-range{position:relative}.ant-picker-cell-in-view.ant-picker-cell-in-range:before{background:#111b26}.ant-picker-cell-in-view.ant-picker-cell-selected .ant-picker-cell-inner,.ant-picker-cell-in-view.ant-picker-cell-range-start .ant-picker-cell-inner,.ant-picker-cell-in-view.ant-picker-cell-range-end .ant-picker-cell-inner{color:#fff;background:#177ddc}.ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single):before,.ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single):before{background:#111b26}.ant-picker-cell-in-view.ant-picker-cell-range-start:before{left:50%}.ant-picker-cell-in-view.ant-picker-cell-range-end:before{right:50%}.ant-picker-cell-in-view.ant-picker-cell-range-hover-start:not(.ant-picker-cell-in-range):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end):after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-end:not(.ant-picker-cell-in-range):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end):after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start-single:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-end-near-hover:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-start-near-hover:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-end-single:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-in-range):after{position:absolute;top:50%;z-index:0;height:24px;border-top:1px dashed #0e4980;border-bottom:1px dashed #0e4980;transform:translateY(-50%);transition:all .3s;content:""}.ant-picker-cell-range-hover-start:after,.ant-picker-cell-range-hover-end:after,.ant-picker-cell-range-hover:after{right:0;left:2px}.ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover:before,.ant-picker-cell-in-view.ant-picker-cell-range-start.ant-picker-cell-range-hover:before,.ant-picker-cell-in-view.ant-picker-cell-range-end.ant-picker-cell-range-hover:before,.ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single).ant-picker-cell-range-hover-start:before,.ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single).ant-picker-cell-range-hover-end:before,.ant-picker-panel>:not(.ant-picker-date-panel) .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start:before,.ant-picker-panel>:not(.ant-picker-date-panel) .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end:before{background:#06213a}.ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single):not(.ant-picker-cell-range-end) .ant-picker-cell-inner{border-radius:2px 0 0 2px}.ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single):not(.ant-picker-cell-range-start) .ant-picker-cell-inner{border-radius:0 2px 2px 0}.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner:after,.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner:after{position:absolute;top:0;bottom:0;z-index:-1;background:#06213a;transition:all .3s;content:""}.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner:after{right:-6px;left:0}.ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner:after{right:0;left:-6px}.ant-picker-cell-range-hover.ant-picker-cell-range-start:after{right:50%}.ant-picker-cell-range-hover.ant-picker-cell-range-end:after{left:50%}tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover:first-child:after,tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover-end:first-child:after,.ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start.ant-picker-cell-range-hover-edge-start-near-range:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover-edge-start-near-range):after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-start:after{left:6px;border-left:1px dashed #0e4980;border-top-left-radius:2px;border-bottom-left-radius:2px}tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover:last-child:after,tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child:after,.ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-edge-end.ant-picker-cell-range-hover-edge-end-near-range:after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-end:not(.ant-picker-cell-range-hover-edge-end-near-range):after,.ant-picker-cell-in-view.ant-picker-cell-range-hover-end:after{right:6px;border-right:1px dashed #0e4980;border-top-right-radius:2px;border-bottom-right-radius:2px}.ant-picker-cell-disabled{color:#ffffff4d;pointer-events:none}.ant-picker-cell-disabled .ant-picker-cell-inner{background:transparent}.ant-picker-cell-disabled:before{background:#303030}.ant-picker-cell-disabled.ant-picker-cell-today .ant-picker-cell-inner:before{border-color:#ffffff4d}.ant-picker-decade-panel .ant-picker-content,.ant-picker-year-panel .ant-picker-content,.ant-picker-quarter-panel .ant-picker-content,.ant-picker-month-panel .ant-picker-content{height:264px}.ant-picker-decade-panel .ant-picker-cell-inner,.ant-picker-year-panel .ant-picker-cell-inner,.ant-picker-quarter-panel .ant-picker-cell-inner,.ant-picker-month-panel .ant-picker-cell-inner{padding:0 8px}.ant-picker-quarter-panel .ant-picker-content{height:56px}.ant-picker-footer{width:-moz-min-content;width:min-content;min-width:100%;line-height:38px;text-align:center;border-bottom:1px solid transparent}.ant-picker-panel .ant-picker-footer{border-top:1px solid #303030}.ant-picker-footer-extra{padding:0 12px;line-height:38px;text-align:left}.ant-picker-footer-extra:not(:last-child){border-bottom:1px solid #303030}.ant-picker-now{text-align:left}.ant-picker-today-btn{color:#177ddc}.ant-picker-today-btn:hover{color:#165996}.ant-picker-today-btn:active{color:#388ed3}.ant-picker-today-btn.ant-picker-today-btn-disabled{color:#ffffff4d;cursor:not-allowed}.ant-picker-decade-panel .ant-picker-cell-inner{padding:0 4px}.ant-picker-decade-panel .ant-picker-cell:before{display:none}.ant-picker-year-panel .ant-picker-body,.ant-picker-quarter-panel .ant-picker-body,.ant-picker-month-panel .ant-picker-body{padding:0 8px}.ant-picker-year-panel .ant-picker-cell-inner,.ant-picker-quarter-panel .ant-picker-cell-inner,.ant-picker-month-panel .ant-picker-cell-inner{width:60px}.ant-picker-year-panel .ant-picker-cell-range-hover-start:after,.ant-picker-quarter-panel .ant-picker-cell-range-hover-start:after,.ant-picker-month-panel .ant-picker-cell-range-hover-start:after{left:14px;border-left:1px dashed #0e4980;border-radius:2px 0 0 2px}.ant-picker-panel-rtl .ant-picker-year-panel .ant-picker-cell-range-hover-start:after,.ant-picker-panel-rtl .ant-picker-quarter-panel .ant-picker-cell-range-hover-start:after,.ant-picker-panel-rtl .ant-picker-month-panel .ant-picker-cell-range-hover-start:after{right:14px;border-right:1px dashed #0e4980;border-radius:0 2px 2px 0}.ant-picker-year-panel .ant-picker-cell-range-hover-end:after,.ant-picker-quarter-panel .ant-picker-cell-range-hover-end:after,.ant-picker-month-panel .ant-picker-cell-range-hover-end:after{right:14px;border-right:1px dashed #0e4980;border-radius:0 2px 2px 0}.ant-picker-panel-rtl .ant-picker-year-panel .ant-picker-cell-range-hover-end:after,.ant-picker-panel-rtl .ant-picker-quarter-panel .ant-picker-cell-range-hover-end:after,.ant-picker-panel-rtl .ant-picker-month-panel .ant-picker-cell-range-hover-end:after{left:14px;border-left:1px dashed #0e4980;border-radius:2px 0 0 2px}.ant-picker-week-panel .ant-picker-body{padding:8px 12px}.ant-picker-week-panel .ant-picker-cell:hover .ant-picker-cell-inner,.ant-picker-week-panel .ant-picker-cell-selected .ant-picker-cell-inner,.ant-picker-week-panel .ant-picker-cell .ant-picker-cell-inner{background:transparent!important}.ant-picker-week-panel-row td{transition:background .3s}.ant-picker-week-panel-row:hover td{background:rgba(255,255,255,.08)}.ant-picker-week-panel-row-selected td,.ant-picker-week-panel-row-selected:hover td{background:#177ddc}.ant-picker-week-panel-row-selected td.ant-picker-cell-week,.ant-picker-week-panel-row-selected:hover td.ant-picker-cell-week{color:#ffffff80}.ant-picker-week-panel-row-selected td.ant-picker-cell-today .ant-picker-cell-inner:before,.ant-picker-week-panel-row-selected:hover td.ant-picker-cell-today .ant-picker-cell-inner:before{border-color:#fff}.ant-picker-week-panel-row-selected td .ant-picker-cell-inner,.ant-picker-week-panel-row-selected:hover td .ant-picker-cell-inner{color:#fff}.ant-picker-date-panel .ant-picker-body{padding:8px 12px}.ant-picker-date-panel .ant-picker-content{width:252px}.ant-picker-date-panel .ant-picker-content th{width:36px}.ant-picker-datetime-panel{display:flex}.ant-picker-datetime-panel .ant-picker-time-panel{border-left:1px solid #303030}.ant-picker-datetime-panel .ant-picker-date-panel,.ant-picker-datetime-panel .ant-picker-time-panel{transition:opacity .3s}.ant-picker-datetime-panel-active .ant-picker-date-panel,.ant-picker-datetime-panel-active .ant-picker-time-panel{opacity:.3}.ant-picker-datetime-panel-active .ant-picker-date-panel-active,.ant-picker-datetime-panel-active .ant-picker-time-panel-active{opacity:1}.ant-picker-time-panel{width:auto;min-width:auto}.ant-picker-time-panel .ant-picker-content{display:flex;flex:auto;height:224px}.ant-picker-time-panel-column{flex:1 0 auto;width:56px;margin:0;padding:0;overflow-y:hidden;text-align:left;list-style:none;transition:background .3s}.ant-picker-time-panel-column:after{display:block;height:196px;content:""}.ant-picker-datetime-panel .ant-picker-time-panel-column:after{height:198px}.ant-picker-time-panel-column:not(:first-child){border-left:1px solid #303030}.ant-picker-time-panel-column-active{background:rgba(17,27,38,.2)}.ant-picker-time-panel-column:hover{overflow-y:auto}.ant-picker-time-panel-column>li{margin:0;padding:0}.ant-picker-time-panel-column>li.ant-picker-time-panel-cell .ant-picker-time-panel-cell-inner{display:block;width:100%;height:28px;margin:0;padding:0 0 0 14px;color:#ffffffd9;line-height:28px;border-radius:0;cursor:pointer;transition:background .3s}.ant-picker-time-panel-column>li.ant-picker-time-panel-cell .ant-picker-time-panel-cell-inner:hover{background:rgba(255,255,255,.08)}.ant-picker-time-panel-column>li.ant-picker-time-panel-cell-selected .ant-picker-time-panel-cell-inner{background:#111b26}.ant-picker-time-panel-column>li.ant-picker-time-panel-cell-disabled .ant-picker-time-panel-cell-inner{color:#ffffff4d;background:transparent;cursor:not-allowed}_:-ms-fullscreen .ant-picker-range-wrapper .ant-picker-month-panel .ant-picker-cell,:root .ant-picker-range-wrapper .ant-picker-month-panel .ant-picker-cell,_:-ms-fullscreen .ant-picker-range-wrapper .ant-picker-year-panel .ant-picker-cell,:root .ant-picker-range-wrapper .ant-picker-year-panel .ant-picker-cell{padding:21px 0}.ant-picker-rtl{direction:rtl}.ant-picker-rtl .ant-picker-suffix{margin-right:4px;margin-left:0}.ant-picker-rtl .ant-picker-clear{right:auto;left:0}.ant-picker-rtl .ant-picker-separator{transform:rotate(180deg)}.ant-picker-panel-rtl .ant-picker-header-view button:not(:first-child){margin-right:8px;margin-left:0}.ant-picker-rtl.ant-picker-range .ant-picker-clear{right:auto;left:11px}.ant-picker-rtl.ant-picker-range .ant-picker-active-bar{margin-right:11px;margin-left:0}.ant-picker-rtl.ant-picker-range.ant-picker-small .ant-picker-active-bar{margin-right:7px}.ant-picker-dropdown-rtl .ant-picker-ranges{text-align:right}.ant-picker-dropdown-rtl .ant-picker-ranges .ant-picker-ok{float:left;margin-right:8px;margin-left:0}.ant-picker-panel-rtl{direction:rtl}.ant-picker-panel-rtl .ant-picker-prev-icon,.ant-picker-panel-rtl .ant-picker-super-prev-icon{transform:rotate(135deg)}.ant-picker-panel-rtl .ant-picker-next-icon,.ant-picker-panel-rtl .ant-picker-super-next-icon{transform:rotate(-45deg)}.ant-picker-cell .ant-picker-cell-inner{position:relative;z-index:2;display:inline-block;min-width:24px;height:24px;line-height:24px;border-radius:2px;transition:background .3s,border .3s}.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-start:before{right:50%;left:0}.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-end:before{right:0;left:50%}.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-start.ant-picker-cell-range-end:before{right:50%;left:50%}.ant-picker-panel-rtl .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner:after{right:0;left:-6px}.ant-picker-panel-rtl .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner:after{right:-6px;left:0}.ant-picker-panel-rtl .ant-picker-cell-range-hover.ant-picker-cell-range-start:after{right:0;left:50%}.ant-picker-panel-rtl .ant-picker-cell-range-hover.ant-picker-cell-range-end:after{right:50%;left:0}.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single):not(.ant-picker-cell-range-end) .ant-picker-cell-inner{border-radius:0 2px 2px 0}.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single):not(.ant-picker-cell-range-start) .ant-picker-cell-inner{border-radius:2px 0 0 2px}.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-selected):first-child:after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start.ant-picker-cell-range-hover-edge-start-near-range:after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover-edge-start-near-range):after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:after{right:6px;left:0;border-right:1px dashed #0e4980;border-left:none;border-radius:0 2px 2px 0}.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-selected):last-child:after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-edge-end.ant-picker-cell-range-hover-edge-end-near-range:after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-end:not(.ant-picker-cell-range-hover-edge-end-near-range):after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-end:after{right:0;left:6px;border-right:none;border-left:1px dashed #0e4980;border-radius:2px 0 0 2px}.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child:after,.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-range-hover-end:first-child:after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover):after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-end.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover):after,.ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-start.ant-picker-cell-range-hover-edge-end:not(.ant-picker-cell-range-hover):after,.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover.ant-picker-cell-range-hover-edge-start:last-child:after,.ant-picker-panel-rtl tr>.ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover.ant-picker-cell-range-hover-edge-end:first-child:after{right:6px;left:6px;border-right:1px dashed #0e4980;border-left:1px dashed #0e4980;border-radius:2px}.ant-picker-dropdown-rtl .ant-picker-footer-extra{direction:rtl;text-align:right}.ant-picker-panel-rtl .ant-picker-time-panel{direction:ltr}/*!********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/tag/style/index.less ***! + \\********************************************************************************************************************************************************************************************************************************************************/.ant-tag{box-sizing:border-box;margin:0 8px 0 0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block;height:auto;padding:0 7px;font-size:12px;line-height:20px;white-space:nowrap;background:rgba(255,255,255,.04);border:1px solid #434343;border-radius:2px;opacity:1;transition:all .3s}.ant-tag,.ant-tag a,.ant-tag a:hover{color:#ffffffd9}.ant-tag>a:first-child:last-child{display:inline-block;margin:0 -8px;padding:0 8px}.ant-tag-close-icon{margin-left:3px;color:#ffffff73;font-size:10px;cursor:pointer;transition:all .3s}.ant-tag-close-icon:hover{color:#ffffffd9}.ant-tag-has-color{border-color:transparent}.ant-tag-has-color,.ant-tag-has-color a,.ant-tag-has-color a:hover,.ant-tag-has-color .anticon-close,.ant-tag-has-color .anticon-close:hover{color:#fff}.ant-tag-checkable{background-color:transparent;border-color:transparent;cursor:pointer}.ant-tag-checkable:not(.ant-tag-checkable-checked):hover{color:#177ddc}.ant-tag-checkable:active,.ant-tag-checkable-checked{color:#fff}.ant-tag-checkable-checked{background-color:#177ddc}.ant-tag-checkable:active{background-color:#388ed3}.ant-tag-hidden{display:none}.ant-tag-pink{color:#e0529c;background:#291321;border-color:#551c3b}.ant-tag-pink-inverse{color:#fff;background:#cb2b83;border-color:#cb2b83}.ant-tag-magenta{color:#e0529c;background:#291321;border-color:#551c3b}.ant-tag-magenta-inverse{color:#fff;background:#cb2b83;border-color:#cb2b83}.ant-tag-red{color:#e84749;background:#2a1215;border-color:#58181c}.ant-tag-red-inverse{color:#fff;background:#d32029;border-color:#d32029}.ant-tag-volcano{color:#e87040;background:#2b1611;border-color:#592716}.ant-tag-volcano-inverse{color:#fff;background:#d84a1b;border-color:#d84a1b}.ant-tag-orange{color:#e89a3c;background:#2b1d11;border-color:#593815}.ant-tag-orange-inverse{color:#fff;background:#d87a16;border-color:#d87a16}.ant-tag-yellow{color:#e8d639;background:#2b2611;border-color:#595014}.ant-tag-yellow-inverse{color:#fff;background:#d8bd14;border-color:#d8bd14}.ant-tag-gold{color:#e8b339;background:#2b2111;border-color:#594214}.ant-tag-gold-inverse{color:#fff;background:#d89614;border-color:#d89614}.ant-tag-cyan{color:#33bcb7;background:#112123;border-color:#144848}.ant-tag-cyan-inverse{color:#fff;background:#13a8a8;border-color:#13a8a8}.ant-tag-lime{color:#a9d134;background:#1f2611;border-color:#3e4f13}.ant-tag-lime-inverse{color:#fff;background:#8bbb11;border-color:#8bbb11}.ant-tag-green{color:#6abe39;background:#162312;border-color:#274916}.ant-tag-green-inverse{color:#fff;background:#49aa19;border-color:#49aa19}.ant-tag-blue{color:#3c9ae8;background:#111d2c;border-color:#15395b}.ant-tag-blue-inverse{color:#fff;background:#177ddc;border-color:#177ddc}.ant-tag-geekblue{color:#5273e0;background:#131629;border-color:#1c2755}.ant-tag-geekblue-inverse{color:#fff;background:#2b4acb;border-color:#2b4acb}.ant-tag-purple{color:#854eca;background:#1a1325;border-color:#301c4d}.ant-tag-purple-inverse{color:#fff;background:#642ab5;border-color:#642ab5}.ant-tag-success{color:#49aa19;background:#162312;border-color:#274916}.ant-tag-processing{color:#177ddc;background:#111b26;border-color:#153450}.ant-tag-error{color:#a61d24;background:#2a1215;border-color:#58181c}.ant-tag-warning{color:#d89614;background:#2b1d11;border-color:#593815}.ant-tag>.anticon+span,.ant-tag>span+.anticon{margin-left:7px}.ant-tag.ant-tag-rtl{margin-right:0;margin-left:8px;direction:rtl;text-align:right}.ant-tag-rtl .ant-tag-close-icon{margin-right:3px;margin-left:0}.ant-tag-rtl.ant-tag>.anticon+span,.ant-tag-rtl.ant-tag>span+.anticon{margin-right:7px;margin-left:0}/*!*********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/card/style/index.less ***! + \\*********************************************************************************************************************************************************************************************************************************************************/.ant-card{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;background:#141414;border-radius:2px}.ant-card-rtl{direction:rtl}.ant-card-hoverable{cursor:pointer;transition:box-shadow .3s,border-color .3s}.ant-card-hoverable:hover{border-color:transparent;box-shadow:0 1px 2px -2px #000000a3,0 3px 6px #0000007a,0 5px 12px 4px #0000005c}.ant-card-bordered{border:1px solid #303030}.ant-card-head{min-height:48px;margin-bottom:-1px;padding:0 24px;color:#ffffffd9;font-weight:500;font-size:16px;background:transparent;border-bottom:1px solid #303030;border-radius:2px 2px 0 0}.ant-card-head:before{display:table;content:""}.ant-card-head:after{display:table;clear:both;content:""}.ant-card-head-wrapper{display:flex;align-items:center}.ant-card-head-title{display:inline-block;flex:1;padding:16px 0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-card-head-title>.ant-typography,.ant-card-head-title>.ant-typography-edit-content{left:0;margin-top:0;margin-bottom:0}.ant-card-head .ant-tabs-top{clear:both;margin-bottom:-17px;color:#ffffffd9;font-weight:400;font-size:14px}.ant-card-head .ant-tabs-top-bar{border-bottom:1px solid #303030}.ant-card-extra{float:right;margin-left:auto;padding:16px 0;color:#ffffffd9;font-weight:400;font-size:14px}.ant-card-rtl .ant-card-extra{margin-right:auto;margin-left:0}.ant-card-body{padding:24px}.ant-card-body:before{display:table;content:""}.ant-card-body:after{display:table;clear:both;content:""}.ant-card-contain-grid:not(.ant-card-loading) .ant-card-body{margin:-1px 0 0 -1px;padding:0}.ant-card-grid{float:left;width:33.33%;padding:24px;border:0;border-radius:0;box-shadow:1px 0 #303030,0 1px #303030,1px 1px #303030,1px 0 #303030 inset,0 1px #303030 inset;transition:all .3s}.ant-card-rtl .ant-card-grid{float:right}.ant-card-grid-hoverable:hover{position:relative;z-index:1;box-shadow:0 1px 2px -2px #000000a3,0 3px 6px #0000007a,0 5px 12px 4px #0000005c}.ant-card-contain-tabs>.ant-card-head .ant-card-head-title{min-height:32px;padding-bottom:0}.ant-card-contain-tabs>.ant-card-head .ant-card-extra{padding-bottom:0}.ant-card-bordered .ant-card-cover{margin-top:-1px;margin-right:-1px;margin-left:-1px}.ant-card-cover>*{display:block;width:100%}.ant-card-cover img{border-radius:2px 2px 0 0}.ant-card-actions{margin:0;padding:0;list-style:none;background:#141414;border-top:1px solid #303030}.ant-card-actions:before{display:table;content:""}.ant-card-actions:after{display:table;clear:both;content:""}.ant-card-actions>li{float:left;margin:12px 0;color:#ffffff73;text-align:center}.ant-card-rtl .ant-card-actions>li{float:right}.ant-card-actions>li>span{position:relative;display:block;min-width:32px;font-size:14px;line-height:1.5715;cursor:pointer}.ant-card-actions>li>span:hover{color:#177ddc;transition:color .3s}.ant-card-actions>li>span a:not(.ant-btn),.ant-card-actions>li>span>.anticon{display:inline-block;width:100%;color:#ffffff73;line-height:22px;transition:color .3s}.ant-card-actions>li>span a:not(.ant-btn):hover,.ant-card-actions>li>span>.anticon:hover{color:#177ddc}.ant-card-actions>li>span>.anticon{font-size:16px;line-height:22px}.ant-card-actions>li:not(:last-child){border-right:1px solid #303030}.ant-card-rtl .ant-card-actions>li:not(:last-child){border-right:none;border-left:1px solid #303030}.ant-card-type-inner .ant-card-head{padding:0 24px;background:rgba(255,255,255,.04)}.ant-card-type-inner .ant-card-head-title{padding:12px 0;font-size:14px}.ant-card-type-inner .ant-card-body{padding:16px 24px}.ant-card-type-inner .ant-card-extra{padding:13.5px 0}.ant-card-meta{margin:-4px 0}.ant-card-meta:before{display:table;content:""}.ant-card-meta:after{display:table;clear:both;content:""}.ant-card-meta-avatar{float:left;padding-right:16px}.ant-card-rtl .ant-card-meta-avatar{float:right;padding-right:0;padding-left:16px}.ant-card-meta-detail{overflow:hidden}.ant-card-meta-detail>div:not(:last-child){margin-bottom:8px}.ant-card-meta-title{overflow:hidden;color:#ffffffd9;font-weight:500;font-size:16px;white-space:nowrap;text-overflow:ellipsis}.ant-card-meta-description{color:#ffffff73}.ant-card-loading{overflow:hidden}.ant-card-loading .ant-card-body{-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-card-loading-content p{margin:0}.ant-card-loading-block{height:14px;margin:4px 0;background:linear-gradient(90deg,rgba(48,48,48,.2),rgba(48,48,48,.4),rgba(48,48,48,.2));background-size:600% 600%;border-radius:2px;animation:card-loading 1.4s ease infinite}@keyframes card-loading{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.ant-card-small>.ant-card-head{min-height:36px;padding:0 12px;font-size:14px}.ant-card-small>.ant-card-head>.ant-card-head-wrapper>.ant-card-head-title{padding:8px 0}.ant-card-small>.ant-card-head>.ant-card-head-wrapper>.ant-card-extra{padding:8px 0;font-size:14px}.ant-card-small>.ant-card-body{padding:12px}/*!*********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/tabs/style/index.less ***! + \\*********************************************************************************************************************************************************************************************************************************************************/.ant-tabs-small>.ant-tabs-nav .ant-tabs-tab{padding:8px 0;font-size:14px}.ant-tabs-large>.ant-tabs-nav .ant-tabs-tab{padding:16px 0;font-size:16px}.ant-tabs-card.ant-tabs-small>.ant-tabs-nav .ant-tabs-tab{padding:6px 16px}.ant-tabs-card.ant-tabs-large>.ant-tabs-nav .ant-tabs-tab{padding:7px 16px 6px}.ant-tabs-rtl{direction:rtl}.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab{margin:0 0 0 32px}.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab:last-of-type{margin-left:0}.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab .anticon{margin-right:0;margin-left:12px}.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab .ant-tabs-tab-remove{margin-right:8px;margin-left:-4px}.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab .ant-tabs-tab-remove .anticon{margin:0}.ant-tabs-rtl.ant-tabs-left>.ant-tabs-nav{order:1}.ant-tabs-rtl.ant-tabs-left>.ant-tabs-content-holder{order:0}.ant-tabs-rtl.ant-tabs-right>.ant-tabs-nav{order:0}.ant-tabs-rtl.ant-tabs-right>.ant-tabs-content-holder{order:1}.ant-tabs-rtl.ant-tabs-card.ant-tabs-top>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-rtl.ant-tabs-card.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab{margin-right:2px;margin-left:0}.ant-tabs-rtl.ant-tabs-card.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-add,.ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-add,.ant-tabs-rtl.ant-tabs-card.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-add,.ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-add{margin-right:2px;margin-left:0}.ant-tabs-dropdown-rtl{direction:rtl}.ant-tabs-dropdown-rtl .ant-tabs-dropdown-menu-item{text-align:right}.ant-tabs-top,.ant-tabs-bottom{flex-direction:column}.ant-tabs-top>.ant-tabs-nav,.ant-tabs-bottom>.ant-tabs-nav,.ant-tabs-top>div>.ant-tabs-nav,.ant-tabs-bottom>div>.ant-tabs-nav{margin:0 0 16px}.ant-tabs-top>.ant-tabs-nav:before,.ant-tabs-bottom>.ant-tabs-nav:before,.ant-tabs-top>div>.ant-tabs-nav:before,.ant-tabs-bottom>div>.ant-tabs-nav:before{position:absolute;right:0;left:0;border-bottom:1px solid #303030;content:""}.ant-tabs-top>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-ink-bar{height:2px}.ant-tabs-top>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-ink-bar-animated{transition:width .3s,left .3s,right .3s}.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap:after{top:0;bottom:0;width:30px}.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap:before{left:0;box-shadow:inset 10px 0 8px -8px #00000014}.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap:after{right:0;box-shadow:inset -10px 0 8px -8px #00000014}.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left:before,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left:before,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left:before,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left:before{opacity:1}.ant-tabs-top>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right:after,.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right:after,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right:after,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right:after{opacity:1}.ant-tabs-top>.ant-tabs-nav:before,.ant-tabs-top>div>.ant-tabs-nav:before{bottom:0}.ant-tabs-top>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-ink-bar{bottom:0}.ant-tabs-bottom>.ant-tabs-nav,.ant-tabs-bottom>div>.ant-tabs-nav{order:1;margin-top:16px;margin-bottom:0}.ant-tabs-bottom>.ant-tabs-nav:before,.ant-tabs-bottom>div>.ant-tabs-nav:before{top:0}.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-ink-bar{top:0}.ant-tabs-bottom>.ant-tabs-content-holder,.ant-tabs-bottom>div>.ant-tabs-content-holder{order:0}.ant-tabs-left>.ant-tabs-nav,.ant-tabs-right>.ant-tabs-nav,.ant-tabs-left>div>.ant-tabs-nav,.ant-tabs-right>div>.ant-tabs-nav{flex-direction:column;min-width:50px}.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-right>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-tab{padding:8px 24px;text-align:center}.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-right>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab{margin:16px 0 0}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap{flex-direction:column}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap:after{right:0;left:0;height:30px}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap:before{top:0;box-shadow:inset 0 10px 8px -8px #00000014}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap:after{bottom:0;box-shadow:inset 0 -10px 8px -8px #00000014}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top:before,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top:before,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top:before,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top:before{opacity:1}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom:after,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom:after,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom:after,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom:after{opacity:1}.ant-tabs-left>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-right>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-ink-bar{width:2px}.ant-tabs-left>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-right>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-ink-bar-animated,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-ink-bar-animated{transition:height .3s,top .3s}.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-list,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-list,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-list,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-list,.ant-tabs-left>.ant-tabs-nav .ant-tabs-nav-operations,.ant-tabs-right>.ant-tabs-nav .ant-tabs-nav-operations,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-nav-operations,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-nav-operations{flex:1 0 auto;flex-direction:column}.ant-tabs-left>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-ink-bar{right:0}.ant-tabs-left>.ant-tabs-content-holder,.ant-tabs-left>div>.ant-tabs-content-holder{margin-left:-1px;border-left:1px solid #303030}.ant-tabs-left>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane,.ant-tabs-left>div>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane{padding-left:24px}.ant-tabs-right>.ant-tabs-nav,.ant-tabs-right>div>.ant-tabs-nav{order:1}.ant-tabs-right>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-ink-bar{left:0}.ant-tabs-right>.ant-tabs-content-holder,.ant-tabs-right>div>.ant-tabs-content-holder{order:0;margin-right:-1px;border-right:1px solid #303030}.ant-tabs-right>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane,.ant-tabs-right>div>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane{padding-right:24px}.ant-tabs-dropdown{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;display:block}.ant-tabs-dropdown-hidden{display:none}.ant-tabs-dropdown-menu{max-height:200px;margin:0;padding:4px 0;overflow-x:hidden;overflow-y:auto;text-align:left;list-style-type:none;background-color:#1f1f1f;background-clip:padding-box;border-radius:2px;outline:none;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003}.ant-tabs-dropdown-menu-item{display:flex;align-items:center;min-width:120px;margin:0;padding:5px 12px;overflow:hidden;color:#ffffffd9;font-weight:400;font-size:14px;line-height:22px;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;transition:all .3s}.ant-tabs-dropdown-menu-item>span{flex:1;white-space:nowrap}.ant-tabs-dropdown-menu-item-remove{flex:none;margin-left:12px;color:#ffffff73;font-size:12px;background:transparent;border:0;cursor:pointer}.ant-tabs-dropdown-menu-item-remove:hover{color:#165996}.ant-tabs-dropdown-menu-item:hover{background:rgba(255,255,255,.08)}.ant-tabs-dropdown-menu-item-disabled,.ant-tabs-dropdown-menu-item-disabled:hover{color:#ffffff4d;background:transparent;cursor:not-allowed}.ant-tabs-card>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-card>div>.ant-tabs-nav .ant-tabs-tab{margin:0;padding:8px 16px;background:rgba(255,255,255,.04);border:1px solid #303030;transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-card>.ant-tabs-nav .ant-tabs-tab-active,.ant-tabs-card>div>.ant-tabs-nav .ant-tabs-tab-active{color:#177ddc;background:#141414}.ant-tabs-card>.ant-tabs-nav .ant-tabs-ink-bar,.ant-tabs-card>div>.ant-tabs-nav .ant-tabs-ink-bar{visibility:hidden}.ant-tabs-card.ant-tabs-top>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab{margin-left:2px}.ant-tabs-card.ant-tabs-top>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-card.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-tab{border-radius:2px 2px 0 0}.ant-tabs-card.ant-tabs-top>.ant-tabs-nav .ant-tabs-tab-active,.ant-tabs-card.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-tab-active{border-bottom-color:#141414}.ant-tabs-card.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-card.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-tab{border-radius:0 0 2px 2px}.ant-tabs-card.ant-tabs-bottom>.ant-tabs-nav .ant-tabs-tab-active,.ant-tabs-card.ant-tabs-bottom>div>.ant-tabs-nav .ant-tabs-tab-active{border-top-color:#141414}.ant-tabs-card.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-right>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab,.ant-tabs-card.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-tab+.ant-tabs-tab{margin-top:2px}.ant-tabs-card.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-card.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-tab{border-radius:2px 0 0 2px}.ant-tabs-card.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab-active,.ant-tabs-card.ant-tabs-left>div>.ant-tabs-nav .ant-tabs-tab-active{border-right-color:#141414}.ant-tabs-card.ant-tabs-right>.ant-tabs-nav .ant-tabs-tab,.ant-tabs-card.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-tab{border-radius:0 2px 2px 0}.ant-tabs-card.ant-tabs-right>.ant-tabs-nav .ant-tabs-tab-active,.ant-tabs-card.ant-tabs-right>div>.ant-tabs-nav .ant-tabs-tab-active{border-left-color:#141414}.ant-tabs{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:flex;overflow:hidden}.ant-tabs>.ant-tabs-nav,.ant-tabs>div>.ant-tabs-nav{position:relative;display:flex;flex:none;align-items:center}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-wrap,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-wrap{position:relative;display:inline-block;display:flex;flex:auto;align-self:stretch;overflow:hidden;white-space:nowrap;transform:translate(0)}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-wrap:before,.ant-tabs>.ant-tabs-nav .ant-tabs-nav-wrap:after,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-wrap:after{position:absolute;z-index:1;opacity:0;transition:opacity .3s;content:"";pointer-events:none}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-list,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-list{position:relative;display:flex;transition:transform .3s}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-operations,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-operations{display:flex;align-self:stretch}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-operations-hidden,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-operations-hidden{position:absolute;visibility:hidden;pointer-events:none}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-more,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-more{position:relative;padding:8px 16px;background:transparent;border:0}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-more:after,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-more:after{position:absolute;right:0;bottom:0;left:0;height:5px;transform:translateY(100%);content:""}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-add,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-add{min-width:40px;margin-left:2px;padding:0 8px;background:rgba(255,255,255,.04);border:1px solid #303030;border-radius:2px 2px 0 0;outline:none;cursor:pointer;transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-add:hover,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-add:hover{color:#165996}.ant-tabs>.ant-tabs-nav .ant-tabs-nav-add:active,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-add:active,.ant-tabs>.ant-tabs-nav .ant-tabs-nav-add:focus,.ant-tabs>div>.ant-tabs-nav .ant-tabs-nav-add:focus{color:#388ed3}.ant-tabs-extra-content{flex:none}.ant-tabs-centered>.ant-tabs-nav .ant-tabs-nav-wrap:not([class*="ant-tabs-nav-wrap-ping"]),.ant-tabs-centered>div>.ant-tabs-nav .ant-tabs-nav-wrap:not([class*="ant-tabs-nav-wrap-ping"]){justify-content:center}.ant-tabs-ink-bar{position:absolute;background:#177ddc;pointer-events:none}.ant-tabs-tab{position:relative;display:inline-flex;align-items:center;padding:12px 0;font-size:14px;background:transparent;border:0;outline:none;cursor:pointer}.ant-tabs-tab-btn:focus,.ant-tabs-tab-remove:focus,.ant-tabs-tab-btn:active,.ant-tabs-tab-remove:active{color:#388ed3}.ant-tabs-tab-btn{outline:none;transition:all .3s}.ant-tabs-tab-remove{flex:none;margin-right:-4px;margin-left:8px;color:#ffffff73;font-size:12px;background:transparent;border:none;outline:none;cursor:pointer;transition:all .3s}.ant-tabs-tab-remove:hover{color:#ffffffd9}.ant-tabs-tab:hover{color:#165996}.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn{color:#177ddc;text-shadow:0 0 .25px currentcolor}.ant-tabs-tab.ant-tabs-tab-disabled{color:#ffffff4d;cursor:not-allowed}.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-btn:focus,.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-remove:focus,.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-btn:active,.ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-remove:active{color:#ffffff4d}.ant-tabs-tab .ant-tabs-tab-remove .anticon{margin:0}.ant-tabs-tab .anticon{margin-right:12px}.ant-tabs-tab+.ant-tabs-tab{margin:0 0 0 32px}.ant-tabs-content{display:flex;width:100%}.ant-tabs-content-holder{flex:auto;min-width:0;min-height:0}.ant-tabs-content-animated{transition:margin .3s}.ant-tabs-tabpane{flex:none;width:100%;outline:none}/*!*********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/grid/style/index.less ***! + \\*********************************************************************************************************************************************************************************************************************************************************/.ant-row{display:flex;flex-flow:row wrap}.ant-row:before,.ant-row:after{display:flex}.ant-row-no-wrap{flex-wrap:nowrap}.ant-row-start{justify-content:flex-start}.ant-row-center{justify-content:center}.ant-row-end{justify-content:flex-end}.ant-row-space-between{justify-content:space-between}.ant-row-space-around{justify-content:space-around}.ant-row-top{align-items:flex-start}.ant-row-middle{align-items:center}.ant-row-bottom{align-items:flex-end}.ant-col{position:relative;max-width:100%;min-height:1px}.ant-col-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-push-24{left:100%}.ant-col-pull-24{right:100%}.ant-col-offset-24{margin-left:100%}.ant-col-order-24{order:24}.ant-col-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-push-23{left:95.83333333%}.ant-col-pull-23{right:95.83333333%}.ant-col-offset-23{margin-left:95.83333333%}.ant-col-order-23{order:23}.ant-col-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-push-22{left:91.66666667%}.ant-col-pull-22{right:91.66666667%}.ant-col-offset-22{margin-left:91.66666667%}.ant-col-order-22{order:22}.ant-col-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-push-21{left:87.5%}.ant-col-pull-21{right:87.5%}.ant-col-offset-21{margin-left:87.5%}.ant-col-order-21{order:21}.ant-col-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-push-20{left:83.33333333%}.ant-col-pull-20{right:83.33333333%}.ant-col-offset-20{margin-left:83.33333333%}.ant-col-order-20{order:20}.ant-col-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-push-19{left:79.16666667%}.ant-col-pull-19{right:79.16666667%}.ant-col-offset-19{margin-left:79.16666667%}.ant-col-order-19{order:19}.ant-col-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-push-18{left:75%}.ant-col-pull-18{right:75%}.ant-col-offset-18{margin-left:75%}.ant-col-order-18{order:18}.ant-col-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-push-17{left:70.83333333%}.ant-col-pull-17{right:70.83333333%}.ant-col-offset-17{margin-left:70.83333333%}.ant-col-order-17{order:17}.ant-col-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-push-16{left:66.66666667%}.ant-col-pull-16{right:66.66666667%}.ant-col-offset-16{margin-left:66.66666667%}.ant-col-order-16{order:16}.ant-col-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-push-15{left:62.5%}.ant-col-pull-15{right:62.5%}.ant-col-offset-15{margin-left:62.5%}.ant-col-order-15{order:15}.ant-col-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-push-14{left:58.33333333%}.ant-col-pull-14{right:58.33333333%}.ant-col-offset-14{margin-left:58.33333333%}.ant-col-order-14{order:14}.ant-col-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-push-13{left:54.16666667%}.ant-col-pull-13{right:54.16666667%}.ant-col-offset-13{margin-left:54.16666667%}.ant-col-order-13{order:13}.ant-col-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-push-12{left:50%}.ant-col-pull-12{right:50%}.ant-col-offset-12{margin-left:50%}.ant-col-order-12{order:12}.ant-col-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-push-11{left:45.83333333%}.ant-col-pull-11{right:45.83333333%}.ant-col-offset-11{margin-left:45.83333333%}.ant-col-order-11{order:11}.ant-col-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-push-10{left:41.66666667%}.ant-col-pull-10{right:41.66666667%}.ant-col-offset-10{margin-left:41.66666667%}.ant-col-order-10{order:10}.ant-col-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-push-9{left:37.5%}.ant-col-pull-9{right:37.5%}.ant-col-offset-9{margin-left:37.5%}.ant-col-order-9{order:9}.ant-col-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-push-8{left:33.33333333%}.ant-col-pull-8{right:33.33333333%}.ant-col-offset-8{margin-left:33.33333333%}.ant-col-order-8{order:8}.ant-col-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-push-7{left:29.16666667%}.ant-col-pull-7{right:29.16666667%}.ant-col-offset-7{margin-left:29.16666667%}.ant-col-order-7{order:7}.ant-col-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-push-6{left:25%}.ant-col-pull-6{right:25%}.ant-col-offset-6{margin-left:25%}.ant-col-order-6{order:6}.ant-col-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-push-5{left:20.83333333%}.ant-col-pull-5{right:20.83333333%}.ant-col-offset-5{margin-left:20.83333333%}.ant-col-order-5{order:5}.ant-col-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-push-4{left:16.66666667%}.ant-col-pull-4{right:16.66666667%}.ant-col-offset-4{margin-left:16.66666667%}.ant-col-order-4{order:4}.ant-col-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-push-3{left:12.5%}.ant-col-pull-3{right:12.5%}.ant-col-offset-3{margin-left:12.5%}.ant-col-order-3{order:3}.ant-col-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-push-2{left:8.33333333%}.ant-col-pull-2{right:8.33333333%}.ant-col-offset-2{margin-left:8.33333333%}.ant-col-order-2{order:2}.ant-col-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-push-1{left:4.16666667%}.ant-col-pull-1{right:4.16666667%}.ant-col-offset-1{margin-left:4.16666667%}.ant-col-order-1{order:1}.ant-col-0{display:none}.ant-col-offset-0{margin-left:0}.ant-col-order-0{order:0}.ant-col-offset-0.ant-col-rtl{margin-right:0}.ant-col-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}.ant-col-xs-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-xs-push-24{left:100%}.ant-col-xs-pull-24{right:100%}.ant-col-xs-offset-24{margin-left:100%}.ant-col-xs-order-24{order:24}.ant-col-xs-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-xs-push-23{left:95.83333333%}.ant-col-xs-pull-23{right:95.83333333%}.ant-col-xs-offset-23{margin-left:95.83333333%}.ant-col-xs-order-23{order:23}.ant-col-xs-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-xs-push-22{left:91.66666667%}.ant-col-xs-pull-22{right:91.66666667%}.ant-col-xs-offset-22{margin-left:91.66666667%}.ant-col-xs-order-22{order:22}.ant-col-xs-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-xs-push-21{left:87.5%}.ant-col-xs-pull-21{right:87.5%}.ant-col-xs-offset-21{margin-left:87.5%}.ant-col-xs-order-21{order:21}.ant-col-xs-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-xs-push-20{left:83.33333333%}.ant-col-xs-pull-20{right:83.33333333%}.ant-col-xs-offset-20{margin-left:83.33333333%}.ant-col-xs-order-20{order:20}.ant-col-xs-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-xs-push-19{left:79.16666667%}.ant-col-xs-pull-19{right:79.16666667%}.ant-col-xs-offset-19{margin-left:79.16666667%}.ant-col-xs-order-19{order:19}.ant-col-xs-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-xs-push-18{left:75%}.ant-col-xs-pull-18{right:75%}.ant-col-xs-offset-18{margin-left:75%}.ant-col-xs-order-18{order:18}.ant-col-xs-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-xs-push-17{left:70.83333333%}.ant-col-xs-pull-17{right:70.83333333%}.ant-col-xs-offset-17{margin-left:70.83333333%}.ant-col-xs-order-17{order:17}.ant-col-xs-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-xs-push-16{left:66.66666667%}.ant-col-xs-pull-16{right:66.66666667%}.ant-col-xs-offset-16{margin-left:66.66666667%}.ant-col-xs-order-16{order:16}.ant-col-xs-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-xs-push-15{left:62.5%}.ant-col-xs-pull-15{right:62.5%}.ant-col-xs-offset-15{margin-left:62.5%}.ant-col-xs-order-15{order:15}.ant-col-xs-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-xs-push-14{left:58.33333333%}.ant-col-xs-pull-14{right:58.33333333%}.ant-col-xs-offset-14{margin-left:58.33333333%}.ant-col-xs-order-14{order:14}.ant-col-xs-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-xs-push-13{left:54.16666667%}.ant-col-xs-pull-13{right:54.16666667%}.ant-col-xs-offset-13{margin-left:54.16666667%}.ant-col-xs-order-13{order:13}.ant-col-xs-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-xs-push-12{left:50%}.ant-col-xs-pull-12{right:50%}.ant-col-xs-offset-12{margin-left:50%}.ant-col-xs-order-12{order:12}.ant-col-xs-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-xs-push-11{left:45.83333333%}.ant-col-xs-pull-11{right:45.83333333%}.ant-col-xs-offset-11{margin-left:45.83333333%}.ant-col-xs-order-11{order:11}.ant-col-xs-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-xs-push-10{left:41.66666667%}.ant-col-xs-pull-10{right:41.66666667%}.ant-col-xs-offset-10{margin-left:41.66666667%}.ant-col-xs-order-10{order:10}.ant-col-xs-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-xs-push-9{left:37.5%}.ant-col-xs-pull-9{right:37.5%}.ant-col-xs-offset-9{margin-left:37.5%}.ant-col-xs-order-9{order:9}.ant-col-xs-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-xs-push-8{left:33.33333333%}.ant-col-xs-pull-8{right:33.33333333%}.ant-col-xs-offset-8{margin-left:33.33333333%}.ant-col-xs-order-8{order:8}.ant-col-xs-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-xs-push-7{left:29.16666667%}.ant-col-xs-pull-7{right:29.16666667%}.ant-col-xs-offset-7{margin-left:29.16666667%}.ant-col-xs-order-7{order:7}.ant-col-xs-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-xs-push-6{left:25%}.ant-col-xs-pull-6{right:25%}.ant-col-xs-offset-6{margin-left:25%}.ant-col-xs-order-6{order:6}.ant-col-xs-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-xs-push-5{left:20.83333333%}.ant-col-xs-pull-5{right:20.83333333%}.ant-col-xs-offset-5{margin-left:20.83333333%}.ant-col-xs-order-5{order:5}.ant-col-xs-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-xs-push-4{left:16.66666667%}.ant-col-xs-pull-4{right:16.66666667%}.ant-col-xs-offset-4{margin-left:16.66666667%}.ant-col-xs-order-4{order:4}.ant-col-xs-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-xs-push-3{left:12.5%}.ant-col-xs-pull-3{right:12.5%}.ant-col-xs-offset-3{margin-left:12.5%}.ant-col-xs-order-3{order:3}.ant-col-xs-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-xs-push-2{left:8.33333333%}.ant-col-xs-pull-2{right:8.33333333%}.ant-col-xs-offset-2{margin-left:8.33333333%}.ant-col-xs-order-2{order:2}.ant-col-xs-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-xs-push-1{left:4.16666667%}.ant-col-xs-pull-1{right:4.16666667%}.ant-col-xs-offset-1{margin-left:4.16666667%}.ant-col-xs-order-1{order:1}.ant-col-xs-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xs-push-0{left:auto}.ant-col-xs-pull-0{right:auto}.ant-col-xs-offset-0{margin-left:0}.ant-col-xs-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-xs-push-0.ant-col-rtl{right:auto}.ant-col-xs-pull-0.ant-col-rtl{left:auto}.ant-col-xs-offset-0.ant-col-rtl{margin-right:0}.ant-col-xs-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-xs-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-xs-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-xs-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-xs-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-xs-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-xs-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-xs-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-xs-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-xs-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-xs-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-xs-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-xs-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-xs-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-xs-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-xs-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-xs-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-xs-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-xs-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-xs-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-xs-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-xs-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-xs-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-xs-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-xs-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-xs-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-xs-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-xs-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-xs-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-xs-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-xs-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-xs-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-xs-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-xs-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-xs-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-xs-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-xs-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-xs-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-xs-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-xs-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-xs-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-xs-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-xs-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-xs-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-xs-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-xs-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-xs-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-xs-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-xs-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-xs-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-xs-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-xs-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-xs-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-xs-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-xs-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-xs-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-xs-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-xs-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-xs-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-xs-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-xs-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-xs-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-xs-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-xs-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-xs-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-xs-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-xs-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-xs-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-xs-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-xs-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-xs-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-xs-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}@media (min-width: 576px){.ant-col-sm-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-sm-push-24{left:100%}.ant-col-sm-pull-24{right:100%}.ant-col-sm-offset-24{margin-left:100%}.ant-col-sm-order-24{order:24}.ant-col-sm-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-sm-push-23{left:95.83333333%}.ant-col-sm-pull-23{right:95.83333333%}.ant-col-sm-offset-23{margin-left:95.83333333%}.ant-col-sm-order-23{order:23}.ant-col-sm-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-sm-push-22{left:91.66666667%}.ant-col-sm-pull-22{right:91.66666667%}.ant-col-sm-offset-22{margin-left:91.66666667%}.ant-col-sm-order-22{order:22}.ant-col-sm-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-sm-push-21{left:87.5%}.ant-col-sm-pull-21{right:87.5%}.ant-col-sm-offset-21{margin-left:87.5%}.ant-col-sm-order-21{order:21}.ant-col-sm-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-sm-push-20{left:83.33333333%}.ant-col-sm-pull-20{right:83.33333333%}.ant-col-sm-offset-20{margin-left:83.33333333%}.ant-col-sm-order-20{order:20}.ant-col-sm-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-sm-push-19{left:79.16666667%}.ant-col-sm-pull-19{right:79.16666667%}.ant-col-sm-offset-19{margin-left:79.16666667%}.ant-col-sm-order-19{order:19}.ant-col-sm-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-sm-push-18{left:75%}.ant-col-sm-pull-18{right:75%}.ant-col-sm-offset-18{margin-left:75%}.ant-col-sm-order-18{order:18}.ant-col-sm-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-sm-push-17{left:70.83333333%}.ant-col-sm-pull-17{right:70.83333333%}.ant-col-sm-offset-17{margin-left:70.83333333%}.ant-col-sm-order-17{order:17}.ant-col-sm-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-sm-push-16{left:66.66666667%}.ant-col-sm-pull-16{right:66.66666667%}.ant-col-sm-offset-16{margin-left:66.66666667%}.ant-col-sm-order-16{order:16}.ant-col-sm-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-sm-push-15{left:62.5%}.ant-col-sm-pull-15{right:62.5%}.ant-col-sm-offset-15{margin-left:62.5%}.ant-col-sm-order-15{order:15}.ant-col-sm-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-sm-push-14{left:58.33333333%}.ant-col-sm-pull-14{right:58.33333333%}.ant-col-sm-offset-14{margin-left:58.33333333%}.ant-col-sm-order-14{order:14}.ant-col-sm-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-sm-push-13{left:54.16666667%}.ant-col-sm-pull-13{right:54.16666667%}.ant-col-sm-offset-13{margin-left:54.16666667%}.ant-col-sm-order-13{order:13}.ant-col-sm-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-sm-push-12{left:50%}.ant-col-sm-pull-12{right:50%}.ant-col-sm-offset-12{margin-left:50%}.ant-col-sm-order-12{order:12}.ant-col-sm-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-sm-push-11{left:45.83333333%}.ant-col-sm-pull-11{right:45.83333333%}.ant-col-sm-offset-11{margin-left:45.83333333%}.ant-col-sm-order-11{order:11}.ant-col-sm-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-sm-push-10{left:41.66666667%}.ant-col-sm-pull-10{right:41.66666667%}.ant-col-sm-offset-10{margin-left:41.66666667%}.ant-col-sm-order-10{order:10}.ant-col-sm-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-sm-push-9{left:37.5%}.ant-col-sm-pull-9{right:37.5%}.ant-col-sm-offset-9{margin-left:37.5%}.ant-col-sm-order-9{order:9}.ant-col-sm-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-sm-push-8{left:33.33333333%}.ant-col-sm-pull-8{right:33.33333333%}.ant-col-sm-offset-8{margin-left:33.33333333%}.ant-col-sm-order-8{order:8}.ant-col-sm-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-sm-push-7{left:29.16666667%}.ant-col-sm-pull-7{right:29.16666667%}.ant-col-sm-offset-7{margin-left:29.16666667%}.ant-col-sm-order-7{order:7}.ant-col-sm-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-sm-push-6{left:25%}.ant-col-sm-pull-6{right:25%}.ant-col-sm-offset-6{margin-left:25%}.ant-col-sm-order-6{order:6}.ant-col-sm-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-sm-push-5{left:20.83333333%}.ant-col-sm-pull-5{right:20.83333333%}.ant-col-sm-offset-5{margin-left:20.83333333%}.ant-col-sm-order-5{order:5}.ant-col-sm-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-sm-push-4{left:16.66666667%}.ant-col-sm-pull-4{right:16.66666667%}.ant-col-sm-offset-4{margin-left:16.66666667%}.ant-col-sm-order-4{order:4}.ant-col-sm-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-sm-push-3{left:12.5%}.ant-col-sm-pull-3{right:12.5%}.ant-col-sm-offset-3{margin-left:12.5%}.ant-col-sm-order-3{order:3}.ant-col-sm-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-sm-push-2{left:8.33333333%}.ant-col-sm-pull-2{right:8.33333333%}.ant-col-sm-offset-2{margin-left:8.33333333%}.ant-col-sm-order-2{order:2}.ant-col-sm-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-sm-push-1{left:4.16666667%}.ant-col-sm-pull-1{right:4.16666667%}.ant-col-sm-offset-1{margin-left:4.16666667%}.ant-col-sm-order-1{order:1}.ant-col-sm-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-sm-push-0{left:auto}.ant-col-sm-pull-0{right:auto}.ant-col-sm-offset-0{margin-left:0}.ant-col-sm-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-sm-push-0.ant-col-rtl{right:auto}.ant-col-sm-pull-0.ant-col-rtl{left:auto}.ant-col-sm-offset-0.ant-col-rtl{margin-right:0}.ant-col-sm-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-sm-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-sm-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-sm-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-sm-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-sm-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-sm-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-sm-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-sm-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-sm-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-sm-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-sm-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-sm-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-sm-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-sm-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-sm-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-sm-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-sm-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-sm-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-sm-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-sm-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-sm-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-sm-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-sm-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-sm-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-sm-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-sm-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-sm-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-sm-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-sm-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-sm-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-sm-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-sm-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-sm-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-sm-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-sm-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-sm-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-sm-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-sm-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-sm-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-sm-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-sm-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-sm-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-sm-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-sm-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-sm-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-sm-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-sm-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-sm-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-sm-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-sm-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-sm-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-sm-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-sm-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-sm-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-sm-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-sm-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-sm-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-sm-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-sm-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-sm-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-sm-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-sm-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-sm-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-sm-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-sm-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-sm-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-sm-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-sm-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-sm-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-sm-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-sm-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 768px){.ant-col-md-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-md-push-24{left:100%}.ant-col-md-pull-24{right:100%}.ant-col-md-offset-24{margin-left:100%}.ant-col-md-order-24{order:24}.ant-col-md-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-md-push-23{left:95.83333333%}.ant-col-md-pull-23{right:95.83333333%}.ant-col-md-offset-23{margin-left:95.83333333%}.ant-col-md-order-23{order:23}.ant-col-md-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-md-push-22{left:91.66666667%}.ant-col-md-pull-22{right:91.66666667%}.ant-col-md-offset-22{margin-left:91.66666667%}.ant-col-md-order-22{order:22}.ant-col-md-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-md-push-21{left:87.5%}.ant-col-md-pull-21{right:87.5%}.ant-col-md-offset-21{margin-left:87.5%}.ant-col-md-order-21{order:21}.ant-col-md-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-md-push-20{left:83.33333333%}.ant-col-md-pull-20{right:83.33333333%}.ant-col-md-offset-20{margin-left:83.33333333%}.ant-col-md-order-20{order:20}.ant-col-md-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-md-push-19{left:79.16666667%}.ant-col-md-pull-19{right:79.16666667%}.ant-col-md-offset-19{margin-left:79.16666667%}.ant-col-md-order-19{order:19}.ant-col-md-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-md-push-18{left:75%}.ant-col-md-pull-18{right:75%}.ant-col-md-offset-18{margin-left:75%}.ant-col-md-order-18{order:18}.ant-col-md-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-md-push-17{left:70.83333333%}.ant-col-md-pull-17{right:70.83333333%}.ant-col-md-offset-17{margin-left:70.83333333%}.ant-col-md-order-17{order:17}.ant-col-md-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-md-push-16{left:66.66666667%}.ant-col-md-pull-16{right:66.66666667%}.ant-col-md-offset-16{margin-left:66.66666667%}.ant-col-md-order-16{order:16}.ant-col-md-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-md-push-15{left:62.5%}.ant-col-md-pull-15{right:62.5%}.ant-col-md-offset-15{margin-left:62.5%}.ant-col-md-order-15{order:15}.ant-col-md-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-md-push-14{left:58.33333333%}.ant-col-md-pull-14{right:58.33333333%}.ant-col-md-offset-14{margin-left:58.33333333%}.ant-col-md-order-14{order:14}.ant-col-md-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-md-push-13{left:54.16666667%}.ant-col-md-pull-13{right:54.16666667%}.ant-col-md-offset-13{margin-left:54.16666667%}.ant-col-md-order-13{order:13}.ant-col-md-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-md-push-12{left:50%}.ant-col-md-pull-12{right:50%}.ant-col-md-offset-12{margin-left:50%}.ant-col-md-order-12{order:12}.ant-col-md-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-md-push-11{left:45.83333333%}.ant-col-md-pull-11{right:45.83333333%}.ant-col-md-offset-11{margin-left:45.83333333%}.ant-col-md-order-11{order:11}.ant-col-md-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-md-push-10{left:41.66666667%}.ant-col-md-pull-10{right:41.66666667%}.ant-col-md-offset-10{margin-left:41.66666667%}.ant-col-md-order-10{order:10}.ant-col-md-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-md-push-9{left:37.5%}.ant-col-md-pull-9{right:37.5%}.ant-col-md-offset-9{margin-left:37.5%}.ant-col-md-order-9{order:9}.ant-col-md-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-md-push-8{left:33.33333333%}.ant-col-md-pull-8{right:33.33333333%}.ant-col-md-offset-8{margin-left:33.33333333%}.ant-col-md-order-8{order:8}.ant-col-md-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-md-push-7{left:29.16666667%}.ant-col-md-pull-7{right:29.16666667%}.ant-col-md-offset-7{margin-left:29.16666667%}.ant-col-md-order-7{order:7}.ant-col-md-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-md-push-6{left:25%}.ant-col-md-pull-6{right:25%}.ant-col-md-offset-6{margin-left:25%}.ant-col-md-order-6{order:6}.ant-col-md-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-md-push-5{left:20.83333333%}.ant-col-md-pull-5{right:20.83333333%}.ant-col-md-offset-5{margin-left:20.83333333%}.ant-col-md-order-5{order:5}.ant-col-md-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-md-push-4{left:16.66666667%}.ant-col-md-pull-4{right:16.66666667%}.ant-col-md-offset-4{margin-left:16.66666667%}.ant-col-md-order-4{order:4}.ant-col-md-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-md-push-3{left:12.5%}.ant-col-md-pull-3{right:12.5%}.ant-col-md-offset-3{margin-left:12.5%}.ant-col-md-order-3{order:3}.ant-col-md-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-md-push-2{left:8.33333333%}.ant-col-md-pull-2{right:8.33333333%}.ant-col-md-offset-2{margin-left:8.33333333%}.ant-col-md-order-2{order:2}.ant-col-md-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-md-push-1{left:4.16666667%}.ant-col-md-pull-1{right:4.16666667%}.ant-col-md-offset-1{margin-left:4.16666667%}.ant-col-md-order-1{order:1}.ant-col-md-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-md-push-0{left:auto}.ant-col-md-pull-0{right:auto}.ant-col-md-offset-0{margin-left:0}.ant-col-md-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-md-push-0.ant-col-rtl{right:auto}.ant-col-md-pull-0.ant-col-rtl{left:auto}.ant-col-md-offset-0.ant-col-rtl{margin-right:0}.ant-col-md-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-md-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-md-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-md-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-md-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-md-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-md-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-md-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-md-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-md-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-md-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-md-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-md-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-md-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-md-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-md-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-md-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-md-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-md-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-md-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-md-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-md-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-md-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-md-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-md-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-md-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-md-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-md-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-md-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-md-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-md-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-md-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-md-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-md-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-md-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-md-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-md-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-md-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-md-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-md-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-md-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-md-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-md-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-md-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-md-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-md-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-md-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-md-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-md-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-md-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-md-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-md-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-md-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-md-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-md-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-md-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-md-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-md-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-md-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-md-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-md-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-md-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-md-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-md-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-md-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-md-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-md-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-md-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-md-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-md-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-md-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-md-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 992px){.ant-col-lg-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-lg-push-24{left:100%}.ant-col-lg-pull-24{right:100%}.ant-col-lg-offset-24{margin-left:100%}.ant-col-lg-order-24{order:24}.ant-col-lg-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-lg-push-23{left:95.83333333%}.ant-col-lg-pull-23{right:95.83333333%}.ant-col-lg-offset-23{margin-left:95.83333333%}.ant-col-lg-order-23{order:23}.ant-col-lg-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-lg-push-22{left:91.66666667%}.ant-col-lg-pull-22{right:91.66666667%}.ant-col-lg-offset-22{margin-left:91.66666667%}.ant-col-lg-order-22{order:22}.ant-col-lg-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-lg-push-21{left:87.5%}.ant-col-lg-pull-21{right:87.5%}.ant-col-lg-offset-21{margin-left:87.5%}.ant-col-lg-order-21{order:21}.ant-col-lg-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-lg-push-20{left:83.33333333%}.ant-col-lg-pull-20{right:83.33333333%}.ant-col-lg-offset-20{margin-left:83.33333333%}.ant-col-lg-order-20{order:20}.ant-col-lg-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-lg-push-19{left:79.16666667%}.ant-col-lg-pull-19{right:79.16666667%}.ant-col-lg-offset-19{margin-left:79.16666667%}.ant-col-lg-order-19{order:19}.ant-col-lg-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-lg-push-18{left:75%}.ant-col-lg-pull-18{right:75%}.ant-col-lg-offset-18{margin-left:75%}.ant-col-lg-order-18{order:18}.ant-col-lg-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-lg-push-17{left:70.83333333%}.ant-col-lg-pull-17{right:70.83333333%}.ant-col-lg-offset-17{margin-left:70.83333333%}.ant-col-lg-order-17{order:17}.ant-col-lg-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-lg-push-16{left:66.66666667%}.ant-col-lg-pull-16{right:66.66666667%}.ant-col-lg-offset-16{margin-left:66.66666667%}.ant-col-lg-order-16{order:16}.ant-col-lg-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-lg-push-15{left:62.5%}.ant-col-lg-pull-15{right:62.5%}.ant-col-lg-offset-15{margin-left:62.5%}.ant-col-lg-order-15{order:15}.ant-col-lg-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-lg-push-14{left:58.33333333%}.ant-col-lg-pull-14{right:58.33333333%}.ant-col-lg-offset-14{margin-left:58.33333333%}.ant-col-lg-order-14{order:14}.ant-col-lg-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-lg-push-13{left:54.16666667%}.ant-col-lg-pull-13{right:54.16666667%}.ant-col-lg-offset-13{margin-left:54.16666667%}.ant-col-lg-order-13{order:13}.ant-col-lg-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-lg-push-12{left:50%}.ant-col-lg-pull-12{right:50%}.ant-col-lg-offset-12{margin-left:50%}.ant-col-lg-order-12{order:12}.ant-col-lg-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-lg-push-11{left:45.83333333%}.ant-col-lg-pull-11{right:45.83333333%}.ant-col-lg-offset-11{margin-left:45.83333333%}.ant-col-lg-order-11{order:11}.ant-col-lg-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-lg-push-10{left:41.66666667%}.ant-col-lg-pull-10{right:41.66666667%}.ant-col-lg-offset-10{margin-left:41.66666667%}.ant-col-lg-order-10{order:10}.ant-col-lg-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-lg-push-9{left:37.5%}.ant-col-lg-pull-9{right:37.5%}.ant-col-lg-offset-9{margin-left:37.5%}.ant-col-lg-order-9{order:9}.ant-col-lg-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-lg-push-8{left:33.33333333%}.ant-col-lg-pull-8{right:33.33333333%}.ant-col-lg-offset-8{margin-left:33.33333333%}.ant-col-lg-order-8{order:8}.ant-col-lg-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-lg-push-7{left:29.16666667%}.ant-col-lg-pull-7{right:29.16666667%}.ant-col-lg-offset-7{margin-left:29.16666667%}.ant-col-lg-order-7{order:7}.ant-col-lg-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-lg-push-6{left:25%}.ant-col-lg-pull-6{right:25%}.ant-col-lg-offset-6{margin-left:25%}.ant-col-lg-order-6{order:6}.ant-col-lg-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-lg-push-5{left:20.83333333%}.ant-col-lg-pull-5{right:20.83333333%}.ant-col-lg-offset-5{margin-left:20.83333333%}.ant-col-lg-order-5{order:5}.ant-col-lg-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-lg-push-4{left:16.66666667%}.ant-col-lg-pull-4{right:16.66666667%}.ant-col-lg-offset-4{margin-left:16.66666667%}.ant-col-lg-order-4{order:4}.ant-col-lg-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-lg-push-3{left:12.5%}.ant-col-lg-pull-3{right:12.5%}.ant-col-lg-offset-3{margin-left:12.5%}.ant-col-lg-order-3{order:3}.ant-col-lg-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-lg-push-2{left:8.33333333%}.ant-col-lg-pull-2{right:8.33333333%}.ant-col-lg-offset-2{margin-left:8.33333333%}.ant-col-lg-order-2{order:2}.ant-col-lg-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-lg-push-1{left:4.16666667%}.ant-col-lg-pull-1{right:4.16666667%}.ant-col-lg-offset-1{margin-left:4.16666667%}.ant-col-lg-order-1{order:1}.ant-col-lg-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-lg-push-0{left:auto}.ant-col-lg-pull-0{right:auto}.ant-col-lg-offset-0{margin-left:0}.ant-col-lg-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-lg-push-0.ant-col-rtl{right:auto}.ant-col-lg-pull-0.ant-col-rtl{left:auto}.ant-col-lg-offset-0.ant-col-rtl{margin-right:0}.ant-col-lg-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-lg-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-lg-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-lg-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-lg-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-lg-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-lg-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-lg-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-lg-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-lg-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-lg-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-lg-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-lg-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-lg-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-lg-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-lg-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-lg-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-lg-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-lg-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-lg-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-lg-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-lg-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-lg-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-lg-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-lg-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-lg-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-lg-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-lg-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-lg-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-lg-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-lg-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-lg-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-lg-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-lg-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-lg-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-lg-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-lg-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-lg-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-lg-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-lg-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-lg-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-lg-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-lg-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-lg-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-lg-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-lg-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-lg-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-lg-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-lg-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-lg-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-lg-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-lg-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-lg-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-lg-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-lg-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-lg-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-lg-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-lg-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-lg-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-lg-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-lg-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-lg-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-lg-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-lg-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-lg-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-lg-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-lg-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-lg-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-lg-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-lg-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-lg-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-lg-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 1200px){.ant-col-xl-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-xl-push-24{left:100%}.ant-col-xl-pull-24{right:100%}.ant-col-xl-offset-24{margin-left:100%}.ant-col-xl-order-24{order:24}.ant-col-xl-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-xl-push-23{left:95.83333333%}.ant-col-xl-pull-23{right:95.83333333%}.ant-col-xl-offset-23{margin-left:95.83333333%}.ant-col-xl-order-23{order:23}.ant-col-xl-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-xl-push-22{left:91.66666667%}.ant-col-xl-pull-22{right:91.66666667%}.ant-col-xl-offset-22{margin-left:91.66666667%}.ant-col-xl-order-22{order:22}.ant-col-xl-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-xl-push-21{left:87.5%}.ant-col-xl-pull-21{right:87.5%}.ant-col-xl-offset-21{margin-left:87.5%}.ant-col-xl-order-21{order:21}.ant-col-xl-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-xl-push-20{left:83.33333333%}.ant-col-xl-pull-20{right:83.33333333%}.ant-col-xl-offset-20{margin-left:83.33333333%}.ant-col-xl-order-20{order:20}.ant-col-xl-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-xl-push-19{left:79.16666667%}.ant-col-xl-pull-19{right:79.16666667%}.ant-col-xl-offset-19{margin-left:79.16666667%}.ant-col-xl-order-19{order:19}.ant-col-xl-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-xl-push-18{left:75%}.ant-col-xl-pull-18{right:75%}.ant-col-xl-offset-18{margin-left:75%}.ant-col-xl-order-18{order:18}.ant-col-xl-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-xl-push-17{left:70.83333333%}.ant-col-xl-pull-17{right:70.83333333%}.ant-col-xl-offset-17{margin-left:70.83333333%}.ant-col-xl-order-17{order:17}.ant-col-xl-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-xl-push-16{left:66.66666667%}.ant-col-xl-pull-16{right:66.66666667%}.ant-col-xl-offset-16{margin-left:66.66666667%}.ant-col-xl-order-16{order:16}.ant-col-xl-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-xl-push-15{left:62.5%}.ant-col-xl-pull-15{right:62.5%}.ant-col-xl-offset-15{margin-left:62.5%}.ant-col-xl-order-15{order:15}.ant-col-xl-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-xl-push-14{left:58.33333333%}.ant-col-xl-pull-14{right:58.33333333%}.ant-col-xl-offset-14{margin-left:58.33333333%}.ant-col-xl-order-14{order:14}.ant-col-xl-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-xl-push-13{left:54.16666667%}.ant-col-xl-pull-13{right:54.16666667%}.ant-col-xl-offset-13{margin-left:54.16666667%}.ant-col-xl-order-13{order:13}.ant-col-xl-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-xl-push-12{left:50%}.ant-col-xl-pull-12{right:50%}.ant-col-xl-offset-12{margin-left:50%}.ant-col-xl-order-12{order:12}.ant-col-xl-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-xl-push-11{left:45.83333333%}.ant-col-xl-pull-11{right:45.83333333%}.ant-col-xl-offset-11{margin-left:45.83333333%}.ant-col-xl-order-11{order:11}.ant-col-xl-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-xl-push-10{left:41.66666667%}.ant-col-xl-pull-10{right:41.66666667%}.ant-col-xl-offset-10{margin-left:41.66666667%}.ant-col-xl-order-10{order:10}.ant-col-xl-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-xl-push-9{left:37.5%}.ant-col-xl-pull-9{right:37.5%}.ant-col-xl-offset-9{margin-left:37.5%}.ant-col-xl-order-9{order:9}.ant-col-xl-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-xl-push-8{left:33.33333333%}.ant-col-xl-pull-8{right:33.33333333%}.ant-col-xl-offset-8{margin-left:33.33333333%}.ant-col-xl-order-8{order:8}.ant-col-xl-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-xl-push-7{left:29.16666667%}.ant-col-xl-pull-7{right:29.16666667%}.ant-col-xl-offset-7{margin-left:29.16666667%}.ant-col-xl-order-7{order:7}.ant-col-xl-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-xl-push-6{left:25%}.ant-col-xl-pull-6{right:25%}.ant-col-xl-offset-6{margin-left:25%}.ant-col-xl-order-6{order:6}.ant-col-xl-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-xl-push-5{left:20.83333333%}.ant-col-xl-pull-5{right:20.83333333%}.ant-col-xl-offset-5{margin-left:20.83333333%}.ant-col-xl-order-5{order:5}.ant-col-xl-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-xl-push-4{left:16.66666667%}.ant-col-xl-pull-4{right:16.66666667%}.ant-col-xl-offset-4{margin-left:16.66666667%}.ant-col-xl-order-4{order:4}.ant-col-xl-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-xl-push-3{left:12.5%}.ant-col-xl-pull-3{right:12.5%}.ant-col-xl-offset-3{margin-left:12.5%}.ant-col-xl-order-3{order:3}.ant-col-xl-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-xl-push-2{left:8.33333333%}.ant-col-xl-pull-2{right:8.33333333%}.ant-col-xl-offset-2{margin-left:8.33333333%}.ant-col-xl-order-2{order:2}.ant-col-xl-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-xl-push-1{left:4.16666667%}.ant-col-xl-pull-1{right:4.16666667%}.ant-col-xl-offset-1{margin-left:4.16666667%}.ant-col-xl-order-1{order:1}.ant-col-xl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xl-push-0{left:auto}.ant-col-xl-pull-0{right:auto}.ant-col-xl-offset-0{margin-left:0}.ant-col-xl-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-xl-push-0.ant-col-rtl{right:auto}.ant-col-xl-pull-0.ant-col-rtl{left:auto}.ant-col-xl-offset-0.ant-col-rtl{margin-right:0}.ant-col-xl-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-xl-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-xl-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-xl-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-xl-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-xl-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-xl-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-xl-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-xl-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-xl-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-xl-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-xl-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-xl-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-xl-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-xl-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-xl-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-xl-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-xl-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-xl-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-xl-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-xl-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-xl-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-xl-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-xl-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-xl-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-xl-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-xl-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-xl-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-xl-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-xl-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-xl-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-xl-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-xl-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-xl-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-xl-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-xl-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-xl-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-xl-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-xl-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-xl-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-xl-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-xl-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-xl-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-xl-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-xl-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-xl-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-xl-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-xl-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-xl-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-xl-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-xl-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-xl-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-xl-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-xl-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-xl-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-xl-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-xl-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-xl-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-xl-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-xl-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-xl-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-xl-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-xl-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-xl-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-xl-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-xl-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-xl-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-xl-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-xl-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-xl-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-xl-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-xl-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 1600px){.ant-col-xxl-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-xxl-push-24{left:100%}.ant-col-xxl-pull-24{right:100%}.ant-col-xxl-offset-24{margin-left:100%}.ant-col-xxl-order-24{order:24}.ant-col-xxl-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-xxl-push-23{left:95.83333333%}.ant-col-xxl-pull-23{right:95.83333333%}.ant-col-xxl-offset-23{margin-left:95.83333333%}.ant-col-xxl-order-23{order:23}.ant-col-xxl-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-xxl-push-22{left:91.66666667%}.ant-col-xxl-pull-22{right:91.66666667%}.ant-col-xxl-offset-22{margin-left:91.66666667%}.ant-col-xxl-order-22{order:22}.ant-col-xxl-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-xxl-push-21{left:87.5%}.ant-col-xxl-pull-21{right:87.5%}.ant-col-xxl-offset-21{margin-left:87.5%}.ant-col-xxl-order-21{order:21}.ant-col-xxl-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-xxl-push-20{left:83.33333333%}.ant-col-xxl-pull-20{right:83.33333333%}.ant-col-xxl-offset-20{margin-left:83.33333333%}.ant-col-xxl-order-20{order:20}.ant-col-xxl-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-xxl-push-19{left:79.16666667%}.ant-col-xxl-pull-19{right:79.16666667%}.ant-col-xxl-offset-19{margin-left:79.16666667%}.ant-col-xxl-order-19{order:19}.ant-col-xxl-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-xxl-push-18{left:75%}.ant-col-xxl-pull-18{right:75%}.ant-col-xxl-offset-18{margin-left:75%}.ant-col-xxl-order-18{order:18}.ant-col-xxl-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-xxl-push-17{left:70.83333333%}.ant-col-xxl-pull-17{right:70.83333333%}.ant-col-xxl-offset-17{margin-left:70.83333333%}.ant-col-xxl-order-17{order:17}.ant-col-xxl-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-xxl-push-16{left:66.66666667%}.ant-col-xxl-pull-16{right:66.66666667%}.ant-col-xxl-offset-16{margin-left:66.66666667%}.ant-col-xxl-order-16{order:16}.ant-col-xxl-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-xxl-push-15{left:62.5%}.ant-col-xxl-pull-15{right:62.5%}.ant-col-xxl-offset-15{margin-left:62.5%}.ant-col-xxl-order-15{order:15}.ant-col-xxl-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-xxl-push-14{left:58.33333333%}.ant-col-xxl-pull-14{right:58.33333333%}.ant-col-xxl-offset-14{margin-left:58.33333333%}.ant-col-xxl-order-14{order:14}.ant-col-xxl-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-xxl-push-13{left:54.16666667%}.ant-col-xxl-pull-13{right:54.16666667%}.ant-col-xxl-offset-13{margin-left:54.16666667%}.ant-col-xxl-order-13{order:13}.ant-col-xxl-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-xxl-push-12{left:50%}.ant-col-xxl-pull-12{right:50%}.ant-col-xxl-offset-12{margin-left:50%}.ant-col-xxl-order-12{order:12}.ant-col-xxl-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-xxl-push-11{left:45.83333333%}.ant-col-xxl-pull-11{right:45.83333333%}.ant-col-xxl-offset-11{margin-left:45.83333333%}.ant-col-xxl-order-11{order:11}.ant-col-xxl-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-xxl-push-10{left:41.66666667%}.ant-col-xxl-pull-10{right:41.66666667%}.ant-col-xxl-offset-10{margin-left:41.66666667%}.ant-col-xxl-order-10{order:10}.ant-col-xxl-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-xxl-push-9{left:37.5%}.ant-col-xxl-pull-9{right:37.5%}.ant-col-xxl-offset-9{margin-left:37.5%}.ant-col-xxl-order-9{order:9}.ant-col-xxl-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-xxl-push-8{left:33.33333333%}.ant-col-xxl-pull-8{right:33.33333333%}.ant-col-xxl-offset-8{margin-left:33.33333333%}.ant-col-xxl-order-8{order:8}.ant-col-xxl-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-xxl-push-7{left:29.16666667%}.ant-col-xxl-pull-7{right:29.16666667%}.ant-col-xxl-offset-7{margin-left:29.16666667%}.ant-col-xxl-order-7{order:7}.ant-col-xxl-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-xxl-push-6{left:25%}.ant-col-xxl-pull-6{right:25%}.ant-col-xxl-offset-6{margin-left:25%}.ant-col-xxl-order-6{order:6}.ant-col-xxl-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-xxl-push-5{left:20.83333333%}.ant-col-xxl-pull-5{right:20.83333333%}.ant-col-xxl-offset-5{margin-left:20.83333333%}.ant-col-xxl-order-5{order:5}.ant-col-xxl-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-xxl-push-4{left:16.66666667%}.ant-col-xxl-pull-4{right:16.66666667%}.ant-col-xxl-offset-4{margin-left:16.66666667%}.ant-col-xxl-order-4{order:4}.ant-col-xxl-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-xxl-push-3{left:12.5%}.ant-col-xxl-pull-3{right:12.5%}.ant-col-xxl-offset-3{margin-left:12.5%}.ant-col-xxl-order-3{order:3}.ant-col-xxl-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-xxl-push-2{left:8.33333333%}.ant-col-xxl-pull-2{right:8.33333333%}.ant-col-xxl-offset-2{margin-left:8.33333333%}.ant-col-xxl-order-2{order:2}.ant-col-xxl-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-xxl-push-1{left:4.16666667%}.ant-col-xxl-pull-1{right:4.16666667%}.ant-col-xxl-offset-1{margin-left:4.16666667%}.ant-col-xxl-order-1{order:1}.ant-col-xxl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xxl-push-0{left:auto}.ant-col-xxl-pull-0{right:auto}.ant-col-xxl-offset-0{margin-left:0}.ant-col-xxl-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-xxl-push-0.ant-col-rtl{right:auto}.ant-col-xxl-pull-0.ant-col-rtl{left:auto}.ant-col-xxl-offset-0.ant-col-rtl{margin-right:0}.ant-col-xxl-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-xxl-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-xxl-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-xxl-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-xxl-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-xxl-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-xxl-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-xxl-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-xxl-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-xxl-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-xxl-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-xxl-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-xxl-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-xxl-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-xxl-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-xxl-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-xxl-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-xxl-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-xxl-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-xxl-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-xxl-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-xxl-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-xxl-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-xxl-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-xxl-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-xxl-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-xxl-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-xxl-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-xxl-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-xxl-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-xxl-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-xxl-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-xxl-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-xxl-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-xxl-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-xxl-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-xxl-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-xxl-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-xxl-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-xxl-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-xxl-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-xxl-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-xxl-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-xxl-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-xxl-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-xxl-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-xxl-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-xxl-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-xxl-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-xxl-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-xxl-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-xxl-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-xxl-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-xxl-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-xxl-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-xxl-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-xxl-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-xxl-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-xxl-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-xxl-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-xxl-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-xxl-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-xxl-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-xxl-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-xxl-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-xxl-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-xxl-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-xxl-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-xxl-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-xxl-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-xxl-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-xxl-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 2000px){.ant-col-xxxl-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-xxxl-push-24{left:100%}.ant-col-xxxl-pull-24{right:100%}.ant-col-xxxl-offset-24{margin-left:100%}.ant-col-xxxl-order-24{order:24}.ant-col-xxxl-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-xxxl-push-23{left:95.83333333%}.ant-col-xxxl-pull-23{right:95.83333333%}.ant-col-xxxl-offset-23{margin-left:95.83333333%}.ant-col-xxxl-order-23{order:23}.ant-col-xxxl-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-xxxl-push-22{left:91.66666667%}.ant-col-xxxl-pull-22{right:91.66666667%}.ant-col-xxxl-offset-22{margin-left:91.66666667%}.ant-col-xxxl-order-22{order:22}.ant-col-xxxl-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-xxxl-push-21{left:87.5%}.ant-col-xxxl-pull-21{right:87.5%}.ant-col-xxxl-offset-21{margin-left:87.5%}.ant-col-xxxl-order-21{order:21}.ant-col-xxxl-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-xxxl-push-20{left:83.33333333%}.ant-col-xxxl-pull-20{right:83.33333333%}.ant-col-xxxl-offset-20{margin-left:83.33333333%}.ant-col-xxxl-order-20{order:20}.ant-col-xxxl-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-xxxl-push-19{left:79.16666667%}.ant-col-xxxl-pull-19{right:79.16666667%}.ant-col-xxxl-offset-19{margin-left:79.16666667%}.ant-col-xxxl-order-19{order:19}.ant-col-xxxl-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-xxxl-push-18{left:75%}.ant-col-xxxl-pull-18{right:75%}.ant-col-xxxl-offset-18{margin-left:75%}.ant-col-xxxl-order-18{order:18}.ant-col-xxxl-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-xxxl-push-17{left:70.83333333%}.ant-col-xxxl-pull-17{right:70.83333333%}.ant-col-xxxl-offset-17{margin-left:70.83333333%}.ant-col-xxxl-order-17{order:17}.ant-col-xxxl-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-xxxl-push-16{left:66.66666667%}.ant-col-xxxl-pull-16{right:66.66666667%}.ant-col-xxxl-offset-16{margin-left:66.66666667%}.ant-col-xxxl-order-16{order:16}.ant-col-xxxl-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-xxxl-push-15{left:62.5%}.ant-col-xxxl-pull-15{right:62.5%}.ant-col-xxxl-offset-15{margin-left:62.5%}.ant-col-xxxl-order-15{order:15}.ant-col-xxxl-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-xxxl-push-14{left:58.33333333%}.ant-col-xxxl-pull-14{right:58.33333333%}.ant-col-xxxl-offset-14{margin-left:58.33333333%}.ant-col-xxxl-order-14{order:14}.ant-col-xxxl-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-xxxl-push-13{left:54.16666667%}.ant-col-xxxl-pull-13{right:54.16666667%}.ant-col-xxxl-offset-13{margin-left:54.16666667%}.ant-col-xxxl-order-13{order:13}.ant-col-xxxl-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-xxxl-push-12{left:50%}.ant-col-xxxl-pull-12{right:50%}.ant-col-xxxl-offset-12{margin-left:50%}.ant-col-xxxl-order-12{order:12}.ant-col-xxxl-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-xxxl-push-11{left:45.83333333%}.ant-col-xxxl-pull-11{right:45.83333333%}.ant-col-xxxl-offset-11{margin-left:45.83333333%}.ant-col-xxxl-order-11{order:11}.ant-col-xxxl-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-xxxl-push-10{left:41.66666667%}.ant-col-xxxl-pull-10{right:41.66666667%}.ant-col-xxxl-offset-10{margin-left:41.66666667%}.ant-col-xxxl-order-10{order:10}.ant-col-xxxl-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-xxxl-push-9{left:37.5%}.ant-col-xxxl-pull-9{right:37.5%}.ant-col-xxxl-offset-9{margin-left:37.5%}.ant-col-xxxl-order-9{order:9}.ant-col-xxxl-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-xxxl-push-8{left:33.33333333%}.ant-col-xxxl-pull-8{right:33.33333333%}.ant-col-xxxl-offset-8{margin-left:33.33333333%}.ant-col-xxxl-order-8{order:8}.ant-col-xxxl-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-xxxl-push-7{left:29.16666667%}.ant-col-xxxl-pull-7{right:29.16666667%}.ant-col-xxxl-offset-7{margin-left:29.16666667%}.ant-col-xxxl-order-7{order:7}.ant-col-xxxl-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-xxxl-push-6{left:25%}.ant-col-xxxl-pull-6{right:25%}.ant-col-xxxl-offset-6{margin-left:25%}.ant-col-xxxl-order-6{order:6}.ant-col-xxxl-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-xxxl-push-5{left:20.83333333%}.ant-col-xxxl-pull-5{right:20.83333333%}.ant-col-xxxl-offset-5{margin-left:20.83333333%}.ant-col-xxxl-order-5{order:5}.ant-col-xxxl-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-xxxl-push-4{left:16.66666667%}.ant-col-xxxl-pull-4{right:16.66666667%}.ant-col-xxxl-offset-4{margin-left:16.66666667%}.ant-col-xxxl-order-4{order:4}.ant-col-xxxl-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-xxxl-push-3{left:12.5%}.ant-col-xxxl-pull-3{right:12.5%}.ant-col-xxxl-offset-3{margin-left:12.5%}.ant-col-xxxl-order-3{order:3}.ant-col-xxxl-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-xxxl-push-2{left:8.33333333%}.ant-col-xxxl-pull-2{right:8.33333333%}.ant-col-xxxl-offset-2{margin-left:8.33333333%}.ant-col-xxxl-order-2{order:2}.ant-col-xxxl-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-xxxl-push-1{left:4.16666667%}.ant-col-xxxl-pull-1{right:4.16666667%}.ant-col-xxxl-offset-1{margin-left:4.16666667%}.ant-col-xxxl-order-1{order:1}.ant-col-xxxl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xxxl-push-0{left:auto}.ant-col-xxxl-pull-0{right:auto}.ant-col-xxxl-offset-0{margin-left:0}.ant-col-xxxl-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-xxxl-push-0.ant-col-rtl{right:auto}.ant-col-xxxl-pull-0.ant-col-rtl{left:auto}.ant-col-xxxl-offset-0.ant-col-rtl{margin-right:0}.ant-col-xxxl-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-xxxl-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-xxxl-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-xxxl-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-xxxl-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-xxxl-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-xxxl-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-xxxl-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-xxxl-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-xxxl-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-xxxl-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-xxxl-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-xxxl-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-xxxl-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-xxxl-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-xxxl-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-xxxl-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-xxxl-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-xxxl-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-xxxl-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-xxxl-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-xxxl-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-xxxl-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-xxxl-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-xxxl-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-xxxl-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-xxxl-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-xxxl-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-xxxl-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-xxxl-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-xxxl-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-xxxl-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-xxxl-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-xxxl-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-xxxl-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-xxxl-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-xxxl-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-xxxl-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-xxxl-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-xxxl-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-xxxl-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-xxxl-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-xxxl-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-xxxl-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-xxxl-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-xxxl-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-xxxl-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-xxxl-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-xxxl-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-xxxl-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-xxxl-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-xxxl-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-xxxl-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-xxxl-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-xxxl-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-xxxl-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-xxxl-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-xxxl-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-xxxl-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-xxxl-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-xxxl-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-xxxl-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-xxxl-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-xxxl-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-xxxl-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-xxxl-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-xxxl-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-xxxl-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-xxxl-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-xxxl-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-xxxl-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-xxxl-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}.ant-row-rtl{direction:rtl}/*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/carousel/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-carousel{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum"}.ant-carousel .slick-slider{position:relative;display:block;box-sizing:border-box;touch-action:pan-y;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent}.ant-carousel .slick-list{position:relative;display:block;margin:0;padding:0;overflow:hidden}.ant-carousel .slick-list:focus{outline:none}.ant-carousel .slick-list.dragging{cursor:pointer}.ant-carousel .slick-list .slick-slide{pointer-events:none}.ant-carousel .slick-list .slick-slide input.ant-radio-input,.ant-carousel .slick-list .slick-slide input.ant-checkbox-input{visibility:hidden}.ant-carousel .slick-list .slick-slide.slick-active{pointer-events:auto}.ant-carousel .slick-list .slick-slide.slick-active input.ant-radio-input,.ant-carousel .slick-list .slick-slide.slick-active input.ant-checkbox-input{visibility:visible}.ant-carousel .slick-list .slick-slide>div>div{vertical-align:bottom}.ant-carousel .slick-slider .slick-track,.ant-carousel .slick-slider .slick-list{transform:translateZ(0);touch-action:pan-y}.ant-carousel .slick-track{position:relative;top:0;left:0;display:block}.ant-carousel .slick-track:before,.ant-carousel .slick-track:after{display:table;content:""}.ant-carousel .slick-track:after{clear:both}.slick-loading .ant-carousel .slick-track{visibility:hidden}.ant-carousel .slick-slide{display:none;float:left;height:100%;min-height:1px}.ant-carousel .slick-slide img{display:block}.ant-carousel .slick-slide.slick-loading img{display:none}.ant-carousel .slick-slide.dragging img{pointer-events:none}.ant-carousel .slick-initialized .slick-slide{display:block}.ant-carousel .slick-loading .slick-slide{visibility:hidden}.ant-carousel .slick-vertical .slick-slide{display:block;height:auto}.ant-carousel .slick-arrow.slick-hidden{display:none}.ant-carousel .slick-prev,.ant-carousel .slick-next{position:absolute;top:50%;display:block;width:20px;height:20px;margin-top:-10px;padding:0;color:transparent;font-size:0;line-height:0;background:transparent;border:0;outline:none;cursor:pointer}.ant-carousel .slick-prev:hover,.ant-carousel .slick-next:hover,.ant-carousel .slick-prev:focus,.ant-carousel .slick-next:focus{color:transparent;background:transparent;outline:none}.ant-carousel .slick-prev:hover:before,.ant-carousel .slick-next:hover:before,.ant-carousel .slick-prev:focus:before,.ant-carousel .slick-next:focus:before{opacity:1}.ant-carousel .slick-prev.slick-disabled:before,.ant-carousel .slick-next.slick-disabled:before{opacity:.25}.ant-carousel .slick-prev{left:-25px}.ant-carousel .slick-prev:before{content:"←"}.ant-carousel .slick-next{right:-25px}.ant-carousel .slick-next:before{content:"→"}.ant-carousel .slick-dots{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex!important;justify-content:center;margin-right:15%;margin-left:15%;padding-left:0;list-style:none}.ant-carousel .slick-dots-bottom{bottom:12px}.ant-carousel .slick-dots-top{top:12px;bottom:auto}.ant-carousel .slick-dots li{position:relative;display:inline-block;flex:0 1 auto;box-sizing:content-box;width:16px;height:3px;margin:0 3px;padding:0;text-align:center;text-indent:-999px;vertical-align:top;transition:all .5s}.ant-carousel .slick-dots li button{display:block;width:100%;height:3px;padding:0;color:transparent;font-size:0;background:#141414;border:0;border-radius:1px;outline:none;cursor:pointer;opacity:.3;transition:all .5s}.ant-carousel .slick-dots li button:hover,.ant-carousel .slick-dots li button:focus{opacity:.75}.ant-carousel .slick-dots li.slick-active{width:24px}.ant-carousel .slick-dots li.slick-active button{background:#141414;opacity:1}.ant-carousel .slick-dots li.slick-active:hover,.ant-carousel .slick-dots li.slick-active:focus{opacity:1}.ant-carousel-vertical .slick-dots{top:50%;bottom:auto;flex-direction:column;width:3px;height:auto;margin:0;transform:translateY(-50%)}.ant-carousel-vertical .slick-dots-left{right:auto;left:12px}.ant-carousel-vertical .slick-dots-right{right:12px;left:auto}.ant-carousel-vertical .slick-dots li{width:3px;height:16px;margin:4px 2px;vertical-align:baseline}.ant-carousel-vertical .slick-dots li button{width:3px;height:16px}.ant-carousel-vertical .slick-dots li.slick-active,.ant-carousel-vertical .slick-dots li.slick-active button{width:3px;height:24px}.ant-carousel-rtl{direction:rtl}.ant-carousel-rtl .ant-carousel .slick-track{right:0;left:auto}.ant-carousel-rtl .ant-carousel .slick-prev{right:-25px;left:auto}.ant-carousel-rtl .ant-carousel .slick-prev:before{content:"→"}.ant-carousel-rtl .ant-carousel .slick-next{right:auto;left:-25px}.ant-carousel-rtl .ant-carousel .slick-next:before{content:"←"}.ant-carousel-rtl.ant-carousel .slick-dots{flex-direction:row-reverse}.ant-carousel-rtl.ant-carousel-vertical .slick-dots{flex-direction:column}/*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/cascader/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-cascader-checkbox{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;line-height:1;white-space:nowrap;outline:none;cursor:pointer}.ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox-inner,.ant-cascader-checkbox:hover .ant-cascader-checkbox-inner,.ant-cascader-checkbox-input:focus+.ant-cascader-checkbox-inner{border-color:#177ddc}.ant-cascader-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #177ddc;border-radius:2px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-cascader-checkbox:hover:after,.ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox:after{visibility:visible}.ant-cascader-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;direction:ltr;background-color:transparent;border:1px solid #434343;border-radius:2px;border-collapse:separate;transition:all .3s}.ant-cascader-checkbox-inner:after{position:absolute;top:50%;left:21.5%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-cascader-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-cascader-checkbox-checked .ant-cascader-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-cascader-checkbox-checked .ant-cascader-checkbox-inner{background-color:#177ddc;border-color:#177ddc}.ant-cascader-checkbox-disabled{cursor:not-allowed}.ant-cascader-checkbox-disabled.ant-cascader-checkbox-checked .ant-cascader-checkbox-inner:after{border-color:#ffffff4d;animation-name:none}.ant-cascader-checkbox-disabled .ant-cascader-checkbox-input{cursor:not-allowed;pointer-events:none}.ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner{background-color:#ffffff14;border-color:#434343!important}.ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner:after{border-color:#ffffff14;border-collapse:separate;animation-name:none}.ant-cascader-checkbox-disabled+span{color:#ffffff4d;cursor:not-allowed}.ant-cascader-checkbox-disabled:hover:after,.ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox-disabled:after{visibility:hidden}.ant-cascader-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-flex;align-items:baseline;line-height:unset;cursor:pointer}.ant-cascader-checkbox-wrapper:after{display:inline-block;width:0;overflow:hidden;content:" "}.ant-cascader-checkbox-wrapper.ant-cascader-checkbox-wrapper-disabled{cursor:not-allowed}.ant-cascader-checkbox-wrapper+.ant-cascader-checkbox-wrapper{margin-left:8px}.ant-cascader-checkbox+span{padding-right:8px;padding-left:8px}.ant-cascader-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-cascader-checkbox-group-item{margin-right:8px}.ant-cascader-checkbox-group-item:last-child{margin-right:0}.ant-cascader-checkbox-group-item+.ant-cascader-checkbox-group-item{margin-left:0}.ant-cascader-checkbox-indeterminate .ant-cascader-checkbox-inner{background-color:transparent;border-color:#434343}.ant-cascader-checkbox-indeterminate .ant-cascader-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#177ddc;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-cascader-checkbox-indeterminate.ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner:after{background-color:#ffffff4d;border-color:#ffffff4d}.ant-cascader{width:184px}.ant-cascader-checkbox{top:0;margin-right:8px}.ant-cascader-menus{display:flex;flex-wrap:nowrap;align-items:flex-start}.ant-cascader-menus.ant-cascader-menu-empty .ant-cascader-menu{width:100%;height:auto}.ant-cascader-menu{min-width:111px;height:180px;margin:-4px 0;padding:4px 0;overflow:auto;vertical-align:top;list-style:none;border-right:1px solid #303030;-ms-overflow-style:-ms-autohiding-scrollbar}.ant-cascader-menu-item{display:flex;flex-wrap:nowrap;align-items:center;padding:5px 12px;overflow:hidden;line-height:22px;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;transition:all .3s}.ant-cascader-menu-item:hover{background:rgba(255,255,255,.08)}.ant-cascader-menu-item-disabled{color:#ffffff4d;cursor:not-allowed}.ant-cascader-menu-item-disabled:hover{background:transparent}.ant-cascader-menu-empty .ant-cascader-menu-item{color:#ffffff4d;cursor:default;pointer-events:none}.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled),.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled):hover{font-weight:600;background-color:#111b26}.ant-cascader-menu-item-content{flex:auto}.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,.ant-cascader-menu-item-loading-icon{margin-left:4px;color:#ffffff73;font-size:10px}.ant-cascader-menu-item-disabled.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,.ant-cascader-menu-item-disabled.ant-cascader-menu-item-loading-icon{color:#ffffff4d}.ant-cascader-menu-item-keyword{color:#a61d24}.ant-cascader-rtl .ant-cascader-menu-item-expand-icon,.ant-cascader-rtl .ant-cascader-menu-item-loading-icon{margin-right:4px;margin-left:0}.ant-cascader-rtl .ant-cascader-checkbox{top:0;margin-right:0;margin-left:8px}/*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/checkbox/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-checkbox{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;line-height:1;white-space:nowrap;outline:none;cursor:pointer}.ant-checkbox-wrapper:hover .ant-checkbox-inner,.ant-checkbox:hover .ant-checkbox-inner,.ant-checkbox-input:focus+.ant-checkbox-inner{border-color:#177ddc}.ant-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #177ddc;border-radius:2px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-checkbox:hover:after,.ant-checkbox-wrapper:hover .ant-checkbox:after{visibility:visible}.ant-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;direction:ltr;background-color:transparent;border:1px solid #434343;border-radius:2px;border-collapse:separate;transition:all .3s}.ant-checkbox-inner:after{position:absolute;top:50%;left:21.5%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-checkbox-checked .ant-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-checkbox-checked .ant-checkbox-inner{background-color:#177ddc;border-color:#177ddc}.ant-checkbox-disabled{cursor:not-allowed}.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner:after{border-color:#ffffff4d;animation-name:none}.ant-checkbox-disabled .ant-checkbox-input{cursor:not-allowed;pointer-events:none}.ant-checkbox-disabled .ant-checkbox-inner{background-color:#ffffff14;border-color:#434343!important}.ant-checkbox-disabled .ant-checkbox-inner:after{border-color:#ffffff14;border-collapse:separate;animation-name:none}.ant-checkbox-disabled+span{color:#ffffff4d;cursor:not-allowed}.ant-checkbox-disabled:hover:after,.ant-checkbox-wrapper:hover .ant-checkbox-disabled:after{visibility:hidden}.ant-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-flex;align-items:baseline;line-height:unset;cursor:pointer}.ant-checkbox-wrapper:after{display:inline-block;width:0;overflow:hidden;content:" "}.ant-checkbox-wrapper.ant-checkbox-wrapper-disabled{cursor:not-allowed}.ant-checkbox-wrapper+.ant-checkbox-wrapper{margin-left:8px}.ant-checkbox+span{padding-right:8px;padding-left:8px}.ant-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-checkbox-group-item{margin-right:8px}.ant-checkbox-group-item:last-child{margin-right:0}.ant-checkbox-group-item+.ant-checkbox-group-item{margin-left:0}.ant-checkbox-indeterminate .ant-checkbox-inner{background-color:transparent;border-color:#434343}.ant-checkbox-indeterminate .ant-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#177ddc;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-checkbox-indeterminate.ant-checkbox-disabled .ant-checkbox-inner:after{background-color:#ffffff4d;border-color:#ffffff4d}.ant-checkbox-rtl{direction:rtl}.ant-checkbox-group-rtl .ant-checkbox-group-item{margin-right:0;margin-left:8px}.ant-checkbox-group-rtl .ant-checkbox-group-item:last-child{margin-left:0!important}.ant-checkbox-group-rtl .ant-checkbox-group-item+.ant-checkbox-group-item{margin-left:8px}/*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/collapse/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-collapse{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";background-color:#ffffff0a;border:1px solid #434343;border-bottom:0;border-radius:2px}.ant-collapse>.ant-collapse-item{border-bottom:1px solid #434343}.ant-collapse>.ant-collapse-item:last-child,.ant-collapse>.ant-collapse-item:last-child>.ant-collapse-header{border-radius:0 0 2px 2px}.ant-collapse>.ant-collapse-item>.ant-collapse-header{position:relative;display:flex;flex-wrap:nowrap;align-items:flex-start;padding:12px 16px;color:#ffffffd9;line-height:1.5715;cursor:pointer;transition:all .3s,visibility 0s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{display:inline-block;margin-right:12px;font-size:12px;vertical-align:-1px}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{transition:transform .24s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-extra{margin-left:auto}.ant-collapse>.ant-collapse-item>.ant-collapse-header:focus{outline:none}.ant-collapse>.ant-collapse-item .ant-collapse-header-collapsible-only{cursor:default}.ant-collapse>.ant-collapse-item .ant-collapse-header-collapsible-only .ant-collapse-header-text{cursor:pointer}.ant-collapse>.ant-collapse-item.ant-collapse-no-arrow>.ant-collapse-header{padding-left:12px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header{position:relative;padding:12px 40px 12px 16px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{position:absolute;top:50%;right:16px;left:auto;margin:0;transform:translateY(-50%)}.ant-collapse-content{color:#ffffffd9;background-color:#141414;border-top:1px solid #434343}.ant-collapse-content>.ant-collapse-content-box{padding:16px}.ant-collapse-content-hidden{display:none}.ant-collapse-item:last-child>.ant-collapse-content{border-radius:0 0 2px 2px}.ant-collapse-borderless{background-color:#ffffff0a;border:0}.ant-collapse-borderless>.ant-collapse-item{border-bottom:1px solid #434343}.ant-collapse-borderless>.ant-collapse-item:last-child,.ant-collapse-borderless>.ant-collapse-item:last-child .ant-collapse-header{border-radius:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content{background-color:transparent;border-top:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box{padding-top:4px}.ant-collapse-ghost{background-color:transparent;border:0}.ant-collapse-ghost>.ant-collapse-item{border-bottom:0}.ant-collapse-ghost>.ant-collapse-item>.ant-collapse-content{background-color:transparent;border-top:0}.ant-collapse-ghost>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box{padding-top:12px;padding-bottom:12px}.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:#ffffff4d;cursor:not-allowed}.ant-collapse-rtl{direction:rtl}.ant-collapse-rtl .ant-collapse>.ant-collapse-item>.ant-collapse-header{padding:12px 40px 12px 16px}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{margin-right:0;margin-left:12px}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{transform:rotate(180deg)}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-extra{margin-right:auto;margin-left:0}.ant-collapse-rtl.ant-collapse>.ant-collapse-item.ant-collapse-no-arrow>.ant-collapse-header{padding-right:12px;padding-left:0}/*!************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/comment/style/index.less ***! + \\************************************************************************************************************************************************************************************************************************************************************/.ant-comment{position:relative;background-color:transparent}.ant-comment-inner{display:flex;padding:16px 0}.ant-comment-avatar{position:relative;flex-shrink:0;margin-right:12px;cursor:pointer}.ant-comment-avatar img{width:32px;height:32px;border-radius:50%}.ant-comment-content{position:relative;flex:1 1 auto;min-width:1px;font-size:14px;word-wrap:break-word}.ant-comment-content-author{display:flex;flex-wrap:wrap;justify-content:flex-start;margin-bottom:4px;font-size:14px}.ant-comment-content-author>a,.ant-comment-content-author>span{padding-right:8px;font-size:12px;line-height:18px}.ant-comment-content-author-name{color:#ffffff73;font-size:14px;transition:color .3s}.ant-comment-content-author-name>*{color:#ffffff73}.ant-comment-content-author-name>*:hover{color:#ffffff73}.ant-comment-content-author-time{color:#ffffff4d;white-space:nowrap;cursor:auto}.ant-comment-content-detail p{margin-bottom:inherit;white-space:pre-wrap}.ant-comment-actions{margin-top:12px;margin-bottom:inherit;padding-left:0}.ant-comment-actions>li{display:inline-block;color:#ffffff73}.ant-comment-actions>li>span{margin-right:10px;color:#ffffff73;font-size:12px;cursor:pointer;transition:color .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-comment-actions>li>span:hover{color:#ffffffa6}.ant-comment-nested{margin-left:44px}.ant-comment-rtl{direction:rtl}.ant-comment-rtl .ant-comment-avatar{margin-right:0;margin-left:12px}.ant-comment-rtl .ant-comment-content-author>a,.ant-comment-rtl .ant-comment-content-author>span{padding-right:0;padding-left:8px}.ant-comment-rtl .ant-comment-actions{padding-right:0}.ant-comment-rtl .ant-comment-actions>li>span{margin-right:0;margin-left:10px}.ant-comment-rtl .ant-comment-nested{margin-right:44px;margin-left:0}/*!********************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/config-provider/style/index.less ***! + \\********************************************************************************************************************************************************************************************************************************************************************//*!*****************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/descriptions/style/index.less ***! + \\*****************************************************************************************************************************************************************************************************************************************************************/.ant-descriptions-header{display:flex;align-items:center;margin-bottom:20px}.ant-descriptions-title{flex:auto;overflow:hidden;color:#ffffffd9;font-weight:700;font-size:16px;line-height:1.5715;white-space:nowrap;text-overflow:ellipsis}.ant-descriptions-extra{margin-left:auto;color:#ffffffd9;font-size:14px}.ant-descriptions-view{width:100%;border-radius:2px}.ant-descriptions-view table{width:100%;table-layout:fixed}.ant-descriptions-row>th,.ant-descriptions-row>td{padding-bottom:16px}.ant-descriptions-row:last-child{border-bottom:none}.ant-descriptions-item-label{color:#ffffffd9;font-weight:400;font-size:14px;line-height:1.5715;text-align:start}.ant-descriptions-item-label:after{content:":";position:relative;top:-.5px;margin:0 8px 0 2px}.ant-descriptions-item-label.ant-descriptions-item-no-colon:after{content:" "}.ant-descriptions-item-no-label:after{margin:0;content:""}.ant-descriptions-item-content{display:table-cell;flex:1;color:#ffffffd9;font-size:14px;line-height:1.5715;word-break:break-word;overflow-wrap:break-word}.ant-descriptions-item{padding-bottom:0;vertical-align:top}.ant-descriptions-item-container{display:flex}.ant-descriptions-item-container .ant-descriptions-item-label,.ant-descriptions-item-container .ant-descriptions-item-content{display:inline-flex;align-items:baseline}.ant-descriptions-middle .ant-descriptions-row>th,.ant-descriptions-middle .ant-descriptions-row>td{padding-bottom:12px}.ant-descriptions-small .ant-descriptions-row>th,.ant-descriptions-small .ant-descriptions-row>td{padding-bottom:8px}.ant-descriptions-bordered .ant-descriptions-view{border:1px solid #303030}.ant-descriptions-bordered .ant-descriptions-view>table{table-layout:auto;border-collapse:collapse}.ant-descriptions-bordered .ant-descriptions-item-label,.ant-descriptions-bordered .ant-descriptions-item-content{padding:16px 24px;border-right:1px solid #303030}.ant-descriptions-bordered .ant-descriptions-item-label:last-child,.ant-descriptions-bordered .ant-descriptions-item-content:last-child{border-right:none}.ant-descriptions-bordered .ant-descriptions-item-label{background-color:#ffffff0a}.ant-descriptions-bordered .ant-descriptions-item-label:after{display:none}.ant-descriptions-bordered .ant-descriptions-row{border-bottom:1px solid #303030}.ant-descriptions-bordered .ant-descriptions-row:last-child{border-bottom:none}.ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-label,.ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-content{padding:12px 24px}.ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-label,.ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-content{padding:8px 16px}.ant-descriptions-rtl{direction:rtl}.ant-descriptions-rtl .ant-descriptions-item-label:after{margin:0 2px 0 8px}.ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-label,.ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-content{border-right:none;border-left:1px solid #303030}.ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-label:last-child,.ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-content:last-child{border-left:none}/*!************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/divider/style/index.less ***! + \\************************************************************************************************************************************************************************************************************************************************************/.ant-divider{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";border-top:1px solid rgba(255,255,255,.12)}.ant-divider-vertical{position:relative;top:-.06em;display:inline-block;height:.9em;margin:0 8px;vertical-align:middle;border-top:0;border-left:1px solid rgba(255,255,255,.12)}.ant-divider-horizontal{display:flex;clear:both;width:100%;min-width:100%;margin:24px 0}.ant-divider-horizontal.ant-divider-with-text{display:flex;margin:16px 0;color:#ffffffd9;font-weight:500;font-size:16px;white-space:nowrap;text-align:center;border-top:0;border-top-color:#ffffff1f}.ant-divider-horizontal.ant-divider-with-text:before,.ant-divider-horizontal.ant-divider-with-text:after{position:relative;top:50%;width:50%;border-top:1px solid transparent;border-top-color:inherit;border-bottom:0;transform:translateY(50%);content:""}.ant-divider-horizontal.ant-divider-with-text-left:before{top:50%;width:5%}.ant-divider-horizontal.ant-divider-with-text-left:after{top:50%;width:95%}.ant-divider-horizontal.ant-divider-with-text-right:before{top:50%;width:95%}.ant-divider-horizontal.ant-divider-with-text-right:after{top:50%;width:5%}.ant-divider-inner-text{display:inline-block;padding:0 1em}.ant-divider-dashed{background:none;border-color:#ffffff1f;border-style:dashed;border-width:1px 0 0}.ant-divider-horizontal.ant-divider-with-text.ant-divider-dashed:before,.ant-divider-horizontal.ant-divider-with-text.ant-divider-dashed:after{border-style:dashed none none}.ant-divider-vertical.ant-divider-dashed{border-width:0 0 0 1px}.ant-divider-plain.ant-divider-with-text{color:#ffffffd9;font-weight:400;font-size:14px}.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-no-default-orientation-margin-left:before{width:0}.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-no-default-orientation-margin-left:after{width:100%}.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-no-default-orientation-margin-left .ant-divider-inner-text{padding-left:0}.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-no-default-orientation-margin-right:before{width:100%}.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-no-default-orientation-margin-right:after{width:0}.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-no-default-orientation-margin-right .ant-divider-inner-text{padding-right:0}.ant-divider-rtl{direction:rtl}.ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-left:before{width:95%}.ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-left:after{width:5%}.ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-right:before{width:5%}.ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-right:after{width:95%}/*!***********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/drawer/style/index.less ***! + \\***********************************************************************************************************************************************************************************************************************************************************/.ant-drawer{position:fixed;z-index:1000;width:0%;height:100%;transition:width 0s ease .3s,height 0s ease .3s}.ant-drawer-content-wrapper{position:absolute;width:100%;height:100%;transition:transform .3s cubic-bezier(.23,1,.32,1),box-shadow .3s cubic-bezier(.23,1,.32,1)}.ant-drawer .ant-drawer-content{width:100%;height:100%}.ant-drawer-left,.ant-drawer-right{top:0;width:0%;height:100%}.ant-drawer-left .ant-drawer-content-wrapper,.ant-drawer-right .ant-drawer-content-wrapper{height:100%}.ant-drawer-left.ant-drawer-open,.ant-drawer-right.ant-drawer-open{width:100%;transition:transform .3s cubic-bezier(.23,1,.32,1)}.ant-drawer-left,.ant-drawer-left .ant-drawer-content-wrapper{left:0}.ant-drawer-left.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:6px 0 16px -8px #00000052,9px 0 28px #0003,12px 0 48px 16px #0000001f}.ant-drawer-right,.ant-drawer-right .ant-drawer-content-wrapper{right:0}.ant-drawer-right.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:-6px 0 16px -8px #00000014,-9px 0 28px #0000000d,-12px 0 48px 16px #00000008}.ant-drawer-right.ant-drawer-open.no-mask{right:1px;transform:translate(1px)}.ant-drawer-top,.ant-drawer-bottom{left:0;width:100%;height:0%}.ant-drawer-top .ant-drawer-content-wrapper,.ant-drawer-bottom .ant-drawer-content-wrapper{width:100%}.ant-drawer-top.ant-drawer-open,.ant-drawer-bottom.ant-drawer-open{height:100%;transition:transform .3s cubic-bezier(.23,1,.32,1)}.ant-drawer-top{top:0}.ant-drawer-top.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:0 6px 16px -8px #00000052,0 9px 28px #0003,0 12px 48px 16px #0000001f}.ant-drawer-bottom,.ant-drawer-bottom .ant-drawer-content-wrapper{bottom:0}.ant-drawer-bottom.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:0 -6px 16px -8px #00000052,0 -9px 28px #0003,0 -12px 48px 16px #0000001f}.ant-drawer-bottom.ant-drawer-open.no-mask{bottom:1px;transform:translateY(1px)}.ant-drawer.ant-drawer-open .ant-drawer-mask{height:100%;opacity:1;transition:none;animation:antdDrawerFadeIn .3s cubic-bezier(.23,1,.32,1);pointer-events:auto}.ant-drawer-title{flex:1;margin:0;color:#ffffffd9;font-weight:500;font-size:16px;line-height:22px}.ant-drawer-content{position:relative;z-index:1;overflow:auto;background-color:#1f1f1f;background-clip:padding-box;border:0}.ant-drawer-close{display:inline-block;margin-right:12px;color:#ffffff73;font-weight:700;font-size:16px;font-style:normal;line-height:1;text-align:center;text-transform:none;text-decoration:none;background:transparent;border:0;outline:0;cursor:pointer;transition:color .3s;text-rendering:auto}.ant-drawer-close:focus,.ant-drawer-close:hover{color:#ffffffbf;text-decoration:none}.ant-drawer-header{position:relative;display:flex;align-items:center;justify-content:space-between;padding:16px 24px;color:#ffffffd9;background:#1f1f1f;border-bottom:1px solid #303030;border-radius:2px 2px 0 0}.ant-drawer-header-title{display:flex;flex:1;align-items:center;justify-content:space-between}.ant-drawer-header-close-only{padding-bottom:0;border:none}.ant-drawer-wrapper-body{display:flex;flex-flow:column nowrap;width:100%;height:100%}.ant-drawer-body{flex-grow:1;padding:24px;overflow:auto;font-size:14px;line-height:1.5715;word-wrap:break-word}.ant-drawer-footer{flex-shrink:0;padding:10px 16px;border-top:1px solid #303030}.ant-drawer-mask{position:absolute;top:0;left:0;width:100%;height:0;background-color:#00000073;opacity:0;transition:opacity .3s linear,height 0s ease .3s;pointer-events:none}.ant-drawer .ant-picker-clear{background:#1f1f1f}@keyframes antdDrawerFadeIn{0%{opacity:0}to{opacity:1}}.ant-drawer-rtl{direction:rtl}.ant-drawer-rtl .ant-drawer-close{margin-right:0;margin-left:12px}.ant-drawer .ant-picker-clear,.ant-drawer .ant-slider-handle,.ant-drawer .ant-anchor-wrapper,.ant-drawer .ant-collapse-content,.ant-drawer .ant-timeline-item-head,.ant-drawer .ant-card{background-color:#1f1f1f}.ant-drawer .ant-transfer-list-header{background:#1f1f1f;border-bottom:1px solid #3a3a3a}.ant-drawer .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover{background-color:#ffffff14}.ant-drawer tr.ant-table-expanded-row>td,.ant-drawer tr.ant-table-expanded-row:hover>td{background:#272727}.ant-drawer .ant-table.ant-table-small thead>tr>th{background-color:#1f1f1f;border-bottom:1px solid #3a3a3a}.ant-drawer .ant-table{background-color:#1f1f1f}.ant-drawer .ant-table .ant-table-row-expand-icon{border:1px solid #3a3a3a}.ant-drawer .ant-table tfoot>tr>th,.ant-drawer .ant-table tfoot>tr>td{border-bottom:1px solid #3a3a3a}.ant-drawer .ant-table thead>tr>th{background-color:#272727;border-bottom:1px solid #3a3a3a}.ant-drawer .ant-table tbody>tr>td{border-bottom:1px solid #3a3a3a}.ant-drawer .ant-table tbody>tr>td.ant-table-cell-fix-left,.ant-drawer .ant-table tbody>tr>td.ant-table-cell-fix-right{background-color:#1f1f1f}.ant-drawer .ant-table tbody>tr.ant-table-row:hover>td{background:#303030}.ant-drawer .ant-table.ant-table-bordered .ant-table-title{border:1px solid #3a3a3a}.ant-drawer .ant-table.ant-table-bordered thead>tr>th,.ant-drawer .ant-table.ant-table-bordered tbody>tr>td,.ant-drawer .ant-table.ant-table-bordered tfoot>tr>th,.ant-drawer .ant-table.ant-table-bordered tfoot>tr>td{border-right:1px solid #3a3a3a}.ant-drawer .ant-table.ant-table-bordered .ant-table-cell-fix-right-first:after{border-right:1px solid #3a3a3a}.ant-drawer .ant-table.ant-table-bordered table thead>tr:not(:last-child)>th{border-bottom:1px solid #303030}.ant-drawer .ant-table.ant-table-bordered .ant-table-container{border:1px solid #3a3a3a}.ant-drawer .ant-table.ant-table-bordered .ant-table-expanded-row-fixed:after{border-right:1px solid #3a3a3a}.ant-drawer .ant-table.ant-table-bordered .ant-table-footer{border:1px solid #3a3a3a}.ant-drawer .ant-table .ant-table-filter-trigger-container-open{background-color:#525252}.ant-drawer .ant-picker-calendar-full,.ant-drawer .ant-picker-calendar-full .ant-picker-panel{background-color:#1f1f1f}.ant-drawer .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date{border-top:2px solid #3a3a3a}.ant-drawer .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active{background-color:#1f1f1f;border-bottom:1px solid #1f1f1f}.ant-drawer .ant-badge-count{box-shadow:0 0 0 1px #1f1f1f}.ant-drawer .ant-tree-show-line .ant-tree-switcher{background:#1f1f1f}/*!*********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/form/style/index.less ***! + \\*********************************************************************************************************************************************************************************************************************************************************/.ant-form-item .ant-upload{background:transparent}.ant-form-item .ant-upload.ant-upload-drag{background:rgba(255,255,255,.04)}.ant-form-item input[type=radio],.ant-form-item input[type=checkbox]{width:14px;height:14px}.ant-form-item .ant-radio-inline,.ant-form-item .ant-checkbox-inline{display:inline-block;margin-left:8px;font-weight:400;vertical-align:middle;cursor:pointer}.ant-form-item .ant-radio-inline:first-child,.ant-form-item .ant-checkbox-inline:first-child{margin-left:0}.ant-form-item .ant-checkbox-vertical,.ant-form-item .ant-radio-vertical{display:block}.ant-form-item .ant-checkbox-vertical+.ant-checkbox-vertical,.ant-form-item .ant-radio-vertical+.ant-radio-vertical{margin-left:0}.ant-form-item .ant-input-number+.ant-form-text{margin-left:8px}.ant-form-item .ant-input-number-handler-wrap{z-index:2}.ant-form-item .ant-select,.ant-form-item .ant-cascader-picker{width:100%}.ant-form-item .ant-picker-calendar-year-select,.ant-form-item .ant-picker-calendar-month-select,.ant-form-item .ant-input-group .ant-select,.ant-form-item .ant-input-group .ant-cascader-picker,.ant-form-item .ant-input-number-group .ant-select,.ant-form-item .ant-input-number-group .ant-cascader-picker{width:auto}.ant-form-inline{display:flex;flex-wrap:wrap}.ant-form-inline .ant-form-item{flex:none;flex-wrap:nowrap;margin-right:16px;margin-bottom:0}.ant-form-inline .ant-form-item-with-help{margin-bottom:24px}.ant-form-inline .ant-form-item>.ant-form-item-label,.ant-form-inline .ant-form-item>.ant-form-item-control{display:inline-block;vertical-align:top}.ant-form-inline .ant-form-item>.ant-form-item-label{flex:none}.ant-form-inline .ant-form-item .ant-form-text,.ant-form-inline .ant-form-item .ant-form-item-has-feedback{display:inline-block}.ant-form-horizontal .ant-form-item-label{flex-grow:0}.ant-form-horizontal .ant-form-item-control{flex:1 1 0;min-width:0}.ant-form-horizontal .ant-form-item-label.ant-col-24+.ant-form-item-control{min-width:unset}.ant-form-vertical .ant-form-item{flex-direction:column}.ant-form-vertical .ant-form-item-label>label{height:auto}.ant-form-vertical .ant-form-item-label,.ant-col-24.ant-form-item-label,.ant-col-xl-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-form-vertical .ant-form-item-label>label,.ant-col-24.ant-form-item-label>label,.ant-col-xl-24.ant-form-item-label>label{margin:0}.ant-form-vertical .ant-form-item-label>label:after,.ant-col-24.ant-form-item-label>label:after,.ant-col-xl-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-form-vertical .ant-form-item-label,.ant-form-rtl.ant-col-24.ant-form-item-label,.ant-form-rtl.ant-col-xl-24.ant-form-item-label{text-align:right}@media (max-width: 575px){.ant-form-item .ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-form-item .ant-form-item-label>label{margin:0}.ant-form-item .ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-form-item .ant-form-item-label{text-align:right}.ant-form .ant-form-item{flex-wrap:wrap}.ant-form .ant-form-item .ant-form-item-label,.ant-form .ant-form-item .ant-form-item-control{flex:0 0 100%;max-width:100%}.ant-col-xs-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-col-xs-24.ant-form-item-label>label{margin:0}.ant-col-xs-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-col-xs-24.ant-form-item-label{text-align:right}}@media (max-width: 767px){.ant-col-sm-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-col-sm-24.ant-form-item-label>label{margin:0}.ant-col-sm-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-col-sm-24.ant-form-item-label{text-align:right}}@media (max-width: 991px){.ant-col-md-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-col-md-24.ant-form-item-label>label{margin:0}.ant-col-md-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-col-md-24.ant-form-item-label{text-align:right}}@media (max-width: 1199px){.ant-col-lg-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-col-lg-24.ant-form-item-label>label{margin:0}.ant-col-lg-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-col-lg-24.ant-form-item-label{text-align:right}}@media (max-width: 1599px){.ant-col-xl-24.ant-form-item-label{padding:0 0 8px;line-height:1.5715;white-space:initial;text-align:left}.ant-col-xl-24.ant-form-item-label>label{margin:0}.ant-col-xl-24.ant-form-item-label>label:after{display:none}.ant-form-rtl.ant-col-xl-24.ant-form-item-label{text-align:right}}.ant-form-item-explain-error{color:#a61d24}.ant-form-item-explain-warning{color:#d89614}.ant-form-item-has-feedback .ant-input{padding-right:24px}.ant-form-item-has-feedback .ant-input-affix-wrapper .ant-input-suffix{padding-right:18px}.ant-form-item-has-feedback .ant-input-search:not(.ant-input-search-enter-button) .ant-input-suffix{right:28px}.ant-form-item-has-feedback .ant-switch{margin:2px 0 4px}.ant-form-item-has-feedback>.ant-select .ant-select-arrow,.ant-form-item-has-feedback>.ant-select .ant-select-clear,.ant-form-item-has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-arrow,.ant-form-item-has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-clear,.ant-form-item-has-feedback :not(.ant-input-number-group-addon)>.ant-select .ant-select-arrow,.ant-form-item-has-feedback :not(.ant-input-number-group-addon)>.ant-select .ant-select-clear{right:32px}.ant-form-item-has-feedback>.ant-select .ant-select-selection-selected-value,.ant-form-item-has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-selection-selected-value,.ant-form-item-has-feedback :not(.ant-input-number-group-addon)>.ant-select .ant-select-selection-selected-value{padding-right:42px}.ant-form-item-has-feedback .ant-cascader-picker-arrow{margin-right:19px}.ant-form-item-has-feedback .ant-cascader-picker-clear{right:32px}.ant-form-item-has-feedback .ant-picker,.ant-form-item-has-feedback .ant-picker-large{padding-right:29.2px}.ant-form-item-has-feedback .ant-picker-small{padding-right:25.2px}.ant-form-item-has-feedback.ant-form-item-has-success .ant-form-item-children-icon,.ant-form-item-has-feedback.ant-form-item-has-warning .ant-form-item-children-icon,.ant-form-item-has-feedback.ant-form-item-has-error .ant-form-item-children-icon,.ant-form-item-has-feedback.ant-form-item-is-validating .ant-form-item-children-icon{position:absolute;top:50%;right:0;z-index:1;width:32px;height:20px;margin-top:-10px;font-size:14px;line-height:20px;text-align:center;visibility:visible;animation:zoomIn .3s cubic-bezier(.12,.4,.29,1.46);pointer-events:none}.ant-form-item-has-success.ant-form-item-has-feedback .ant-form-item-children-icon{color:#49aa19;animation-name:diffZoomIn1!important}.ant-form-item-has-warning .ant-form-item-split{color:#d89614}.ant-form-item-has-warning :not(.ant-input-disabled):not(.ant-input-borderless).ant-input,.ant-form-item-has-warning :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper,.ant-form-item-has-warning :not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper,.ant-form-item-has-warning :not(.ant-input-disabled):not(.ant-input-borderless).ant-input:hover,.ant-form-item-has-warning :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:hover,.ant-form-item-has-warning :not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:hover{background-color:transparent;border-color:#d89614}.ant-form-item-has-warning :not(.ant-input-disabled):not(.ant-input-borderless).ant-input:focus,.ant-form-item-has-warning :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:focus,.ant-form-item-has-warning :not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:focus,.ant-form-item-has-warning :not(.ant-input-disabled):not(.ant-input-borderless).ant-input-focused,.ant-form-item-has-warning :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper-focused,.ant-form-item-has-warning :not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper-focused{border-color:#d89614;box-shadow:0 0 0 2px #d8961433;border-right-width:1px!important;outline:0}.ant-form-item-has-warning .ant-calendar-picker-open .ant-calendar-picker-input{border-color:#d89614;box-shadow:0 0 0 2px #d8961433;border-right-width:1px!important;outline:0}.ant-form-item-has-warning .ant-input-prefix,.ant-form-item-has-warning .ant-input-number-prefix{color:#d89614}.ant-form-item-has-warning :not(.ant-input-group-addon-disabled).ant-input-group-addon,.ant-form-item-has-warning :not(.ant-input-number-group-addon-disabled).ant-input-number-group-addon{color:#d89614;border-color:#d89614}.ant-form-item-has-warning .has-feedback{color:#d89614}.ant-form-item-has-warning.ant-form-item-has-feedback .ant-form-item-children-icon{color:#d89614;animation-name:diffZoomIn3!important}.ant-form-item-has-warning .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input) .ant-select-selector{background-color:transparent;border-color:#d89614!important}.ant-form-item-has-warning .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-open .ant-select-selector,.ant-form-item-has-warning .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-focused .ant-select-selector{border-color:#d89614;box-shadow:0 0 0 2px #d8961433;border-right-width:1px!important;outline:0}.ant-form-item-has-warning .ant-input-number,.ant-form-item-has-warning .ant-picker{background-color:transparent;border-color:#d89614}.ant-form-item-has-warning .ant-input-number-focused,.ant-form-item-has-warning .ant-picker-focused,.ant-form-item-has-warning .ant-input-number:focus,.ant-form-item-has-warning .ant-picker:focus{border-color:#d89614;box-shadow:0 0 0 2px #d8961433;border-right-width:1px!important;outline:0}.ant-form-item-has-warning .ant-input-number:not([disabled]):hover,.ant-form-item-has-warning .ant-picker:not([disabled]):hover{background-color:transparent;border-color:#d89614}.ant-form-item-has-warning .ant-cascader-picker:focus .ant-cascader-input{border-color:#d89614;box-shadow:0 0 0 2px #d8961433;border-right-width:1px!important;outline:0}.ant-form-item-has-error .ant-form-item-split{color:#a61d24}.ant-form-item-has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input,.ant-form-item-has-error :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper,.ant-form-item-has-error :not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper,.ant-form-item-has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input:hover,.ant-form-item-has-error :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:hover,.ant-form-item-has-error :not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:hover{background-color:transparent;border-color:#a61d24}.ant-form-item-has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input:focus,.ant-form-item-has-error :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:focus,.ant-form-item-has-error :not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:focus,.ant-form-item-has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input-focused,.ant-form-item-has-error :not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper-focused,.ant-form-item-has-error :not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper-focused{border-color:#a61d24;box-shadow:0 0 0 2px #a61d2433;border-right-width:1px!important;outline:0}.ant-form-item-has-error .ant-calendar-picker-open .ant-calendar-picker-input{border-color:#a61d24;box-shadow:0 0 0 2px #a61d2433;border-right-width:1px!important;outline:0}.ant-form-item-has-error .ant-input-prefix,.ant-form-item-has-error .ant-input-number-prefix{color:#a61d24}.ant-form-item-has-error :not(.ant-input-group-addon-disabled).ant-input-group-addon,.ant-form-item-has-error :not(.ant-input-number-group-addon-disabled).ant-input-number-group-addon{color:#a61d24;border-color:#a61d24}.ant-form-item-has-error .has-feedback{color:#a61d24}.ant-form-item-has-error.ant-form-item-has-feedback .ant-form-item-children-icon{color:#a61d24;animation-name:diffZoomIn2!important}.ant-form-item-has-error .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input) .ant-select-selector{background-color:transparent;border-color:#a61d24!important}.ant-form-item-has-error .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-open .ant-select-selector,.ant-form-item-has-error .ant-select:not(.ant-select-disabled):not(.ant-select-customize-input).ant-select-focused .ant-select-selector{border-color:#a61d24;box-shadow:0 0 0 2px #a61d2433;border-right-width:1px!important;outline:0}.ant-form-item-has-error .ant-input-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector,.ant-form-item-has-error .ant-input-number-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector{background-color:inherit;border:0;box-shadow:none}.ant-form-item-has-error .ant-select.ant-select-auto-complete .ant-input:focus{border-color:#a61d24}.ant-form-item-has-error .ant-input-number,.ant-form-item-has-error .ant-picker{background-color:transparent;border-color:#a61d24}.ant-form-item-has-error .ant-input-number-focused,.ant-form-item-has-error .ant-picker-focused,.ant-form-item-has-error .ant-input-number:focus,.ant-form-item-has-error .ant-picker:focus{border-color:#a61d24;box-shadow:0 0 0 2px #a61d2433;border-right-width:1px!important;outline:0}.ant-form-item-has-error .ant-input-number:not([disabled]):hover,.ant-form-item-has-error .ant-picker:not([disabled]):hover{background-color:transparent;border-color:#a61d24}.ant-form-item-has-error .ant-mention-wrapper .ant-mention-editor,.ant-form-item-has-error .ant-mention-wrapper .ant-mention-editor:not([disabled]):hover{background-color:transparent;border-color:#a61d24}.ant-form-item-has-error .ant-mention-wrapper.ant-mention-active:not([disabled]) .ant-mention-editor,.ant-form-item-has-error .ant-mention-wrapper .ant-mention-editor:not([disabled]):focus{border-color:#a61d24;box-shadow:0 0 0 2px #a61d2433;border-right-width:1px!important;outline:0}.ant-form-item-has-error .ant-cascader-picker:hover .ant-cascader-picker-label:hover+.ant-cascader-input.ant-input{border-color:#a61d24}.ant-form-item-has-error .ant-cascader-picker:focus .ant-cascader-input{background-color:transparent;border-color:#a61d24;box-shadow:0 0 0 2px #a61d2433;border-right-width:1px!important;outline:0}.ant-form-item-has-error .ant-transfer-list{border-color:#a61d24}.ant-form-item-has-error .ant-transfer-list-search:not([disabled]){border-color:#434343}.ant-form-item-has-error .ant-transfer-list-search:not([disabled]):hover{border-color:#165996;border-right-width:1px!important}.ant-form-item-has-error .ant-transfer-list-search:not([disabled]):focus{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-form-item-has-error .ant-radio-button-wrapper{border-color:#a61d24!important}.ant-form-item-has-error .ant-radio-button-wrapper:not(:first-child):before{background-color:#a61d24}.ant-form-item-has-error .ant-mentions{border-color:#a61d24!important}.ant-form-item-has-error .ant-mentions-focused,.ant-form-item-has-error .ant-mentions:focus{border-color:#a61d24;box-shadow:0 0 0 2px #a61d2433;border-right-width:1px!important;outline:0}.ant-form-item-is-validating.ant-form-item-has-feedback .ant-form-item-children-icon{display:inline-block;color:#177ddc}.ant-form{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum"}.ant-form legend{display:block;width:100%;margin-bottom:20px;padding:0;color:#ffffff73;font-size:16px;line-height:inherit;border:0;border-bottom:1px solid #434343}.ant-form label{font-size:14px}.ant-form input[type=search]{box-sizing:border-box}.ant-form input[type=radio],.ant-form input[type=checkbox]{line-height:normal}.ant-form input[type=file]{display:block}.ant-form input[type=range]{display:block;width:100%}.ant-form select[multiple],.ant-form select[size]{height:auto}.ant-form input[type=file]:focus,.ant-form input[type=radio]:focus,.ant-form input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.ant-form output{display:block;padding-top:15px;color:#ffffffd9;font-size:14px;line-height:1.5715}.ant-form .ant-form-text{display:inline-block;padding-right:8px}.ant-form-small .ant-form-item-label>label{height:24px}.ant-form-small .ant-form-item-control-input{min-height:24px}.ant-form-large .ant-form-item-label>label{height:40px}.ant-form-large .ant-form-item-control-input{min-height:40px}.ant-form-item{box-sizing:border-box;margin:0 0 24px;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";vertical-align:top}.ant-form-item-with-help{margin-bottom:0;transition:none}.ant-form-item-hidden,.ant-form-item-hidden.ant-row{display:none}.ant-form-item-label{display:inline-block;flex-grow:0;overflow:hidden;white-space:nowrap;text-align:right;vertical-align:middle}.ant-form-item-label-left{text-align:left}.ant-form-item-label-wrap{overflow:unset;line-height:1.3215em;white-space:unset}.ant-form-item-label>label{position:relative;display:inline-flex;align-items:center;max-width:100%;height:32px;color:#ffffffd9;font-size:14px}.ant-form-item-label>label>.anticon{font-size:14px;vertical-align:top}.ant-form-item-label>label.ant-form-item-required:not(.ant-form-item-required-mark-optional):before{display:inline-block;margin-right:4px;color:#a61d24;font-size:14px;font-family:SimSun,sans-serif;line-height:1;content:"*"}.ant-form-hide-required-mark .ant-form-item-label>label.ant-form-item-required:not(.ant-form-item-required-mark-optional):before{display:none}.ant-form-item-label>label .ant-form-item-optional{display:inline-block;margin-left:4px;color:#ffffff73}.ant-form-hide-required-mark .ant-form-item-label>label .ant-form-item-optional{display:none}.ant-form-item-label>label .ant-form-item-tooltip{color:#ffffff73;cursor:help;writing-mode:horizontal-tb;-webkit-margin-start:4px;margin-inline-start:4px}.ant-form-item-label>label:after{content:":";position:relative;top:-.5px;margin:0 8px 0 2px}.ant-form-item-label>label.ant-form-item-no-colon:after{content:" "}.ant-form-item-control{display:flex;flex-direction:column;flex-grow:1}.ant-form-item-control:first-child:not([class^="ant-col-"]):not([class*=" ant-col-"]){width:100%}.ant-form-item-control-input{position:relative;display:flex;align-items:center;min-height:32px}.ant-form-item-control-input-content{flex:auto;max-width:100%}.ant-form-item-explain,.ant-form-item-extra{clear:both;color:#ffffff73;font-size:14px;line-height:1.5715;transition:color .3s cubic-bezier(.215,.61,.355,1)}.ant-form-item-explain-connected{height:0;min-height:0;opacity:0}.ant-form-item-extra{min-height:24px}.ant-form-item .ant-input-textarea-show-count:after{margin-bottom:-22px}.ant-form-item-with-help .ant-form-item-explain{height:auto;min-height:24px;opacity:1}.ant-show-help{transition:height .3s linear,min-height .3s linear,margin-bottom .3s cubic-bezier(.645,.045,.355,1),opacity .3s cubic-bezier(.645,.045,.355,1)}.ant-show-help-leave{min-height:24px}.ant-show-help-leave-active{min-height:0}.ant-show-help-item{overflow:hidden;transition:height .3s cubic-bezier(.645,.045,.355,1),opacity .3s cubic-bezier(.645,.045,.355,1),transform .3s cubic-bezier(.645,.045,.355,1)!important}.ant-show-help-item-appear,.ant-show-help-item-enter{transform:translateY(-5px);opacity:0}.ant-show-help-item-appear-active,.ant-show-help-item-enter-active{transform:translateY(0);opacity:1}.ant-show-help-item-leave-active{transform:translateY(-5px)}@keyframes diffZoomIn1{0%{transform:scale(0);opacity:0}to{transform:scale(1);opacity:1}}@keyframes diffZoomIn2{0%{transform:scale(0);opacity:0}to{transform:scale(1);opacity:1}}@keyframes diffZoomIn3{0%{transform:scale(0);opacity:0}to{transform:scale(1);opacity:1}}.ant-form-rtl{direction:rtl}.ant-form-rtl .ant-form-item-label{text-align:left}.ant-form-rtl .ant-form-item-label>label.ant-form-item-required:before{margin-right:0;margin-left:4px}.ant-form-rtl .ant-form-item-label>label:after{margin:0 2px 0 8px}.ant-form-rtl .ant-form-item-label>label .ant-form-item-optional{margin-right:4px;margin-left:0}.ant-col-rtl .ant-form-item-control:first-child{width:100%}.ant-form-rtl .ant-form-item-has-feedback .ant-input{padding-right:11px;padding-left:24px}.ant-form-rtl .ant-form-item-has-feedback .ant-input-affix-wrapper .ant-input-suffix{padding-right:11px;padding-left:18px}.ant-form-rtl .ant-form-item-has-feedback .ant-input-affix-wrapper .ant-input,.ant-form-rtl .ant-form-item-has-feedback .ant-input-number-affix-wrapper .ant-input-number{padding:0}.ant-form-rtl .ant-form-item-has-feedback .ant-input-search:not(.ant-input-search-enter-button) .ant-input-suffix{right:auto;left:28px}.ant-form-rtl .ant-form-item-has-feedback .ant-input-number{padding-left:18px}.ant-form-rtl .ant-form-item-has-feedback>.ant-select .ant-select-arrow,.ant-form-rtl .ant-form-item-has-feedback>.ant-select .ant-select-clear,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-arrow,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-clear,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-number-group-addon)>.ant-select .ant-select-arrow,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-number-group-addon)>.ant-select .ant-select-clear{right:auto;left:32px}.ant-form-rtl .ant-form-item-has-feedback>.ant-select .ant-select-selection-selected-value,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-selection-selected-value,.ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-number-group-addon)>.ant-select .ant-select-selection-selected-value{padding-right:0;padding-left:42px}.ant-form-rtl .ant-form-item-has-feedback .ant-cascader-picker-arrow{margin-right:0;margin-left:19px}.ant-form-rtl .ant-form-item-has-feedback .ant-cascader-picker-clear{right:auto;left:32px}.ant-form-rtl .ant-form-item-has-feedback .ant-picker,.ant-form-rtl .ant-form-item-has-feedback .ant-picker-large{padding-right:11px;padding-left:29.2px}.ant-form-rtl .ant-form-item-has-feedback .ant-picker-small{padding-right:7px;padding-left:25.2px}.ant-form-rtl .ant-form-item-has-feedback.ant-form-item-has-success .ant-form-item-children-icon,.ant-form-rtl .ant-form-item-has-feedback.ant-form-item-has-warning .ant-form-item-children-icon,.ant-form-rtl .ant-form-item-has-feedback.ant-form-item-has-error .ant-form-item-children-icon,.ant-form-rtl .ant-form-item-has-feedback.ant-form-item-is-validating .ant-form-item-children-icon{right:auto;left:0}.ant-form-rtl.ant-form-inline .ant-form-item{margin-right:0;margin-left:16px}/*!*********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/icon/style/index.less ***! + \\*********************************************************************************************************************************************************************************************************************************************************//*!**********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/image/style/index.less ***! + \\**********************************************************************************************************************************************************************************************************************************************************/.ant-image{position:relative;display:inline-block}.ant-image-img{width:100%;height:auto;vertical-align:middle}.ant-image-img-placeholder{background-color:#f5f5f5;background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTQuNSAyLjVoLTEzQS41LjUgMCAwIDAgMSAzdjEwYS41LjUgMCAwIDAgLjUuNWgxM2EuNS41IDAgMCAwIC41LS41VjNhLjUuNSAwIDAgMC0uNS0uNXpNNS4yODEgNC43NWExIDEgMCAwIDEgMCAyIDEgMSAwIDAgMSAwLTJ6bTguMDMgNi44M2EuMTI3LjEyNyAwIDAgMS0uMDgxLjAzSDIuNzY5YS4xMjUuMTI1IDAgMCAxLS4wOTYtLjIwN2wyLjY2MS0zLjE1NmEuMTI2LjEyNiAwIDAgMSAuMTc3LS4wMTZsLjAxNi4wMTZMNy4wOCAxMC4wOWwyLjQ3LTIuOTNhLjEyNi4xMjYgMCAwIDEgLjE3Ny0uMDE2bC4wMTUuMDE2IDMuNTg4IDQuMjQ0YS4xMjcuMTI3IDAgMCAxLS4wMi4xNzV6IiBmaWxsPSIjOEM4QzhDIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48L3N2Zz4=);background-repeat:no-repeat;background-position:center center;background-size:30%}.ant-image-mask{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;color:#fff;background:rgba(0,0,0,.5);cursor:pointer;opacity:0;transition:opacity .3s}.ant-image-mask-info{padding:0 4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-image-mask-info .anticon{-webkit-margin-end:4px;margin-inline-end:4px}.ant-image-mask:hover{opacity:1}.ant-image-placeholder{position:absolute;top:0;right:0;bottom:0;left:0}.ant-image-preview{pointer-events:none;height:100%;text-align:center}.ant-image-preview.ant-zoom-enter,.ant-image-preview.antzoom-appear{transform:none;opacity:0;animation-duration:.3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-image-preview-mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;height:100%;background-color:#00000073}.ant-image-preview-mask-hidden{display:none}.ant-image-preview-wrap{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;outline:0;-webkit-overflow-scrolling:touch}.ant-image-preview-body{position:absolute;top:0;right:0;bottom:0;left:0;overflow:hidden}.ant-image-preview-img{max-width:100%;max-height:100%;vertical-align:middle;transform:scaleZ(1);cursor:grab;transition:transform .3s cubic-bezier(.215,.61,.355,1) 0s;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:auto}.ant-image-preview-img-wrapper{position:absolute;top:0;right:0;bottom:0;left:0;transition:transform .3s cubic-bezier(.215,.61,.355,1) 0s}.ant-image-preview-img-wrapper:before{display:inline-block;width:1px;height:50%;margin-right:-1px;content:""}.ant-image-preview-moving .ant-image-preview-img{cursor:grabbing}.ant-image-preview-moving .ant-image-preview-img-wrapper{transition-duration:0s}.ant-image-preview-wrap{z-index:1080}.ant-image-preview-operations{box-sizing:border-box;margin:0;padding:0;font-size:14px;font-variant:tabular-nums;line-height:1.5715;font-feature-settings:"tnum";position:absolute;top:0;right:0;z-index:1;display:flex;flex-direction:row-reverse;align-items:center;width:100%;color:#ffffffd9;list-style:none;background:rgba(0,0,0,.1);pointer-events:auto}.ant-image-preview-operations-operation{margin-left:12px;padding:12px;cursor:pointer}.ant-image-preview-operations-operation-disabled{color:#ffffff40;pointer-events:none}.ant-image-preview-operations-operation:last-of-type{margin-left:0}.ant-image-preview-operations-icon{font-size:18px}.ant-image-preview-switch-left,.ant-image-preview-switch-right{position:absolute;top:50%;right:10px;z-index:1;display:flex;align-items:center;justify-content:center;width:44px;height:44px;margin-top:-22px;color:#ffffffd9;background:rgba(0,0,0,.1);border-radius:50%;cursor:pointer;pointer-events:auto}.ant-image-preview-switch-left-disabled,.ant-image-preview-switch-right-disabled{color:#ffffff40;cursor:not-allowed}.ant-image-preview-switch-left-disabled>.anticon,.ant-image-preview-switch-right-disabled>.anticon{cursor:not-allowed}.ant-image-preview-switch-left>.anticon,.ant-image-preview-switch-right>.anticon{font-size:18px}.ant-image-preview-switch-left{left:10px}.ant-image-preview-switch-right{right:10px}/*!*****************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/input-number/style/index.less ***! + \\*****************************************************************************************************************************************************************************************************************************************************************/.ant-input-number-affix-wrapper{position:relative;display:inline-block;width:100%;min-width:0;color:#ffffffd9;font-size:14px;line-height:1.5715;background-color:transparent;background-image:none;border:1px solid #434343;border-radius:2px;transition:all .3s;position:static;display:inline-flex;width:90px;padding:0;-webkit-padding-start:11px;padding-inline-start:11px}.ant-input-number-affix-wrapper::-moz-placeholder{opacity:1}.ant-input-number-affix-wrapper::placeholder{color:#ffffff4d;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-input-number-affix-wrapper:-moz-placeholder-shown{text-overflow:ellipsis}.ant-input-number-affix-wrapper:placeholder-shown{text-overflow:ellipsis}.ant-input-number-affix-wrapper:hover{border-color:#165996;border-right-width:1px!important}.ant-input-number-affix-wrapper:focus,.ant-input-number-affix-wrapper-focused{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-input-number-affix-wrapper-disabled{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-affix-wrapper-disabled:hover{border-color:#434343;border-right-width:1px!important}.ant-input-number-affix-wrapper[disabled]{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-affix-wrapper[disabled]:hover{border-color:#434343;border-right-width:1px!important}.ant-input-number-affix-wrapper-borderless,.ant-input-number-affix-wrapper-borderless:hover,.ant-input-number-affix-wrapper-borderless:focus,.ant-input-number-affix-wrapper-borderless-focused,.ant-input-number-affix-wrapper-borderless-disabled,.ant-input-number-affix-wrapper-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input-number-affix-wrapper{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-number-affix-wrapper-lg{padding:6.5px 11px;font-size:16px}.ant-input-number-affix-wrapper-sm{padding:0 7px}.ant-input-number-affix-wrapper:not(.ant-input-number-affix-wrapper-disabled):hover{border-color:#165996;border-right-width:1px!important;z-index:1}.ant-input-number-affix-wrapper-focused,.ant-input-number-affix-wrapper:focus{z-index:1}.ant-input-number-affix-wrapper-disabled .ant-input-number[disabled]{background:transparent}.ant-input-number-affix-wrapper>div.ant-input-number{width:100%;border:none;outline:none}.ant-input-number-affix-wrapper>div.ant-input-number.ant-input-number-focused{box-shadow:none!important}.ant-input-number-affix-wrapper input.ant-input-number-input{padding:0}.ant-input-number-affix-wrapper:before{width:0;visibility:hidden;content:" "}.ant-input-number-prefix{display:flex;flex:none;align-items:center;-webkit-margin-end:4px;margin-inline-end:4px}.ant-input-number-group-wrapper .ant-input-number-affix-wrapper{width:100%}.ant-input-number{box-sizing:border-box;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";position:relative;width:100%;min-width:0;color:#ffffffd9;font-size:14px;line-height:1.5715;background-color:transparent;background-image:none;transition:all .3s;display:inline-block;width:90px;margin:0;padding:0;border:1px solid #434343;border-radius:2px}.ant-input-number::-moz-placeholder{opacity:1}.ant-input-number::placeholder{color:#ffffff4d;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-input-number:-moz-placeholder-shown{text-overflow:ellipsis}.ant-input-number:placeholder-shown{text-overflow:ellipsis}.ant-input-number:focus,.ant-input-number-focused{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-input-number[disabled]{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number[disabled]:hover{border-color:#434343;border-right-width:1px!important}.ant-input-number-borderless,.ant-input-number-borderless:hover,.ant-input-number-borderless:focus,.ant-input-number-borderless-focused,.ant-input-number-borderless-disabled,.ant-input-number-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-number-lg{padding:6.5px 11px;font-size:16px}.ant-input-number-sm{padding:0 7px}.ant-input-number-group{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:table;width:100%;border-collapse:separate;border-spacing:0}.ant-input-number-group[class*=col-]{float:none;padding-right:0;padding-left:0}.ant-input-number-group>[class*=col-]{padding-right:8px}.ant-input-number-group>[class*=col-]:last-child{padding-right:0}.ant-input-number-group-addon,.ant-input-number-group-wrap,.ant-input-number-group>.ant-input-number{display:table-cell}.ant-input-number-group-addon:not(:first-child):not(:last-child),.ant-input-number-group-wrap:not(:first-child):not(:last-child),.ant-input-number-group>.ant-input-number:not(:first-child):not(:last-child){border-radius:0}.ant-input-number-group-addon,.ant-input-number-group-wrap{width:1px;white-space:nowrap;vertical-align:middle}.ant-input-number-group-wrap>*{display:block!important}.ant-input-number-group .ant-input-number{float:left;width:100%;margin-bottom:0;text-align:inherit}.ant-input-number-group .ant-input-number:focus{z-index:1;border-right-width:1px}.ant-input-number-group .ant-input-number:hover{z-index:1;border-right-width:1px}.ant-input-search-with-button .ant-input-number-group .ant-input-number:hover{z-index:0}.ant-input-number-group-addon{position:relative;padding:0 11px;color:#ffffffd9;font-weight:400;font-size:14px;text-align:center;background-color:#ffffff0a;border:1px solid #434343;border-radius:2px;transition:all .3s}.ant-input-number-group-addon .ant-select{margin:-5px -11px}.ant-input-number-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector{background-color:inherit;border:1px solid transparent;box-shadow:none}.ant-input-number-group-addon .ant-select-open .ant-select-selector,.ant-input-number-group-addon .ant-select-focused .ant-select-selector{color:#177ddc}.ant-input-number-group-addon .ant-cascader-picker{margin:-9px -12px;background-color:transparent}.ant-input-number-group-addon .ant-cascader-picker .ant-cascader-input{text-align:left;border:0;box-shadow:none}.ant-input-number-group>.ant-input-number:first-child,.ant-input-number-group-addon:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group>.ant-input-number:first-child .ant-select .ant-select-selector,.ant-input-number-group-addon:first-child .ant-select .ant-select-selector{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group>.ant-input-number-affix-wrapper:not(:first-child) .ant-input-number{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group>.ant-input-number-affix-wrapper:not(:last-child) .ant-input-number{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group-addon:first-child{border-right:0}.ant-input-number-group-addon:last-child{border-left:0}.ant-input-number-group>.ant-input-number:last-child,.ant-input-number-group-addon:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group>.ant-input-number:last-child .ant-select .ant-select-selector,.ant-input-number-group-addon:last-child .ant-select .ant-select-selector{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group-lg .ant-input-number,.ant-input-number-group-lg>.ant-input-number-group-addon{padding:6.5px 11px;font-size:16px}.ant-input-number-group-sm .ant-input-number,.ant-input-number-group-sm>.ant-input-number-group-addon{padding:0 7px}.ant-input-number-group-lg .ant-select-single .ant-select-selector{height:40px}.ant-input-number-group-sm .ant-select-single .ant-select-selector{height:24px}.ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child){border-top-left-radius:2px;border-bottom-left-radius:2px}.ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child),.ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group.ant-input-number-group-compact{display:block}.ant-input-number-group.ant-input-number-group-compact:before{display:table;content:""}.ant-input-number-group.ant-input-number-group-compact:after{display:table;clear:both;content:""}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child),.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child),.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child){border-right-width:1px}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):hover,.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):hover,.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child):hover{z-index:1}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):focus,.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):focus,.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child):focus{z-index:1}.ant-input-number-group.ant-input-number-group-compact>*{display:inline-block;float:none;vertical-align:top;border-radius:0}.ant-input-number-group.ant-input-number-group-compact>.ant-input-number-affix-wrapper{display:inline-flex}.ant-input-number-group.ant-input-number-group-compact>.ant-picker-range{display:inline-flex}.ant-input-number-group.ant-input-number-group-compact>*:not(:last-child){margin-right:-1px;border-right-width:1px}.ant-input-number-group.ant-input-number-group-compact .ant-input-number{float:none}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input{border-right-width:1px;border-radius:0}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input:hover{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input:focus{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-select-focused{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-arrow{z-index:1}.ant-input-number-group.ant-input-number-group-compact>*:first-child,.ant-input-number-group.ant-input-number-group-compact>.ant-select:first-child>.ant-select-selector,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete:first-child .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker:first-child .ant-input{border-top-left-radius:2px;border-bottom-left-radius:2px}.ant-input-number-group.ant-input-number-group-compact>*:last-child,.ant-input-number-group.ant-input-number-group-compact>.ant-select:last-child>.ant-select-selector,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker:last-child .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker-focused:last-child .ant-input{border-right-width:1px;border-top-right-radius:2px;border-bottom-right-radius:2px}.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input{vertical-align:top}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper{margin-left:-1px}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper .ant-input-affix-wrapper{border-radius:0}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input-group-addon>.ant-input-search-button{border-radius:0}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input{border-radius:2px 0 0 2px}.ant-input-number-group-wrapper{display:inline-block;text-align:start;vertical-align:top}.ant-input-number-handler{position:relative;display:block;width:100%;height:50%;overflow:hidden;color:#ffffff73;font-weight:700;line-height:0;text-align:center;border-left:1px solid #434343;transition:all .1s linear}.ant-input-number-handler:active{background:rgba(255,255,255,.08)}.ant-input-number-handler:hover .ant-input-number-handler-up-inner,.ant-input-number-handler:hover .ant-input-number-handler-down-inner{color:#165996}.ant-input-number-handler-up-inner,.ant-input-number-handler-down-inner{display:inline-block;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;right:4px;width:12px;height:12px;color:#ffffff73;line-height:12px;transition:all .1s linear;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-input-number-handler-up-inner>*,.ant-input-number-handler-down-inner>*{line-height:1}.ant-input-number-handler-up-inner svg,.ant-input-number-handler-down-inner svg{display:inline-block}.ant-input-number-handler-up-inner:before,.ant-input-number-handler-down-inner:before{display:none}.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon{display:block}.ant-input-number:hover{border-color:#165996;border-right-width:1px!important}.ant-input-number:hover+.ant-form-item-children-icon{opacity:0;transition:opacity .24s linear .24s}.ant-input-number-focused{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-input-number-disabled{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-disabled:hover{border-color:#434343;border-right-width:1px!important}.ant-input-number-disabled .ant-input-number-input{cursor:not-allowed}.ant-input-number-disabled .ant-input-number-handler-wrap,.ant-input-number-readonly .ant-input-number-handler-wrap{display:none}.ant-input-number-input{width:100%;height:30px;padding:0 11px;text-align:left;background-color:transparent;border:0;border-radius:2px;outline:0;transition:all .3s linear;-webkit-appearance:textfield!important;-moz-appearance:textfield!important;appearance:textfield!important}.ant-input-number-input::-moz-placeholder{opacity:1}.ant-input-number-input::placeholder{color:#ffffff4d;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-input-number-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-input-number-input:placeholder-shown{text-overflow:ellipsis}.ant-input-number-input[type=number]::-webkit-inner-spin-button,.ant-input-number-input[type=number]::-webkit-outer-spin-button{margin:0;-webkit-appearance:none;appearance:none}.ant-input-number-lg{padding:0;font-size:16px}.ant-input-number-lg input{height:38px}.ant-input-number-sm{padding:0}.ant-input-number-sm input{height:22px;padding:0 7px}.ant-input-number-handler-wrap{position:absolute;top:0;right:0;width:22px;height:100%;background:#141414;border-radius:0 2px 2px 0;opacity:0;transition:opacity .24s linear .1s}.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-up-inner,.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-down-inner{display:flex;align-items:center;justify-content:center;min-width:auto;margin-right:0;font-size:7px}.ant-input-number-borderless .ant-input-number-handler-wrap{border-left-width:0}.ant-input-number-handler-wrap:hover .ant-input-number-handler{height:40%}.ant-input-number:hover .ant-input-number-handler-wrap,.ant-input-number-focused .ant-input-number-handler-wrap{opacity:1}.ant-input-number-handler-up{border-top-right-radius:2px;cursor:pointer}.ant-input-number-handler-up-inner{top:50%;margin-top:-5px;text-align:center}.ant-input-number-handler-up:hover{height:60%!important}.ant-input-number-handler-down{top:0;border-top:1px solid #434343;border-bottom-right-radius:2px;cursor:pointer}.ant-input-number-handler-down-inner{top:50%;text-align:center;transform:translateY(-50%)}.ant-input-number-handler-down:hover{height:60%!important}.ant-input-number-borderless .ant-input-number-handler-down{border-top-width:0}.ant-input-number-handler-up-disabled,.ant-input-number-handler-down-disabled{cursor:not-allowed}.ant-input-number-handler-up-disabled:hover .ant-input-number-handler-up-inner,.ant-input-number-handler-down-disabled:hover .ant-input-number-handler-down-inner{color:#ffffff4d}.ant-input-number-borderless{box-shadow:none}.ant-input-number-out-of-range input{color:#a61d24}.ant-input-number-rtl{direction:rtl}.ant-input-number-rtl .ant-input-number-handler{border-right:1px solid #434343;border-left:0}.ant-input-number-rtl .ant-input-number-handler-wrap{right:auto;left:0}.ant-input-number-rtl.ant-input-number-borderless .ant-input-number-handler-wrap{border-right-width:0}.ant-input-number-rtl .ant-input-number-handler-up{border-top-right-radius:0}.ant-input-number-rtl .ant-input-number-handler-down{border-bottom-right-radius:0}.ant-input-number-rtl .ant-input-number-input{direction:ltr;text-align:right}/*!**********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/input/style/index.less ***! + \\**********************************************************************************************************************************************************************************************************************************************************/.ant-input-affix-wrapper{position:relative;display:inline-block;width:100%;min-width:0;padding:4px 11px;color:#ffffffd9;font-size:14px;line-height:1.5715;background-color:transparent;background-image:none;border:1px solid #434343;border-radius:2px;transition:all .3s;display:inline-flex}.ant-input-affix-wrapper::-moz-placeholder{opacity:1}.ant-input-affix-wrapper::placeholder{color:#ffffff4d;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-input-affix-wrapper:-moz-placeholder-shown{text-overflow:ellipsis}.ant-input-affix-wrapper:placeholder-shown{text-overflow:ellipsis}.ant-input-affix-wrapper:hover{border-color:#165996;border-right-width:1px!important}.ant-input-rtl .ant-input-affix-wrapper:hover{border-right-width:0;border-left-width:1px!important}.ant-input-affix-wrapper:focus,.ant-input-affix-wrapper-focused{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-input-rtl .ant-input-affix-wrapper:focus,.ant-input-rtl .ant-input-affix-wrapper-focused{border-right-width:0;border-left-width:1px!important}.ant-input-affix-wrapper-disabled{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-affix-wrapper-disabled:hover{border-color:#434343;border-right-width:1px!important}.ant-input-affix-wrapper[disabled]{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-affix-wrapper[disabled]:hover{border-color:#434343;border-right-width:1px!important}.ant-input-affix-wrapper-borderless,.ant-input-affix-wrapper-borderless:hover,.ant-input-affix-wrapper-borderless:focus,.ant-input-affix-wrapper-borderless-focused,.ant-input-affix-wrapper-borderless-disabled,.ant-input-affix-wrapper-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input-affix-wrapper{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-affix-wrapper-lg{padding:6.5px 11px;font-size:16px}.ant-input-affix-wrapper-sm{padding:0 7px}.ant-input-affix-wrapper-rtl{direction:rtl}.ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover{border-color:#165996;border-right-width:1px!important;z-index:1}.ant-input-rtl .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover{border-right-width:0;border-left-width:1px!important}.ant-input-search-with-button .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover{z-index:0}.ant-input-affix-wrapper-focused,.ant-input-affix-wrapper:focus{z-index:1}.ant-input-affix-wrapper-disabled .ant-input[disabled]{background:transparent}.ant-input-affix-wrapper>input.ant-input{padding:0;border:none;outline:none}.ant-input-affix-wrapper>input.ant-input:focus{box-shadow:none!important}.ant-input-affix-wrapper:before{width:0;visibility:hidden;content:" "}.ant-input-prefix,.ant-input-suffix{display:flex;flex:none;align-items:center}.ant-input-show-count-suffix{color:#ffffff73}.ant-input-show-count-has-suffix{margin-right:2px}.ant-input-prefix{margin-right:4px}.ant-input-suffix{margin-left:4px}.anticon.ant-input-clear-icon{margin:0;color:#ffffff4d;font-size:12px;vertical-align:-1px;cursor:pointer;transition:color .3s}.anticon.ant-input-clear-icon:hover{color:#ffffff73}.anticon.ant-input-clear-icon:active{color:#ffffffd9}.anticon.ant-input-clear-icon-hidden{visibility:hidden}.anticon.ant-input-clear-icon-has-suffix{margin:0 4px}.ant-input-affix-wrapper-textarea-with-clear-btn{padding:0!important;border:0!important}.ant-input-affix-wrapper-textarea-with-clear-btn .ant-input-clear-icon{position:absolute;top:8px;right:8px;z-index:1}.ant-input{box-sizing:border-box;margin:0;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;width:100%;min-width:0;padding:4px 11px;color:#ffffffd9;font-size:14px;line-height:1.5715;background-color:transparent;background-image:none;border:1px solid #434343;border-radius:2px;transition:all .3s}.ant-input::-moz-placeholder{opacity:1}.ant-input::placeholder{color:#ffffff4d;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-input:placeholder-shown{text-overflow:ellipsis}.ant-input:hover{border-color:#165996;border-right-width:1px!important}.ant-input-rtl .ant-input:hover{border-right-width:0;border-left-width:1px!important}.ant-input:focus,.ant-input-focused{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-input-rtl .ant-input:focus,.ant-input-rtl .ant-input-focused{border-right-width:0;border-left-width:1px!important}.ant-input-disabled{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-disabled:hover{border-color:#434343;border-right-width:1px!important}.ant-input[disabled]{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input[disabled]:hover{border-color:#434343;border-right-width:1px!important}.ant-input-borderless,.ant-input-borderless:hover,.ant-input-borderless:focus,.ant-input-borderless-focused,.ant-input-borderless-disabled,.ant-input-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-lg{padding:6.5px 11px;font-size:16px}.ant-input-sm{padding:0 7px}.ant-input-rtl{direction:rtl}.ant-input-group{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:table;width:100%;border-collapse:separate;border-spacing:0}.ant-input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.ant-input-group>[class*=col-]{padding-right:8px}.ant-input-group>[class*=col-]:last-child{padding-right:0}.ant-input-group-addon,.ant-input-group-wrap,.ant-input-group>.ant-input{display:table-cell}.ant-input-group-addon:not(:first-child):not(:last-child),.ant-input-group-wrap:not(:first-child):not(:last-child),.ant-input-group>.ant-input:not(:first-child):not(:last-child){border-radius:0}.ant-input-group-addon,.ant-input-group-wrap{width:1px;white-space:nowrap;vertical-align:middle}.ant-input-group-wrap>*{display:block!important}.ant-input-group .ant-input{float:left;width:100%;margin-bottom:0;text-align:inherit}.ant-input-group .ant-input:focus{z-index:1;border-right-width:1px}.ant-input-group .ant-input:hover{z-index:1;border-right-width:1px}.ant-input-search-with-button .ant-input-group .ant-input:hover{z-index:0}.ant-input-group-addon{position:relative;padding:0 11px;color:#ffffffd9;font-weight:400;font-size:14px;text-align:center;background-color:#ffffff0a;border:1px solid #434343;border-radius:2px;transition:all .3s}.ant-input-group-addon .ant-select{margin:-5px -11px}.ant-input-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector{background-color:inherit;border:1px solid transparent;box-shadow:none}.ant-input-group-addon .ant-select-open .ant-select-selector,.ant-input-group-addon .ant-select-focused .ant-select-selector{color:#177ddc}.ant-input-group-addon .ant-cascader-picker{margin:-9px -12px;background-color:transparent}.ant-input-group-addon .ant-cascader-picker .ant-cascader-input{text-align:left;border:0;box-shadow:none}.ant-input-group>.ant-input:first-child,.ant-input-group-addon:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-group>.ant-input:first-child .ant-select .ant-select-selector,.ant-input-group-addon:first-child .ant-select .ant-select-selector{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-group>.ant-input-affix-wrapper:not(:first-child) .ant-input{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group>.ant-input-affix-wrapper:not(:last-child) .ant-input{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-group-addon:first-child{border-right:0}.ant-input-group-addon:last-child{border-left:0}.ant-input-group>.ant-input:last-child,.ant-input-group-addon:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group>.ant-input:last-child .ant-select .ant-select-selector,.ant-input-group-addon:last-child .ant-select .ant-select-selector{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group-lg .ant-input,.ant-input-group-lg>.ant-input-group-addon{padding:6.5px 11px;font-size:16px}.ant-input-group-sm .ant-input,.ant-input-group-sm>.ant-input-group-addon{padding:0 7px}.ant-input-group-lg .ant-select-single .ant-select-selector{height:40px}.ant-input-group-sm .ant-select-single .ant-select-selector{height:24px}.ant-input-group .ant-input-affix-wrapper:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-search .ant-input-group .ant-input-affix-wrapper:not(:last-child){border-top-left-radius:2px;border-bottom-left-radius:2px}.ant-input-group .ant-input-affix-wrapper:not(:first-child),.ant-input-search .ant-input-group .ant-input-affix-wrapper:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group.ant-input-group-compact{display:block}.ant-input-group.ant-input-group-compact:before{display:table;content:""}.ant-input-group.ant-input-group-compact:after{display:table;clear:both;content:""}.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child),.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child),.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child){border-right-width:1px}.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):hover,.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):hover,.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child):hover{z-index:1}.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):focus,.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):focus,.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child):focus{z-index:1}.ant-input-group.ant-input-group-compact>*{display:inline-block;float:none;vertical-align:top;border-radius:0}.ant-input-group.ant-input-group-compact>.ant-input-affix-wrapper{display:inline-flex}.ant-input-group.ant-input-group-compact>.ant-picker-range{display:inline-flex}.ant-input-group.ant-input-group-compact>*:not(:last-child){margin-right:-1px;border-right-width:1px}.ant-input-group.ant-input-group-compact .ant-input{float:none}.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selector,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input{border-right-width:1px;border-radius:0}.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selector:hover,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input:hover{z-index:1}.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selector:focus,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input:focus{z-index:1}.ant-input-group.ant-input-group-compact>.ant-select-focused{z-index:1}.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-arrow{z-index:1}.ant-input-group.ant-input-group-compact>*:first-child,.ant-input-group.ant-input-group-compact>.ant-select:first-child>.ant-select-selector,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete:first-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker:first-child .ant-input{border-top-left-radius:2px;border-bottom-left-radius:2px}.ant-input-group.ant-input-group-compact>*:last-child,.ant-input-group.ant-input-group-compact>.ant-select:last-child>.ant-select-selector,.ant-input-group.ant-input-group-compact>.ant-cascader-picker:last-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker-focused:last-child .ant-input{border-right-width:1px;border-top-right-radius:2px;border-bottom-right-radius:2px}.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input{vertical-align:top}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper{margin-left:-1px}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper .ant-input-affix-wrapper{border-radius:0}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input-group-addon>.ant-input-search-button{border-radius:0}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input{border-radius:2px 0 0 2px}.ant-input-group>.ant-input-rtl:first-child,.ant-input-group-rtl .ant-input-group-addon:first-child{border-radius:0 2px 2px 0}.ant-input-group-rtl .ant-input-group-addon:first-child{border-right:1px solid #434343;border-left:0}.ant-input-group-rtl .ant-input-group-addon:last-child{border-right:0;border-left:1px solid #434343}.ant-input-group-rtl.ant-input-group>.ant-input:last-child,.ant-input-group-rtl.ant-input-group-addon:last-child{border-radius:2px 0 0 2px}.ant-input-group-rtl.ant-input-group .ant-input-affix-wrapper:not(:first-child){border-radius:2px 0 0 2px}.ant-input-group-rtl.ant-input-group .ant-input-affix-wrapper:not(:last-child){border-radius:0 2px 2px 0}.ant-input-group-rtl.ant-input-group.ant-input-group-compact>*:not(:last-child){margin-right:0;margin-left:-1px;border-left-width:1px}.ant-input-group-rtl.ant-input-group.ant-input-group-compact>*:first-child,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-select:first-child>.ant-select-selector,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-select-auto-complete:first-child .ant-input,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-cascader-picker:first-child .ant-input{border-radius:0 2px 2px 0}.ant-input-group-rtl.ant-input-group.ant-input-group-compact>*:last-child,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-select:last-child>.ant-select-selector,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-select-auto-complete:last-child .ant-input,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-cascader-picker:last-child .ant-input,.ant-input-group-rtl.ant-input-group.ant-input-group-compact>.ant-cascader-picker-focused:last-child .ant-input{border-left-width:1px;border-radius:2px 0 0 2px}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper-rtl+.ant-input-group-wrapper-rtl{margin-right:-1px;margin-left:0}.ant-input-group.ant-input-group-compact .ant-input-group-wrapper-rtl:not(:last-child).ant-input-search>.ant-input-group>.ant-input{border-radius:0 2px 2px 0}.ant-input-group-wrapper{display:inline-block;width:100%;text-align:start;vertical-align:top}.ant-input-password-icon{color:#ffffff73;cursor:pointer;transition:all .3s}.ant-input-password-icon:hover{color:#ffffffd9}.ant-input[type=color]{height:32px}.ant-input[type=color].ant-input-lg{height:40px}.ant-input[type=color].ant-input-sm{height:24px;padding-top:3px;padding-bottom:3px}.ant-input-textarea-show-count>.ant-input{height:100%}.ant-input-textarea-show-count:after{float:right;color:#ffffff73;white-space:nowrap;content:attr(data-count);pointer-events:none}.ant-input-search .ant-input:hover,.ant-input-search .ant-input:focus{border-color:#165996}.ant-input-search .ant-input:hover+.ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary),.ant-input-search .ant-input:focus+.ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary){border-left-color:#165996}.ant-input-search .ant-input-affix-wrapper{border-radius:0}.ant-input-search .ant-input-lg{line-height:1.5713}.ant-input-search>.ant-input-group>.ant-input-group-addon:last-child{left:-1px;padding:0;border:0}.ant-input-search>.ant-input-group>.ant-input-group-addon:last-child .ant-input-search-button{padding-top:0;padding-bottom:0;border-radius:0 2px 2px 0}.ant-input-search>.ant-input-group>.ant-input-group-addon:last-child .ant-input-search-button:not(.ant-btn-primary){color:#ffffff73}.ant-input-search>.ant-input-group>.ant-input-group-addon:last-child .ant-input-search-button:not(.ant-btn-primary).ant-btn-loading:before{top:0;right:0;bottom:0;left:0}.ant-input-search-button{height:32px}.ant-input-search-button:hover,.ant-input-search-button:focus{z-index:1}.ant-input-search-large .ant-input-search-button{height:40px}.ant-input-search-small .ant-input-search-button{height:24px}.ant-input-group-wrapper-rtl,.ant-input-group-rtl{direction:rtl}.ant-input-affix-wrapper.ant-input-affix-wrapper-rtl>input.ant-input{border:none;outline:none}.ant-input-affix-wrapper-rtl .ant-input-prefix{margin:0 0 0 4px}.ant-input-affix-wrapper-rtl .ant-input-suffix{margin:0 4px 0 0}.ant-input-textarea-rtl{direction:rtl}.ant-input-textarea-rtl.ant-input-textarea-show-count:after{text-align:left}.ant-input-affix-wrapper-rtl .ant-input-clear-icon-has-suffix{margin-right:0;margin-left:4px}.ant-input-affix-wrapper-rtl .ant-input-clear-icon{right:auto;left:8px}.ant-input-search-rtl{direction:rtl}.ant-input-search-rtl .ant-input:hover+.ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary),.ant-input-search-rtl .ant-input:focus+.ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary){border-right-color:#165996;border-left-color:#434343}.ant-input-search-rtl>.ant-input-group>.ant-input-affix-wrapper:hover,.ant-input-search-rtl>.ant-input-group>.ant-input-affix-wrapper-focused{border-right-color:#165996}.ant-input-search-rtl>.ant-input-group>.ant-input-group-addon{right:-1px;left:auto}.ant-input-search-rtl>.ant-input-group>.ant-input-group-addon .ant-input-search-button{border-radius:2px 0 0 2px}@media screen and (-ms-high-contrast: active),(-ms-high-contrast: none){.ant-input{height:32px}.ant-input-lg{height:40px}.ant-input-sm{height:24px}.ant-input-affix-wrapper>input.ant-input{height:auto}}/*!***********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/layout/style/index.less ***! + \\***********************************************************************************************************************************************************************************************************************************************************/.ant-layout{display:flex;flex:auto;flex-direction:column;min-height:0;background:#000}.ant-layout,.ant-layout *{box-sizing:border-box}.ant-layout.ant-layout-has-sider{flex-direction:row}.ant-layout.ant-layout-has-sider>.ant-layout,.ant-layout.ant-layout-has-sider>.ant-layout-content{width:0}.ant-layout-header,.ant-layout-footer{flex:0 0 auto}.ant-layout-header{height:64px;padding:0 50px;color:#ffffffd9;line-height:64px;background:#1f1f1f}.ant-layout-footer{padding:24px 50px;color:#ffffffd9;font-size:14px;background:#000}.ant-layout-content{flex:auto;min-height:0}.ant-layout-sider{position:relative;min-width:0;background:#1f1f1f;transition:all .2s}.ant-layout-sider-children{height:100%;margin-top:-.1px;padding-top:.1px}.ant-layout-sider-children .ant-menu.ant-menu-inline-collapsed{width:auto}.ant-layout-sider-has-trigger{padding-bottom:48px}.ant-layout-sider-right{order:1}.ant-layout-sider-trigger{position:fixed;bottom:0;z-index:1;height:48px;color:#fff;line-height:48px;text-align:center;background:#262626;cursor:pointer;transition:all .2s}.ant-layout-sider-zero-width>*{overflow:hidden}.ant-layout-sider-zero-width-trigger{position:absolute;top:64px;right:-36px;z-index:1;width:36px;height:42px;color:#fff;font-size:18px;line-height:42px;text-align:center;background:#1f1f1f;border-radius:0 2px 2px 0;cursor:pointer;transition:background .3s ease}.ant-layout-sider-zero-width-trigger:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;transition:all .3s;content:""}.ant-layout-sider-zero-width-trigger:hover:after{background:rgba(255,255,255,.1)}.ant-layout-sider-zero-width-trigger-right{left:-36px;border-radius:2px 0 0 2px}.ant-layout-sider-light{background:#fff}.ant-layout-sider-light .ant-layout-sider-trigger,.ant-layout-sider-light .ant-layout-sider-zero-width-trigger{color:#ffffffd9;background:#fff}.ant-layout-rtl{direction:rtl}/*!*********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/list/style/index.less ***! + \\*********************************************************************************************************************************************************************************************************************************************************/.ant-list .ant-card{background:transparent}.ant-list{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative}.ant-list *{outline:none}.ant-list-pagination{margin-top:24px;text-align:right}.ant-list-pagination .ant-pagination-options{text-align:left}.ant-list-more{margin-top:12px;text-align:center}.ant-list-more button{padding-right:32px;padding-left:32px}.ant-list-spin{min-height:40px;text-align:center}.ant-list-empty-text{padding:16px;color:#ffffff4d;font-size:14px;text-align:center}.ant-list-items{margin:0;padding:0;list-style:none}.ant-list-item{display:flex;align-items:center;justify-content:space-between;padding:12px 0;color:#ffffffd9}.ant-list-item-meta{display:flex;flex:1;align-items:flex-start;max-width:100%}.ant-list-item-meta-avatar{margin-right:16px}.ant-list-item-meta-content{flex:1 0;width:0;color:#ffffffd9}.ant-list-item-meta-title{margin-bottom:4px;color:#ffffffd9;font-size:14px;line-height:1.5715}.ant-list-item-meta-title>a{color:#ffffffd9;transition:all .3s}.ant-list-item-meta-title>a:hover{color:#177ddc}.ant-list-item-meta-description{color:#ffffff73;font-size:14px;line-height:1.5715}.ant-list-item-action{flex:0 0 auto;margin-left:48px;padding:0;font-size:0;list-style:none}.ant-list-item-action>li{position:relative;display:inline-block;padding:0 8px;color:#ffffff73;font-size:14px;line-height:1.5715;text-align:center}.ant-list-item-action>li:first-child{padding-left:0}.ant-list-item-action-split{position:absolute;top:50%;right:0;width:1px;height:14px;margin-top:-7px;background-color:#303030}.ant-list-header,.ant-list-footer{background:transparent}.ant-list-header,.ant-list-footer{padding-top:12px;padding-bottom:12px}.ant-list-empty{padding:16px 0;color:#ffffff73;font-size:12px;text-align:center}.ant-list-split .ant-list-item{border-bottom:1px solid #303030}.ant-list-split .ant-list-item:last-child{border-bottom:none}.ant-list-split .ant-list-header{border-bottom:1px solid #303030}.ant-list-split.ant-list-empty .ant-list-footer{border-top:1px solid #303030}.ant-list-loading .ant-list-spin-nested-loading{min-height:32px}.ant-list-split.ant-list-something-after-last-item .ant-spin-container>.ant-list-items>.ant-list-item:last-child{border-bottom:1px solid #303030}.ant-list-lg .ant-list-item{padding:16px 24px}.ant-list-sm .ant-list-item{padding:8px 16px}.ant-list-vertical .ant-list-item{align-items:initial}.ant-list-vertical .ant-list-item-main{display:block;flex:1}.ant-list-vertical .ant-list-item-extra{margin-left:40px}.ant-list-vertical .ant-list-item-meta{margin-bottom:16px}.ant-list-vertical .ant-list-item-meta-title{margin-bottom:12px;color:#ffffffd9;font-size:16px;line-height:24px}.ant-list-vertical .ant-list-item-action{margin-top:16px;margin-left:auto}.ant-list-vertical .ant-list-item-action>li{padding:0 16px}.ant-list-vertical .ant-list-item-action>li:first-child{padding-left:0}.ant-list-grid .ant-col>.ant-list-item{display:block;max-width:100%;margin-bottom:16px;padding-top:0;padding-bottom:0;border-bottom:none}.ant-list-item-no-flex{display:block}.ant-list:not(.ant-list-vertical) .ant-list-item-no-flex .ant-list-item-action{float:right}.ant-list-bordered{border:1px solid #434343;border-radius:2px}.ant-list-bordered .ant-list-header,.ant-list-bordered .ant-list-footer,.ant-list-bordered .ant-list-item{padding-right:24px;padding-left:24px}.ant-list-bordered .ant-list-pagination{margin:16px 24px}.ant-list-bordered.ant-list-sm .ant-list-item,.ant-list-bordered.ant-list-sm .ant-list-header,.ant-list-bordered.ant-list-sm .ant-list-footer{padding:8px 16px}.ant-list-bordered.ant-list-lg .ant-list-item,.ant-list-bordered.ant-list-lg .ant-list-header,.ant-list-bordered.ant-list-lg .ant-list-footer{padding:16px 24px}@media screen and (max-width: 768px){.ant-list-item-action,.ant-list-vertical .ant-list-item-extra{margin-left:24px}}@media screen and (max-width: 576px){.ant-list-item{flex-wrap:wrap}.ant-list-item-action{margin-left:12px}.ant-list-vertical .ant-list-item{flex-wrap:wrap-reverse}.ant-list-vertical .ant-list-item-main{min-width:220px}.ant-list-vertical .ant-list-item-extra{margin:auto auto 16px}}.ant-list-rtl{direction:rtl;text-align:right}.ant-list-rtl .ReactVirtualized__List .ant-list-item{direction:rtl}.ant-list-rtl .ant-list-pagination{text-align:left}.ant-list-rtl .ant-list-item-meta-avatar{margin-right:0;margin-left:16px}.ant-list-rtl .ant-list-item-action{margin-right:48px;margin-left:0}.ant-list.ant-list-rtl .ant-list-item-action>li:first-child{padding-right:0;padding-left:16px}.ant-list-rtl .ant-list-item-action-split{right:auto;left:0}.ant-list-rtl.ant-list-vertical .ant-list-item-extra{margin-right:40px;margin-left:0}.ant-list-rtl.ant-list-vertical .ant-list-item-action{margin-right:auto}.ant-list-rtl .ant-list-vertical .ant-list-item-action>li:first-child{padding-right:0;padding-left:16px}.ant-list-rtl .ant-list:not(.ant-list-vertical) .ant-list-item-no-flex .ant-list-item-action{float:left}@media screen and (max-width: 768px){.ant-list-rtl .ant-list-item-action,.ant-list-rtl .ant-list-vertical .ant-list-item-extra{margin-right:24px;margin-left:0}}@media screen and (max-width: 576px){.ant-list-rtl .ant-list-item-action{margin-right:22px;margin-left:0}.ant-list-rtl.ant-list-vertical .ant-list-item-extra{margin:auto auto 16px}}/*!*********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/spin/style/index.less ***! + \\*********************************************************************************************************************************************************************************************************************************************************/.ant-spin{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;display:none;color:#177ddc;text-align:center;vertical-align:middle;opacity:0;transition:transform .3s cubic-bezier(.78,.14,.15,.86)}.ant-spin-spinning{position:static;display:inline-block;opacity:1}.ant-spin-nested-loading{position:relative}.ant-spin-nested-loading>div>.ant-spin{position:absolute;top:0;left:0;z-index:4;display:block;width:100%;height:100%;max-height:400px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-dot{position:absolute;top:50%;left:50%;margin:-10px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-text{position:absolute;top:50%;width:100%;padding-top:5px;text-shadow:0 1px 2px #141414}.ant-spin-nested-loading>div>.ant-spin.ant-spin-show-text .ant-spin-dot{margin-top:-20px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-dot{margin:-7px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-text{padding-top:2px}.ant-spin-nested-loading>div>.ant-spin-sm.ant-spin-show-text .ant-spin-dot{margin-top:-17px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-dot{margin:-16px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-text{padding-top:11px}.ant-spin-nested-loading>div>.ant-spin-lg.ant-spin-show-text .ant-spin-dot{margin-top:-26px}.ant-spin-container{position:relative;transition:opacity .3s}.ant-spin-container:after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;display:none \\ ;width:100%;height:100%;background:#141414;opacity:0;transition:all .3s;content:"";pointer-events:none}.ant-spin-blur{clear:both;opacity:.5;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:none}.ant-spin-blur:after{opacity:.4;pointer-events:auto}.ant-spin-tip{color:#ffffff73}.ant-spin-dot{position:relative;display:inline-block;font-size:20px;width:1em;height:1em}.ant-spin-dot-item{position:absolute;display:block;width:9px;height:9px;background-color:#177ddc;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.ant-spin-dot-item:nth-child(1){top:0;left:0}.ant-spin-dot-item:nth-child(2){top:0;right:0;animation-delay:.4s}.ant-spin-dot-item:nth-child(3){right:0;bottom:0;animation-delay:.8s}.ant-spin-dot-item:nth-child(4){bottom:0;left:0;animation-delay:1.2s}.ant-spin-dot-spin{transform:rotate(45deg);animation:antRotate 1.2s infinite linear}.ant-spin-sm .ant-spin-dot{font-size:14px}.ant-spin-sm .ant-spin-dot i{width:6px;height:6px}.ant-spin-lg .ant-spin-dot{font-size:32px}.ant-spin-lg .ant-spin-dot i{width:14px;height:14px}.ant-spin.ant-spin-show-text .ant-spin-text{display:block}@media all and (-ms-high-contrast: none),(-ms-high-contrast: active){.ant-spin-blur{background:#141414;opacity:.5}}@keyframes antSpinMove{to{opacity:1}}@keyframes antRotate{to{transform:rotate(405deg)}}.ant-spin-rtl{direction:rtl}.ant-spin-rtl .ant-spin-dot-spin{transform:rotate(-45deg);animation-name:antRotateRtl}@keyframes antRotateRtl{to{transform:rotate(-405deg)}}/*!***************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/pagination/style/index.less ***! + \\***************************************************************************************************************************************************************************************************************************************************************/.ant-pagination{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum"}.ant-pagination ul,.ant-pagination ol{margin:0;padding:0;list-style:none}.ant-pagination:after{display:block;clear:both;height:0;overflow:hidden;visibility:hidden;content:" "}.ant-pagination-total-text{display:inline-block;height:32px;margin-right:8px;line-height:30px;vertical-align:middle}.ant-pagination-item{display:inline-block;min-width:32px;height:32px;margin-right:8px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:30px;text-align:center;vertical-align:middle;list-style:none;background-color:transparent;border:1px solid #434343;border-radius:2px;outline:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-pagination-item a{display:block;padding:0 6px;color:#ffffffd9;transition:none}.ant-pagination-item a:hover{text-decoration:none}.ant-pagination-item:hover{border-color:#177ddc;transition:all .3s}.ant-pagination-item:hover a{color:#177ddc}.ant-pagination-item:focus-visible{border-color:#177ddc;transition:all .3s}.ant-pagination-item:focus-visible a{color:#177ddc}.ant-pagination-item-active{font-weight:500;background:transparent;border-color:#177ddc}.ant-pagination-item-active a{color:#177ddc}.ant-pagination-item-active:hover{border-color:#165996}.ant-pagination-item-active:focus-visible{border-color:#165996}.ant-pagination-item-active:hover a{color:#165996}.ant-pagination-item-active:focus-visible a{color:#165996}.ant-pagination-jump-prev,.ant-pagination-jump-next{outline:0}.ant-pagination-jump-prev .ant-pagination-item-container,.ant-pagination-jump-next .ant-pagination-item-container{position:relative}.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon,.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon{color:#177ddc;font-size:12px;letter-spacing:-1px;opacity:0;transition:all .2s}.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon-svg,.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon-svg{top:0;right:0;bottom:0;left:0;margin:auto}.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-ellipsis,.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-ellipsis{position:absolute;top:0;right:0;bottom:0;left:0;display:block;margin:auto;color:#ffffff4d;font-family:Arial,Helvetica,sans-serif;letter-spacing:2px;text-align:center;text-indent:.13em;opacity:1;transition:all .2s}.ant-pagination-jump-prev:hover .ant-pagination-item-link-icon,.ant-pagination-jump-next:hover .ant-pagination-item-link-icon{opacity:1}.ant-pagination-jump-prev:hover .ant-pagination-item-ellipsis,.ant-pagination-jump-next:hover .ant-pagination-item-ellipsis{opacity:0}.ant-pagination-jump-prev:focus-visible .ant-pagination-item-link-icon,.ant-pagination-jump-next:focus-visible .ant-pagination-item-link-icon{opacity:1}.ant-pagination-jump-prev:focus-visible .ant-pagination-item-ellipsis,.ant-pagination-jump-next:focus-visible .ant-pagination-item-ellipsis{opacity:0}.ant-pagination-prev,.ant-pagination-jump-prev,.ant-pagination-jump-next{margin-right:8px}.ant-pagination-prev,.ant-pagination-next,.ant-pagination-jump-prev,.ant-pagination-jump-next{display:inline-block;min-width:32px;height:32px;color:#ffffffd9;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:32px;text-align:center;vertical-align:middle;list-style:none;border-radius:2px;cursor:pointer;transition:all .3s}.ant-pagination-prev,.ant-pagination-next{font-family:Arial,Helvetica,sans-serif;outline:0}.ant-pagination-prev button,.ant-pagination-next button{color:#ffffffd9;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-pagination-prev:hover button,.ant-pagination-next:hover button{border-color:#165996}.ant-pagination-prev .ant-pagination-item-link,.ant-pagination-next .ant-pagination-item-link{display:block;width:100%;height:100%;padding:0;font-size:12px;text-align:center;background-color:transparent;border:1px solid #434343;border-radius:2px;outline:none;transition:all .3s}.ant-pagination-prev:focus-visible .ant-pagination-item-link,.ant-pagination-next:focus-visible .ant-pagination-item-link{color:#177ddc;border-color:#177ddc}.ant-pagination-prev:hover .ant-pagination-item-link,.ant-pagination-next:hover .ant-pagination-item-link{color:#177ddc;border-color:#177ddc}.ant-pagination-disabled,.ant-pagination-disabled:hover{cursor:not-allowed}.ant-pagination-disabled .ant-pagination-item-link,.ant-pagination-disabled:hover .ant-pagination-item-link{color:#ffffff4d;border-color:#434343;cursor:not-allowed}.ant-pagination-disabled:focus-visible{cursor:not-allowed}.ant-pagination-disabled:focus-visible .ant-pagination-item-link{color:#ffffff4d;border-color:#434343;cursor:not-allowed}.ant-pagination-slash{margin:0 10px 0 5px}.ant-pagination-options{display:inline-block;margin-left:16px;vertical-align:middle}@media all and (-ms-high-contrast: none){.ant-pagination-options *::-ms-backdrop,.ant-pagination-options{vertical-align:top}}.ant-pagination-options-size-changer.ant-select{display:inline-block;width:auto}.ant-pagination-options-quick-jumper{display:inline-block;height:32px;margin-left:8px;line-height:32px;vertical-align:top}.ant-pagination-options-quick-jumper input{position:relative;display:inline-block;width:100%;min-width:0;padding:4px 11px;color:#ffffffd9;font-size:14px;line-height:1.5715;background-color:transparent;background-image:none;border:1px solid #434343;border-radius:2px;transition:all .3s;width:50px;height:32px;margin:0 8px}.ant-pagination-options-quick-jumper input::-moz-placeholder{opacity:1}.ant-pagination-options-quick-jumper input::placeholder{color:#ffffff4d;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-pagination-options-quick-jumper input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-pagination-options-quick-jumper input:placeholder-shown{text-overflow:ellipsis}.ant-pagination-options-quick-jumper input:hover{border-color:#165996;border-right-width:1px!important}.ant-pagination-options-quick-jumper input:focus,.ant-pagination-options-quick-jumper input-focused{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-pagination-options-quick-jumper input-disabled{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-pagination-options-quick-jumper input-disabled:hover{border-color:#434343;border-right-width:1px!important}.ant-pagination-options-quick-jumper input[disabled]{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-pagination-options-quick-jumper input[disabled]:hover{border-color:#434343;border-right-width:1px!important}.ant-pagination-options-quick-jumper input-borderless,.ant-pagination-options-quick-jumper input-borderless:hover,.ant-pagination-options-quick-jumper input-borderless:focus,.ant-pagination-options-quick-jumper input-borderless-focused,.ant-pagination-options-quick-jumper input-borderless-disabled,.ant-pagination-options-quick-jumper input-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-pagination-options-quick-jumper input{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-pagination-options-quick-jumper input-lg{padding:6.5px 11px;font-size:16px}.ant-pagination-options-quick-jumper input-sm{padding:0 7px}.ant-pagination-simple .ant-pagination-prev,.ant-pagination-simple .ant-pagination-next{height:24px;line-height:24px;vertical-align:top}.ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link,.ant-pagination-simple .ant-pagination-next .ant-pagination-item-link{height:24px;background-color:transparent;border:0}.ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link:after,.ant-pagination-simple .ant-pagination-next .ant-pagination-item-link:after{height:24px;line-height:24px}.ant-pagination-simple .ant-pagination-simple-pager{display:inline-block;height:24px;margin-right:8px}.ant-pagination-simple .ant-pagination-simple-pager input{box-sizing:border-box;height:100%;margin-right:8px;padding:0 6px;text-align:center;background-color:transparent;border:1px solid #434343;border-radius:2px;outline:none;transition:border-color .3s}.ant-pagination-simple .ant-pagination-simple-pager input:hover{border-color:#177ddc}.ant-pagination-simple .ant-pagination-simple-pager input:focus{border-color:#3c9be8;box-shadow:0 0 0 2px #177ddc33}.ant-pagination-simple .ant-pagination-simple-pager input[disabled]{color:#ffffff4d;background:rgba(255,255,255,.08);border-color:#434343;cursor:not-allowed}.ant-pagination.mini .ant-pagination-total-text,.ant-pagination.mini .ant-pagination-simple-pager{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-item{min-width:24px;height:24px;margin:0;line-height:22px}.ant-pagination.mini .ant-pagination-item:not(.ant-pagination-item-active){background:transparent;border-color:transparent}.ant-pagination.mini .ant-pagination-prev,.ant-pagination.mini .ant-pagination-next{min-width:24px;height:24px;margin:0;line-height:24px}.ant-pagination.mini .ant-pagination-prev .ant-pagination-item-link,.ant-pagination.mini .ant-pagination-next .ant-pagination-item-link{background:transparent;border-color:transparent}.ant-pagination.mini .ant-pagination-prev .ant-pagination-item-link:after,.ant-pagination.mini .ant-pagination-next .ant-pagination-item-link:after{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-jump-prev,.ant-pagination.mini .ant-pagination-jump-next{height:24px;margin-right:0;line-height:24px}.ant-pagination.mini .ant-pagination-options{margin-left:2px}.ant-pagination.mini .ant-pagination-options-size-changer{top:0px}.ant-pagination.mini .ant-pagination-options-quick-jumper{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-options-quick-jumper input{padding:0 7px;width:44px;height:24px}.ant-pagination.ant-pagination-disabled{cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item{background:rgba(255,255,255,.08);border-color:#434343;cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item a{color:#ffffff4d;background:transparent;border:none;cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item-active{background:rgba(255,255,255,.25)}.ant-pagination.ant-pagination-disabled .ant-pagination-item-active a{color:#000}.ant-pagination.ant-pagination-disabled .ant-pagination-item-link{color:#ffffff4d;background:rgba(255,255,255,.08);border-color:#434343;cursor:not-allowed}.ant-pagination-simple.ant-pagination.ant-pagination-disabled .ant-pagination-item-link{background:transparent}.ant-pagination.ant-pagination-disabled .ant-pagination-item-link-icon{opacity:0}.ant-pagination.ant-pagination-disabled .ant-pagination-item-ellipsis{opacity:1}.ant-pagination.ant-pagination-disabled .ant-pagination-simple-pager{color:#ffffff4d}@media only screen and (max-width: 992px){.ant-pagination-item-after-jump-prev,.ant-pagination-item-before-jump-next{display:none}}@media only screen and (max-width: 576px){.ant-pagination-options{display:none}}.ant-pagination-rtl .ant-pagination-total-text,.ant-pagination-rtl .ant-pagination-item,.ant-pagination-rtl .ant-pagination-prev,.ant-pagination-rtl .ant-pagination-jump-prev,.ant-pagination-rtl .ant-pagination-jump-next{margin-right:0;margin-left:8px}.ant-pagination-rtl .ant-pagination-slash{margin:0 5px 0 10px}.ant-pagination-rtl .ant-pagination-options{margin-right:16px;margin-left:0}.ant-pagination-rtl .ant-pagination-options .ant-pagination-options-size-changer.ant-select{margin-right:0;margin-left:8px}.ant-pagination-rtl .ant-pagination-options .ant-pagination-options-quick-jumper{margin-left:0}.ant-pagination-rtl.ant-pagination-simple .ant-pagination-simple-pager,.ant-pagination-rtl.ant-pagination-simple .ant-pagination-simple-pager input{margin-right:0;margin-left:8px}.ant-pagination-rtl.ant-pagination.mini .ant-pagination-options{margin-right:2px;margin-left:0}/*!********************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/locale-provider/style/index.less ***! + \\********************************************************************************************************************************************************************************************************************************************************************//*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/mentions/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-mentions{box-sizing:border-box;margin:0;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";width:100%;min-width:0;color:#ffffffd9;font-size:14px;background-color:transparent;background-image:none;border:1px solid #434343;border-radius:2px;transition:all .3s;position:relative;display:inline-block;height:auto;padding:0;overflow:hidden;line-height:1.5715;white-space:pre-wrap;vertical-align:bottom}.ant-mentions::-moz-placeholder{opacity:1}.ant-mentions::placeholder{color:#ffffff4d;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-mentions:-moz-placeholder-shown{text-overflow:ellipsis}.ant-mentions:placeholder-shown{text-overflow:ellipsis}.ant-mentions:hover{border-color:#165996;border-right-width:1px!important}.ant-mentions:focus,.ant-mentions-focused{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-mentions-disabled{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-mentions-disabled:hover{border-color:#434343;border-right-width:1px!important}.ant-mentions[disabled]{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-mentions[disabled]:hover{border-color:#434343;border-right-width:1px!important}.ant-mentions-borderless,.ant-mentions-borderless:hover,.ant-mentions-borderless:focus,.ant-mentions-borderless-focused,.ant-mentions-borderless-disabled,.ant-mentions-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-mentions{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-mentions-lg{padding:6.5px 11px;font-size:16px}.ant-mentions-sm{padding:0 7px}.ant-mentions-disabled>textarea{color:#ffffff4d;background-color:#ffffff14;border-color:#434343;box-shadow:none;cursor:not-allowed;opacity:1}.ant-mentions-disabled>textarea:hover{border-color:#434343;border-right-width:1px!important}.ant-mentions-focused{border-color:#177ddc;box-shadow:0 0 0 2px #177ddc33;border-right-width:1px!important;outline:0}.ant-mentions>textarea,.ant-mentions-measure{min-height:30px;margin:0;padding:4px 11px;overflow:inherit;overflow-x:hidden;overflow-y:auto;font-weight:inherit;font-size:inherit;font-family:inherit;font-style:inherit;font-variant:inherit;font-size-adjust:inherit;font-stretch:inherit;line-height:inherit;direction:inherit;letter-spacing:inherit;white-space:inherit;text-align:inherit;vertical-align:top;word-wrap:break-word;word-break:inherit;-moz-tab-size:inherit;-o-tab-size:inherit;tab-size:inherit}.ant-mentions>textarea{width:100%;border:none;outline:none;resize:none;background-color:transparent}.ant-mentions>textarea::-moz-placeholder{opacity:1}.ant-mentions>textarea::placeholder{color:#ffffff4d;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-mentions>textarea:-moz-placeholder-shown{text-overflow:ellipsis}.ant-mentions>textarea:placeholder-shown{text-overflow:ellipsis}.ant-mentions-measure{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;color:transparent;pointer-events:none}.ant-mentions-measure>span{display:inline-block;min-height:1em}.ant-mentions-dropdown{margin:0;padding:0;color:#ffffffd9;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;box-sizing:border-box;font-size:14px;font-variant:initial;background-color:#1f1f1f;border-radius:2px;outline:none;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003}.ant-mentions-dropdown-hidden{display:none}.ant-mentions-dropdown-menu{max-height:250px;margin-bottom:0;padding-left:0;overflow:auto;list-style:none;outline:none}.ant-mentions-dropdown-menu-item{position:relative;display:block;min-width:100px;padding:5px 12px;overflow:hidden;color:#ffffffd9;font-weight:400;line-height:1.5715;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;transition:background .3s ease}.ant-mentions-dropdown-menu-item:hover{background-color:#ffffff14}.ant-mentions-dropdown-menu-item:first-child{border-radius:2px 2px 0 0}.ant-mentions-dropdown-menu-item:last-child{border-radius:0 0 2px 2px}.ant-mentions-dropdown-menu-item-disabled{color:#ffffff4d;cursor:not-allowed}.ant-mentions-dropdown-menu-item-disabled:hover{color:#ffffff4d;background-color:#1f1f1f;cursor:not-allowed}.ant-mentions-dropdown-menu-item-selected{color:#ffffffd9;font-weight:600;background-color:#ffffff0a}.ant-mentions-dropdown-menu-item-active{background-color:#ffffff14}.ant-mentions-rtl{direction:rtl}/*!************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/message/style/index.less ***! + \\************************************************************************************************************************************************************************************************************************************************************/.ant-message{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:fixed;top:8px;left:0;z-index:1010;width:100%;pointer-events:none}.ant-message-notice{padding:8px;text-align:center}.ant-message-notice-content{display:inline-block;padding:10px 16px;background:#1f1f1f;border-radius:2px;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003;pointer-events:all}.ant-message-success .anticon{color:#49aa19}.ant-message-error .anticon{color:#a61d24}.ant-message-warning .anticon{color:#d89614}.ant-message-info .anticon,.ant-message-loading .anticon{color:#177ddc}.ant-message .anticon{position:relative;top:1px;margin-right:8px;font-size:16px}.ant-message-notice.ant-move-up-leave.ant-move-up-leave-active{animation-name:MessageMoveOut;animation-duration:.3s}@keyframes MessageMoveOut{0%{max-height:150px;padding:8px;opacity:1}to{max-height:0;padding:0;opacity:0}}.ant-message-rtl,.ant-message-rtl span{direction:rtl}.ant-message-rtl .anticon{margin-right:0;margin-left:8px}/*!**********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/modal/style/index.less ***! + \\**********************************************************************************************************************************************************************************************************************************************************/.ant-modal{box-sizing:border-box;padding:0 0 24px;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";pointer-events:none;position:relative;top:100px;width:auto;max-width:calc(100vw - 32px);margin:0 auto}.ant-modal.ant-zoom-enter,.ant-modal.antzoom-appear{transform:none;opacity:0;animation-duration:.3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-modal-mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;height:100%;background-color:#00000073}.ant-modal-mask-hidden{display:none}.ant-modal-wrap{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;outline:0;-webkit-overflow-scrolling:touch}.ant-modal-wrap{z-index:1000}.ant-modal-title{margin:0;color:#ffffffd9;font-weight:500;font-size:16px;line-height:22px;word-wrap:break-word}.ant-modal-content{position:relative;background-color:#1f1f1f;background-clip:padding-box;border:0;border-radius:2px;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003;pointer-events:auto}.ant-modal-close{position:absolute;top:0;right:0;z-index:10;padding:0;color:#ffffff73;font-weight:700;line-height:1;text-decoration:none;background:transparent;border:0;outline:0;cursor:pointer;transition:color .3s}.ant-modal-close-x{display:block;width:56px;height:56px;font-size:16px;font-style:normal;line-height:56px;text-align:center;text-transform:none;text-rendering:auto}.ant-modal-close:focus,.ant-modal-close:hover{color:#ffffffbf;text-decoration:none}.ant-modal-header{padding:16px 24px;color:#ffffffd9;background:#1f1f1f;border-bottom:1px solid #303030;border-radius:2px 2px 0 0}.ant-modal-body{padding:24px;font-size:14px;line-height:1.5715;word-wrap:break-word}.ant-modal-footer{padding:10px 16px;text-align:right;background:transparent;border-top:1px solid #303030;border-radius:0 0 2px 2px}.ant-modal-footer .ant-btn+.ant-btn:not(.ant-dropdown-trigger){margin-bottom:0;margin-left:8px}.ant-modal-open{overflow:hidden}.ant-modal-centered{text-align:center}.ant-modal-centered:before{display:inline-block;width:0;height:100%;vertical-align:middle;content:""}.ant-modal-centered .ant-modal{top:0;display:inline-block;padding-bottom:0;text-align:left;vertical-align:middle}@media (max-width: 767px){.ant-modal{max-width:calc(100vw - 16px);margin:8px auto}.ant-modal-centered .ant-modal{flex:1}}.ant-modal-confirm .ant-modal-header{display:none}.ant-modal-confirm .ant-modal-body{padding:32px 32px 24px}.ant-modal-confirm-body-wrapper:before{display:table;content:""}.ant-modal-confirm-body-wrapper:after{display:table;clear:both;content:""}.ant-modal-confirm-body .ant-modal-confirm-title{display:block;overflow:hidden;color:#ffffffd9;font-weight:500;font-size:16px;line-height:1.4}.ant-modal-confirm-body .ant-modal-confirm-content{margin-top:8px;color:#ffffffd9;font-size:14px}.ant-modal-confirm-body>.anticon{float:left;margin-right:16px;font-size:22px}.ant-modal-confirm-body>.anticon+.ant-modal-confirm-title+.ant-modal-confirm-content{margin-left:38px}.ant-modal-confirm .ant-modal-confirm-btns{float:right;margin-top:24px}.ant-modal-confirm .ant-modal-confirm-btns .ant-btn+.ant-btn{margin-bottom:0;margin-left:8px}.ant-modal-confirm-error .ant-modal-confirm-body>.anticon{color:#a61d24}.ant-modal-confirm-warning .ant-modal-confirm-body>.anticon,.ant-modal-confirm-confirm .ant-modal-confirm-body>.anticon{color:#d89614}.ant-modal-confirm-info .ant-modal-confirm-body>.anticon{color:#177ddc}.ant-modal-confirm-success .ant-modal-confirm-body>.anticon{color:#49aa19}.ant-modal-wrap-rtl{direction:rtl}.ant-modal-wrap-rtl .ant-modal-close{right:initial;left:0}.ant-modal-wrap-rtl .ant-modal-footer{text-align:left}.ant-modal-wrap-rtl .ant-modal-footer .ant-btn+.ant-btn{margin-right:8px;margin-left:0}.ant-modal-wrap-rtl .ant-modal-confirm-body{direction:rtl}.ant-modal-wrap-rtl .ant-modal-confirm-body>.anticon{float:right;margin-right:0;margin-left:16px}.ant-modal-wrap-rtl .ant-modal-confirm-body>.anticon+.ant-modal-confirm-title+.ant-modal-confirm-content{margin-right:38px;margin-left:0}.ant-modal-wrap-rtl .ant-modal-confirm-btns{float:left}.ant-modal-wrap-rtl .ant-modal-confirm-btns .ant-btn+.ant-btn{margin-right:8px;margin-left:0}.ant-modal-wrap-rtl.ant-modal-centered .ant-modal{text-align:right}.ant-modal .ant-picker-clear,.ant-modal .ant-slider-handle,.ant-modal .ant-anchor-wrapper,.ant-modal .ant-collapse-content,.ant-modal .ant-timeline-item-head,.ant-modal .ant-card{background-color:#1f1f1f}.ant-modal .ant-transfer-list-header{background:#1f1f1f;border-bottom:1px solid #3a3a3a}.ant-modal .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover{background-color:#ffffff14}.ant-modal tr.ant-table-expanded-row>td,.ant-modal tr.ant-table-expanded-row:hover>td{background:#272727}.ant-modal .ant-table.ant-table-small thead>tr>th{background-color:#1f1f1f;border-bottom:1px solid #3a3a3a}.ant-modal .ant-table{background-color:#1f1f1f}.ant-modal .ant-table .ant-table-row-expand-icon{border:1px solid #3a3a3a}.ant-modal .ant-table tfoot>tr>th,.ant-modal .ant-table tfoot>tr>td{border-bottom:1px solid #3a3a3a}.ant-modal .ant-table thead>tr>th{background-color:#272727;border-bottom:1px solid #3a3a3a}.ant-modal .ant-table tbody>tr>td{border-bottom:1px solid #3a3a3a}.ant-modal .ant-table tbody>tr>td.ant-table-cell-fix-left,.ant-modal .ant-table tbody>tr>td.ant-table-cell-fix-right{background-color:#1f1f1f}.ant-modal .ant-table tbody>tr.ant-table-row:hover>td{background:#303030}.ant-modal .ant-table.ant-table-bordered .ant-table-title{border:1px solid #3a3a3a}.ant-modal .ant-table.ant-table-bordered thead>tr>th,.ant-modal .ant-table.ant-table-bordered tbody>tr>td,.ant-modal .ant-table.ant-table-bordered tfoot>tr>th,.ant-modal .ant-table.ant-table-bordered tfoot>tr>td{border-right:1px solid #3a3a3a}.ant-modal .ant-table.ant-table-bordered .ant-table-cell-fix-right-first:after{border-right:1px solid #3a3a3a}.ant-modal .ant-table.ant-table-bordered table thead>tr:not(:last-child)>th{border-bottom:1px solid #303030}.ant-modal .ant-table.ant-table-bordered .ant-table-container{border:1px solid #3a3a3a}.ant-modal .ant-table.ant-table-bordered .ant-table-expanded-row-fixed:after{border-right:1px solid #3a3a3a}.ant-modal .ant-table.ant-table-bordered .ant-table-footer{border:1px solid #3a3a3a}.ant-modal .ant-table .ant-table-filter-trigger-container-open{background-color:#525252}.ant-modal .ant-picker-calendar-full,.ant-modal .ant-picker-calendar-full .ant-picker-panel{background-color:#1f1f1f}.ant-modal .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date{border-top:2px solid #3a3a3a}.ant-modal .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active{background-color:#1f1f1f;border-bottom:1px solid #1f1f1f}.ant-modal .ant-badge-count{box-shadow:0 0 0 1px #1f1f1f}.ant-modal .ant-tree-show-line .ant-tree-switcher{background:#1f1f1f}/*!*****************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/notification/style/index.less ***! + \\*****************************************************************************************************************************************************************************************************************************************************************/.ant-notification .ant-picker-clear,.ant-notification .ant-slider-handle,.ant-notification .ant-anchor-wrapper,.ant-notification .ant-collapse-content,.ant-notification .ant-timeline-item-head,.ant-notification .ant-card{background-color:#1f1f1f}.ant-notification .ant-transfer-list-header{background:#1f1f1f;border-bottom:1px solid #3a3a3a}.ant-notification .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover{background-color:#ffffff14}.ant-notification tr.ant-table-expanded-row>td,.ant-notification tr.ant-table-expanded-row:hover>td{background:#272727}.ant-notification .ant-table.ant-table-small thead>tr>th{background-color:#1f1f1f;border-bottom:1px solid #3a3a3a}.ant-notification .ant-table{background-color:#1f1f1f}.ant-notification .ant-table .ant-table-row-expand-icon{border:1px solid #3a3a3a}.ant-notification .ant-table tfoot>tr>th,.ant-notification .ant-table tfoot>tr>td{border-bottom:1px solid #3a3a3a}.ant-notification .ant-table thead>tr>th{background-color:#272727;border-bottom:1px solid #3a3a3a}.ant-notification .ant-table tbody>tr>td{border-bottom:1px solid #3a3a3a}.ant-notification .ant-table tbody>tr>td.ant-table-cell-fix-left,.ant-notification .ant-table tbody>tr>td.ant-table-cell-fix-right{background-color:#1f1f1f}.ant-notification .ant-table tbody>tr.ant-table-row:hover>td{background:#303030}.ant-notification .ant-table.ant-table-bordered .ant-table-title{border:1px solid #3a3a3a}.ant-notification .ant-table.ant-table-bordered thead>tr>th,.ant-notification .ant-table.ant-table-bordered tbody>tr>td,.ant-notification .ant-table.ant-table-bordered tfoot>tr>th,.ant-notification .ant-table.ant-table-bordered tfoot>tr>td{border-right:1px solid #3a3a3a}.ant-notification .ant-table.ant-table-bordered .ant-table-cell-fix-right-first:after{border-right:1px solid #3a3a3a}.ant-notification .ant-table.ant-table-bordered table thead>tr:not(:last-child)>th{border-bottom:1px solid #303030}.ant-notification .ant-table.ant-table-bordered .ant-table-container{border:1px solid #3a3a3a}.ant-notification .ant-table.ant-table-bordered .ant-table-expanded-row-fixed:after{border-right:1px solid #3a3a3a}.ant-notification .ant-table.ant-table-bordered .ant-table-footer{border:1px solid #3a3a3a}.ant-notification .ant-table .ant-table-filter-trigger-container-open{background-color:#525252}.ant-notification .ant-picker-calendar-full,.ant-notification .ant-picker-calendar-full .ant-picker-panel{background-color:#1f1f1f}.ant-notification .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date{border-top:2px solid #3a3a3a}.ant-notification .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active{background-color:#1f1f1f;border-bottom:1px solid #1f1f1f}.ant-notification .ant-badge-count{box-shadow:0 0 0 1px #1f1f1f}.ant-notification .ant-tree-show-line .ant-tree-switcher{background:#1f1f1f}.ant-notification{box-sizing:border-box;margin:0 24px 0 0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:fixed;z-index:1010}.ant-notification-topLeft,.ant-notification-bottomLeft{margin-right:0;margin-left:24px}.ant-notification-topLeft .ant-notification-fade-enter.ant-notification-fade-enter-active,.ant-notification-bottomLeft .ant-notification-fade-enter.ant-notification-fade-enter-active,.ant-notification-topLeft .ant-notification-fade-appear.ant-notification-fade-appear-active,.ant-notification-bottomLeft .ant-notification-fade-appear.ant-notification-fade-appear-active{animation-name:NotificationLeftFadeIn}.ant-notification-close-icon{font-size:14px;cursor:pointer}.ant-notification-hook-holder{position:relative}.ant-notification-notice{position:relative;width:384px;max-width:calc(100vw - 48px);margin-bottom:16px;margin-left:auto;padding:16px 24px;overflow:hidden;line-height:1.5715;word-wrap:break-word;background:#1f1f1f;border-radius:2px;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003}.ant-notification-topLeft .ant-notification-notice,.ant-notification-bottomLeft .ant-notification-notice{margin-right:auto;margin-left:0}.ant-notification-notice-message{margin-bottom:8px;color:#ffffffd9;font-size:16px;line-height:24px}.ant-notification-notice-message-single-line-auto-margin{display:block;width:calc(264px - 100%);max-width:4px;background-color:transparent;pointer-events:none}.ant-notification-notice-message-single-line-auto-margin:before{display:block;content:""}.ant-notification-notice-description{font-size:14px}.ant-notification-notice-closable .ant-notification-notice-message{padding-right:24px}.ant-notification-notice-with-icon .ant-notification-notice-message{margin-bottom:4px;margin-left:48px;font-size:16px}.ant-notification-notice-with-icon .ant-notification-notice-description{margin-left:48px;font-size:14px}.ant-notification-notice-icon{position:absolute;margin-left:4px;font-size:24px;line-height:24px}.anticon.ant-notification-notice-icon-success{color:#49aa19}.anticon.ant-notification-notice-icon-info{color:#177ddc}.anticon.ant-notification-notice-icon-warning{color:#d89614}.anticon.ant-notification-notice-icon-error{color:#a61d24}.ant-notification-notice-close{position:absolute;top:16px;right:22px;color:#ffffff73;outline:none}.ant-notification-notice-close:hover{color:#ffffffd9}.ant-notification-notice-btn{float:right;margin-top:16px}.ant-notification .notification-fade-effect{animation-duration:.24s;animation-timing-function:cubic-bezier(.645,.045,.355,1);animation-fill-mode:both}.ant-notification-fade-enter,.ant-notification-fade-appear{animation-duration:.24s;animation-timing-function:cubic-bezier(.645,.045,.355,1);animation-fill-mode:both;opacity:0;animation-play-state:paused}.ant-notification-fade-leave{animation-duration:.24s;animation-timing-function:cubic-bezier(.645,.045,.355,1);animation-fill-mode:both;animation-duration:.2s;animation-play-state:paused}.ant-notification-fade-enter.ant-notification-fade-enter-active,.ant-notification-fade-appear.ant-notification-fade-appear-active{animation-name:NotificationFadeIn;animation-play-state:running}.ant-notification-fade-leave.ant-notification-fade-leave-active{animation-name:NotificationFadeOut;animation-play-state:running}@keyframes NotificationFadeIn{0%{left:384px;opacity:0}to{left:0;opacity:1}}@keyframes NotificationLeftFadeIn{0%{right:384px;opacity:0}to{right:0;opacity:1}}@keyframes NotificationFadeOut{0%{max-height:150px;margin-bottom:16px;opacity:1}to{max-height:0;margin-bottom:0;padding-top:0;padding-bottom:0;opacity:0}}.ant-notification-rtl{direction:rtl}.ant-notification-rtl .ant-notification-notice-closable .ant-notification-notice-message{padding-right:0;padding-left:24px}.ant-notification-rtl .ant-notification-notice-with-icon .ant-notification-notice-message,.ant-notification-rtl .ant-notification-notice-with-icon .ant-notification-notice-description{margin-right:48px;margin-left:0}.ant-notification-rtl .ant-notification-notice-icon{margin-right:4px;margin-left:0}.ant-notification-rtl .ant-notification-notice-close{right:auto;left:22px}.ant-notification-rtl .ant-notification-notice-btn{float:left}/*!****************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/page-header/style/index.less ***! + \\****************************************************************************************************************************************************************************************************************************************************************/.ant-page-header{box-sizing:border-box;margin:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;padding:16px 24px;background-color:#141414}.ant-page-header-ghost{background-color:transparent}.ant-page-header.has-breadcrumb{padding-top:12px}.ant-page-header.has-footer{padding-bottom:0}.ant-page-header-back{margin-right:16px;font-size:16px;line-height:1}.ant-page-header-back-button{color:#177ddc;text-decoration:none;outline:none;transition:color .3s;color:inherit;cursor:pointer}.ant-page-header-back-button:focus,.ant-page-header-back-button:hover{color:#165996}.ant-page-header-back-button:active{color:#388ed3}.ant-page-header .ant-divider-vertical{height:14px;margin:0 12px;vertical-align:middle}.ant-breadcrumb+.ant-page-header-heading{margin-top:8px}.ant-page-header-heading{display:flex;justify-content:space-between}.ant-page-header-heading-left{display:flex;align-items:center;margin:4px 0;overflow:hidden}.ant-page-header-heading-title{margin-right:12px;margin-bottom:0;color:#ffffffd9;font-weight:600;font-size:20px;line-height:32px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-page-header-heading .ant-avatar{margin-right:12px}.ant-page-header-heading-sub-title{margin-right:12px;color:#ffffff73;font-size:14px;line-height:1.5715;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-page-header-heading-extra{margin:4px 0;white-space:nowrap}.ant-page-header-heading-extra>*{margin-left:12px;white-space:unset}.ant-page-header-heading-extra>*:first-child{margin-left:0}.ant-page-header-content{padding-top:12px}.ant-page-header-footer{margin-top:16px}.ant-page-header-footer .ant-tabs>.ant-tabs-nav{margin:0}.ant-page-header-footer .ant-tabs>.ant-tabs-nav:before{border:none}.ant-page-header-footer .ant-tabs .ant-tabs-tab{padding-top:8px;padding-bottom:8px;font-size:16px}.ant-page-header-compact .ant-page-header-heading{flex-wrap:wrap}.ant-page-header-rtl{direction:rtl}.ant-page-header-rtl .ant-page-header-back{float:right;margin-right:0;margin-left:16px}.ant-page-header-rtl .ant-page-header-heading-title,.ant-page-header-rtl .ant-page-header-heading .ant-avatar{margin-right:0;margin-left:12px}.ant-page-header-rtl .ant-page-header-heading-sub-title{float:right;margin-right:0;margin-left:12px}.ant-page-header-rtl .ant-page-header-heading-tags{float:right}.ant-page-header-rtl .ant-page-header-heading-extra{float:left}.ant-page-header-rtl .ant-page-header-heading-extra>*{margin-right:12px;margin-left:0}.ant-page-header-rtl .ant-page-header-heading-extra>*:first-child{margin-right:0}.ant-page-header-rtl .ant-page-header-footer .ant-tabs-bar .ant-tabs-nav{float:right}/*!************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/popover/style/index.less ***! + \\************************************************************************************************************************************************************************************************************************************************************/.ant-popover{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:0;left:0;z-index:1030;font-weight:400;white-space:normal;text-align:left;cursor:auto;-webkit-user-select:text;-moz-user-select:text;user-select:text}.ant-popover:after{position:absolute;background:rgba(255,255,255,.01);content:""}.ant-popover-hidden{display:none}.ant-popover-placement-top,.ant-popover-placement-topLeft,.ant-popover-placement-topRight{padding-bottom:10px}.ant-popover-placement-right,.ant-popover-placement-rightTop,.ant-popover-placement-rightBottom{padding-left:10px}.ant-popover-placement-bottom,.ant-popover-placement-bottomLeft,.ant-popover-placement-bottomRight{padding-top:10px}.ant-popover-placement-left,.ant-popover-placement-leftTop,.ant-popover-placement-leftBottom{padding-right:10px}.ant-popover-inner{background-color:#1f1f1f;background-clip:padding-box;border-radius:2px;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003;box-shadow:0 0 8px #00000073 \\ }@media screen and (-ms-high-contrast: active),(-ms-high-contrast: none){.ant-popover-inner{box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003}}.ant-popover-title{min-width:177px;min-height:32px;margin:0;padding:5px 16px 4px;color:#ffffffd9;font-weight:500;border-bottom:1px solid #303030}.ant-popover-inner-content{padding:12px 16px;color:#ffffffd9}.ant-popover-message{position:relative;padding:4px 0 12px;color:#ffffffd9;font-size:14px}.ant-popover-message>.anticon{position:absolute;top:8.0005px;color:#d89614;font-size:14px}.ant-popover-message-title{padding-left:22px}.ant-popover-buttons{margin-bottom:4px;text-align:right}.ant-popover-buttons button{margin-left:8px}.ant-popover-arrow{position:absolute;display:block;width:8.48528137px;height:8.48528137px;overflow:hidden;background:transparent;pointer-events:none}.ant-popover-arrow-content{position:absolute;top:0;right:0;bottom:0;left:0;display:block;width:6px;height:6px;margin:auto;background-color:#1f1f1f;content:"";pointer-events:auto}.ant-popover-placement-top .ant-popover-arrow,.ant-popover-placement-topLeft .ant-popover-arrow,.ant-popover-placement-topRight .ant-popover-arrow{bottom:1.51471863px}.ant-popover-placement-top .ant-popover-arrow-content,.ant-popover-placement-topLeft .ant-popover-arrow-content,.ant-popover-placement-topRight .ant-popover-arrow-content{box-shadow:3px 3px 7px #00000012;transform:translateY(-4.24264069px) rotate(45deg)}.ant-popover-placement-top .ant-popover-arrow{left:50%;transform:translate(-50%)}.ant-popover-placement-topLeft .ant-popover-arrow{left:16px}.ant-popover-placement-topRight .ant-popover-arrow{right:16px}.ant-popover-placement-right .ant-popover-arrow,.ant-popover-placement-rightTop .ant-popover-arrow,.ant-popover-placement-rightBottom .ant-popover-arrow{left:1.51471863px}.ant-popover-placement-right .ant-popover-arrow-content,.ant-popover-placement-rightTop .ant-popover-arrow-content,.ant-popover-placement-rightBottom .ant-popover-arrow-content{box-shadow:-3px 3px 7px #00000012;transform:translate(4.24264069px) rotate(45deg)}.ant-popover-placement-right .ant-popover-arrow{top:50%;transform:translateY(-50%)}.ant-popover-placement-rightTop .ant-popover-arrow{top:12px}.ant-popover-placement-rightBottom .ant-popover-arrow{bottom:12px}.ant-popover-placement-bottom .ant-popover-arrow,.ant-popover-placement-bottomLeft .ant-popover-arrow,.ant-popover-placement-bottomRight .ant-popover-arrow{top:1.51471863px}.ant-popover-placement-bottom .ant-popover-arrow-content,.ant-popover-placement-bottomLeft .ant-popover-arrow-content,.ant-popover-placement-bottomRight .ant-popover-arrow-content{box-shadow:-2px -2px 5px #0000000f;transform:translateY(4.24264069px) rotate(45deg)}.ant-popover-placement-bottom .ant-popover-arrow{left:50%;transform:translate(-50%)}.ant-popover-placement-bottomLeft .ant-popover-arrow{left:16px}.ant-popover-placement-bottomRight .ant-popover-arrow{right:16px}.ant-popover-placement-left .ant-popover-arrow,.ant-popover-placement-leftTop .ant-popover-arrow,.ant-popover-placement-leftBottom .ant-popover-arrow{right:1.51471863px}.ant-popover-placement-left .ant-popover-arrow-content,.ant-popover-placement-leftTop .ant-popover-arrow-content,.ant-popover-placement-leftBottom .ant-popover-arrow-content{box-shadow:3px -3px 7px #00000012;transform:translate(-4.24264069px) rotate(45deg)}.ant-popover-placement-left .ant-popover-arrow{top:50%;transform:translateY(-50%)}.ant-popover-placement-leftTop .ant-popover-arrow{top:12px}.ant-popover-placement-leftBottom .ant-popover-arrow{bottom:12px}.ant-popover-pink .ant-popover-inner,.ant-popover-pink .ant-popover-arrow-content,.ant-popover-magenta .ant-popover-inner,.ant-popover-magenta .ant-popover-arrow-content{background-color:#cb2b83}.ant-popover-red .ant-popover-inner,.ant-popover-red .ant-popover-arrow-content{background-color:#d32029}.ant-popover-volcano .ant-popover-inner,.ant-popover-volcano .ant-popover-arrow-content{background-color:#d84a1b}.ant-popover-orange .ant-popover-inner,.ant-popover-orange .ant-popover-arrow-content{background-color:#d87a16}.ant-popover-yellow .ant-popover-inner,.ant-popover-yellow .ant-popover-arrow-content{background-color:#d8bd14}.ant-popover-gold .ant-popover-inner,.ant-popover-gold .ant-popover-arrow-content{background-color:#d89614}.ant-popover-cyan .ant-popover-inner,.ant-popover-cyan .ant-popover-arrow-content{background-color:#13a8a8}.ant-popover-lime .ant-popover-inner,.ant-popover-lime .ant-popover-arrow-content{background-color:#8bbb11}.ant-popover-green .ant-popover-inner,.ant-popover-green .ant-popover-arrow-content{background-color:#49aa19}.ant-popover-blue .ant-popover-inner,.ant-popover-blue .ant-popover-arrow-content{background-color:#177ddc}.ant-popover-geekblue .ant-popover-inner,.ant-popover-geekblue .ant-popover-arrow-content{background-color:#2b4acb}.ant-popover-purple .ant-popover-inner,.ant-popover-purple .ant-popover-arrow-content{background-color:#642ab5}.ant-popover-rtl{direction:rtl;text-align:right}.ant-popover-rtl .ant-popover-message-title{padding-right:22px;padding-left:16px}.ant-popover-rtl .ant-popover-buttons{text-align:left}.ant-popover-rtl .ant-popover-buttons button{margin-right:8px;margin-left:0}/*!***************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/popconfirm/style/index.less ***! + \\***************************************************************************************************************************************************************************************************************************************************************/.ant-popconfirm{z-index:1060}/*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/progress/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-progress{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-progress-line{position:relative;width:100%;font-size:14px}.ant-progress-steps{display:inline-block}.ant-progress-steps-outer{display:flex;flex-direction:row;align-items:center}.ant-progress-steps-item{flex-shrink:0;min-width:2px;margin-right:2px;background:rgba(255,255,255,.08);transition:all .3s}.ant-progress-steps-item-active{background:#177ddc}.ant-progress-small.ant-progress-line,.ant-progress-small.ant-progress-line .ant-progress-text .anticon{font-size:12px}.ant-progress-outer{display:inline-block;width:100%;margin-right:0;padding-right:0}.ant-progress-show-info .ant-progress-outer{margin-right:calc(-2em - 8px);padding-right:calc(2em + 8px)}.ant-progress-inner{position:relative;display:inline-block;width:100%;overflow:hidden;vertical-align:middle;background-color:#ffffff14;border-radius:100px}.ant-progress-circle-trail{stroke:#ffffff14}.ant-progress-circle-path{animation:ant-progress-appear .3s}.ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#177ddc}.ant-progress-success-bg,.ant-progress-bg{position:relative;background-color:#177ddc;border-radius:100px;transition:all .4s cubic-bezier(.08,.82,.17,1) 0s}.ant-progress-success-bg{position:absolute;top:0;left:0;background-color:#49aa19}.ant-progress-text{display:inline-block;width:2em;margin-left:8px;color:#ffffffd9;font-size:1em;line-height:1;white-space:nowrap;text-align:left;vertical-align:middle;word-break:normal}.ant-progress-text .anticon{font-size:14px}.ant-progress-status-active .ant-progress-bg:before{position:absolute;top:0;right:0;bottom:0;left:0;background:#141414;border-radius:10px;opacity:0;animation:ant-progress-active 2.4s cubic-bezier(.23,1,.32,1) infinite;content:""}.ant-progress-status-exception .ant-progress-bg{background-color:#a61d24}.ant-progress-status-exception .ant-progress-text{color:#a61d24}.ant-progress-status-exception .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#a61d24}.ant-progress-status-success .ant-progress-bg{background-color:#49aa19}.ant-progress-status-success .ant-progress-text{color:#49aa19}.ant-progress-status-success .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#49aa19}.ant-progress-circle .ant-progress-inner{position:relative;line-height:1;background-color:transparent}.ant-progress-circle .ant-progress-text{position:absolute;top:50%;left:50%;width:100%;margin:0;padding:0;color:#ffffffd9;font-size:1em;line-height:1;white-space:normal;text-align:center;transform:translate(-50%,-50%)}.ant-progress-circle .ant-progress-text .anticon{font-size:1.16666667em}.ant-progress-circle.ant-progress-status-exception .ant-progress-text{color:#a61d24}.ant-progress-circle.ant-progress-status-success .ant-progress-text{color:#49aa19}@keyframes ant-progress-active{0%{transform:translate(-100%) scaleX(0);opacity:.1}20%{transform:translate(-100%) scaleX(0);opacity:.5}to{transform:translate(0) scaleX(1);opacity:0}}.ant-progress-rtl{direction:rtl}.ant-progress-rtl.ant-progress-show-info .ant-progress-outer{margin-right:0;margin-left:calc(-2em - 8px);padding-right:0;padding-left:calc(2em + 8px)}.ant-progress-rtl .ant-progress-success-bg{right:0;left:auto}.ant-progress-rtl.ant-progress-line .ant-progress-text,.ant-progress-rtl.ant-progress-steps .ant-progress-text{margin-right:8px;margin-left:0;text-align:right}/*!*********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/rate/style/index.less ***! + \\*********************************************************************************************************************************************************************************************************************************************************/.ant-rate{box-sizing:border-box;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;font-feature-settings:"tnum";display:inline-block;margin:0;padding:0;color:#d8bd14;font-size:20px;line-height:unset;list-style:none;outline:none}.ant-rate-disabled .ant-rate-star{cursor:default}.ant-rate-disabled .ant-rate-star:hover{transform:scale(1)}.ant-rate-star{position:relative;display:inline-block;color:inherit;cursor:pointer}.ant-rate-star:not(:last-child){margin-right:8px}.ant-rate-star>div{transition:all .3s,outline 0s}.ant-rate-star>div:hover{transform:scale(1.1)}.ant-rate-star>div:focus{outline:0}.ant-rate-star>div:focus-visible{outline:1px dashed #d8bd14;transform:scale(1.1)}.ant-rate-star-first,.ant-rate-star-second{color:#ffffff1f;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-rate-star-first .anticon,.ant-rate-star-second .anticon{vertical-align:middle}.ant-rate-star-first{position:absolute;top:0;left:0;width:50%;height:100%;overflow:hidden;opacity:0}.ant-rate-star-half .ant-rate-star-first,.ant-rate-star-half .ant-rate-star-second{opacity:1}.ant-rate-star-half .ant-rate-star-first,.ant-rate-star-full .ant-rate-star-second{color:inherit}.ant-rate-text{display:inline-block;margin:0 8px;font-size:14px}.ant-rate-rtl{direction:rtl}.ant-rate-rtl .ant-rate-star:not(:last-child){margin-right:0;margin-left:8px}.ant-rate-rtl .ant-rate-star-first{right:0;left:auto}/*!***********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/result/style/index.less ***! + \\***********************************************************************************************************************************************************************************************************************************************************/.ant-result{padding:48px 32px}.ant-result-success .ant-result-icon>.anticon{color:#49aa19}.ant-result-error .ant-result-icon>.anticon{color:#a61d24}.ant-result-info .ant-result-icon>.anticon{color:#177ddc}.ant-result-warning .ant-result-icon>.anticon{color:#d89614}.ant-result-image{width:250px;height:295px;margin:auto}.ant-result-icon{margin-bottom:24px;text-align:center}.ant-result-icon>.anticon{font-size:72px}.ant-result-title{color:#ffffffd9;font-size:24px;line-height:1.8;text-align:center}.ant-result-subtitle{color:#ffffff73;font-size:14px;line-height:1.6;text-align:center}.ant-result-extra{margin:24px 0 0;text-align:center}.ant-result-extra>*{margin-right:8px}.ant-result-extra>*:last-child{margin-right:0}.ant-result-content{margin-top:24px;padding:24px 40px;background-color:#ffffff0a}.ant-result-rtl{direction:rtl}.ant-result-rtl .ant-result-extra>*{margin-right:0;margin-left:8px}.ant-result-rtl .ant-result-extra>*:last-child{margin-left:0}/*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/skeleton/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-skeleton{display:table;width:100%}.ant-skeleton-header{display:table-cell;padding-right:16px;vertical-align:top}.ant-skeleton-header .ant-skeleton-avatar{display:inline-block;vertical-align:top;background:rgba(190,190,190,.2);width:32px;height:32px;line-height:32px}.ant-skeleton-header .ant-skeleton-avatar.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-header .ant-skeleton-avatar-lg{width:40px;height:40px;line-height:40px}.ant-skeleton-header .ant-skeleton-avatar-lg.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-header .ant-skeleton-avatar-sm{width:24px;height:24px;line-height:24px}.ant-skeleton-header .ant-skeleton-avatar-sm.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-content{display:table-cell;width:100%;vertical-align:top}.ant-skeleton-content .ant-skeleton-title{width:100%;height:16px;margin-top:16px;background:rgba(190,190,190,.2);border-radius:4px}.ant-skeleton-content .ant-skeleton-title+.ant-skeleton-paragraph{margin-top:24px}.ant-skeleton-content .ant-skeleton-paragraph{padding:0}.ant-skeleton-content .ant-skeleton-paragraph>li{width:100%;height:16px;list-style:none;background:rgba(190,190,190,.2);border-radius:4px}.ant-skeleton-content .ant-skeleton-paragraph>li:last-child:not(:first-child):not(:nth-child(2)){width:61%}.ant-skeleton-content .ant-skeleton-paragraph>li+li{margin-top:16px}.ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title{margin-top:12px}.ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title+.ant-skeleton-paragraph{margin-top:28px}.ant-skeleton-round .ant-skeleton-content .ant-skeleton-title,.ant-skeleton-round .ant-skeleton-content .ant-skeleton-paragraph>li{border-radius:100px}.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-title,.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-paragraph>li{background:linear-gradient(90deg,rgba(190,190,190,.2) 25%,rgba(255,255,255,.16) 37%,rgba(190,190,190,.2) 63%);background-size:400% 100%;animation:ant-skeleton-loading 1.4s ease infinite}.ant-skeleton.ant-skeleton-active .ant-skeleton-avatar,.ant-skeleton.ant-skeleton-active .ant-skeleton-button,.ant-skeleton.ant-skeleton-active .ant-skeleton-input,.ant-skeleton.ant-skeleton-active .ant-skeleton-image{background:linear-gradient(90deg,rgba(190,190,190,.2) 25%,rgba(255,255,255,.16) 37%,rgba(190,190,190,.2) 63%);background-size:400% 100%;animation:ant-skeleton-loading 1.4s ease infinite}.ant-skeleton.ant-skeleton-block,.ant-skeleton.ant-skeleton-block .ant-skeleton-button{width:100%}.ant-skeleton-element{display:inline-block;width:auto}.ant-skeleton-element .ant-skeleton-button{display:inline-block;vertical-align:top;background:rgba(190,190,190,.2);border-radius:2px;width:64px;min-width:64px;height:32px;line-height:32px}.ant-skeleton-element .ant-skeleton-button.ant-skeleton-button-circle{width:32px;min-width:32px;border-radius:50%}.ant-skeleton-element .ant-skeleton-button.ant-skeleton-button-round{border-radius:32px}.ant-skeleton-element .ant-skeleton-button-lg{width:80px;min-width:80px;height:40px;line-height:40px}.ant-skeleton-element .ant-skeleton-button-lg.ant-skeleton-button-circle{width:40px;min-width:40px;border-radius:50%}.ant-skeleton-element .ant-skeleton-button-lg.ant-skeleton-button-round{border-radius:40px}.ant-skeleton-element .ant-skeleton-button-sm{width:48px;min-width:48px;height:24px;line-height:24px}.ant-skeleton-element .ant-skeleton-button-sm.ant-skeleton-button-circle{width:24px;min-width:24px;border-radius:50%}.ant-skeleton-element .ant-skeleton-button-sm.ant-skeleton-button-round{border-radius:24px}.ant-skeleton-element .ant-skeleton-avatar{display:inline-block;vertical-align:top;background:rgba(190,190,190,.2);width:32px;height:32px;line-height:32px}.ant-skeleton-element .ant-skeleton-avatar.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-element .ant-skeleton-avatar-lg{width:40px;height:40px;line-height:40px}.ant-skeleton-element .ant-skeleton-avatar-lg.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-element .ant-skeleton-avatar-sm{width:24px;height:24px;line-height:24px}.ant-skeleton-element .ant-skeleton-avatar-sm.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-element .ant-skeleton-input{display:inline-block;vertical-align:top;background:rgba(190,190,190,.2);width:100%;height:32px;line-height:32px}.ant-skeleton-element .ant-skeleton-input-lg{width:100%;height:40px;line-height:40px}.ant-skeleton-element .ant-skeleton-input-sm{width:100%;height:24px;line-height:24px}.ant-skeleton-element .ant-skeleton-image{display:flex;align-items:center;justify-content:center;vertical-align:top;background:rgba(190,190,190,.2);width:96px;height:96px;line-height:96px}.ant-skeleton-element .ant-skeleton-image.ant-skeleton-image-circle{border-radius:50%}.ant-skeleton-element .ant-skeleton-image-path{fill:#bfbfbf}.ant-skeleton-element .ant-skeleton-image-svg{width:48px;height:48px;line-height:48px;max-width:192px;max-height:192px}.ant-skeleton-element .ant-skeleton-image-svg.ant-skeleton-image-circle{border-radius:50%}@keyframes ant-skeleton-loading{0%{background-position:100% 50%}to{background-position:0 50%}}.ant-skeleton-rtl{direction:rtl}.ant-skeleton-rtl .ant-skeleton-header{padding-right:0;padding-left:16px}.ant-skeleton-rtl.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-title,.ant-skeleton-rtl.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-paragraph>li{animation-name:ant-skeleton-loading-rtl}.ant-skeleton-rtl.ant-skeleton.ant-skeleton-active .ant-skeleton-avatar{animation-name:ant-skeleton-loading-rtl}@keyframes ant-skeleton-loading-rtl{0%{background-position:0% 50%}to{background-position:100% 50%}}/*!***********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/slider/style/index.less ***! + \\***********************************************************************************************************************************************************************************************************************************************************/.ant-slider{box-sizing:border-box;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;height:12px;margin:10px 6px;padding:4px 0;cursor:pointer;touch-action:none}.ant-slider-vertical{width:12px;height:100%;margin:6px 10px;padding:0 4px}.ant-slider-vertical .ant-slider-rail{width:4px;height:100%}.ant-slider-vertical .ant-slider-track{width:4px}.ant-slider-vertical .ant-slider-handle{margin-top:-6px;margin-left:-5px}.ant-slider-vertical .ant-slider-mark{top:0;left:12px;width:18px;height:100%}.ant-slider-vertical .ant-slider-mark-text{left:4px;white-space:nowrap}.ant-slider-vertical .ant-slider-step{width:4px;height:100%}.ant-slider-vertical .ant-slider-dot{top:auto;left:2px;margin-bottom:-4px}.ant-slider-tooltip .ant-tooltip-inner{min-width:unset}.ant-slider-rtl.ant-slider-vertical .ant-slider-handle{margin-right:-5px;margin-left:0}.ant-slider-rtl.ant-slider-vertical .ant-slider-mark{right:12px;left:auto}.ant-slider-rtl.ant-slider-vertical .ant-slider-mark-text{right:4px;left:auto}.ant-slider-rtl.ant-slider-vertical .ant-slider-dot{right:2px;left:auto}.ant-slider-with-marks{margin-bottom:28px}.ant-slider-rail{position:absolute;width:100%;height:4px;background-color:#262626;border-radius:2px;transition:background-color .3s}.ant-slider-track{position:absolute;height:4px;background-color:#153450;border-radius:2px;transition:background-color .3s}.ant-slider-handle{position:absolute;width:14px;height:14px;margin-top:-5px;background-color:#141414;border:solid 2px #153450;border-radius:50%;box-shadow:0;cursor:pointer;transition:border-color .3s,box-shadow .6s,transform .3s cubic-bezier(.18,.89,.32,1.28)}.ant-slider-handle-dragging.ant-slider-handle-dragging.ant-slider-handle-dragging{border-color:#4697e3;box-shadow:0 0 0 5px #177ddc1f}.ant-slider-handle:focus{border-color:#4697e3;outline:none;box-shadow:0 0 0 5px #177ddc1f}.ant-slider-handle.ant-tooltip-open{border-color:#177ddc}.ant-slider:hover .ant-slider-rail{background-color:#434343}.ant-slider:hover .ant-slider-track{background-color:#16436e}.ant-slider:hover .ant-slider-handle:not(.ant-tooltip-open){border-color:#16436e}.ant-slider-mark{position:absolute;top:14px;left:0;width:100%;font-size:14px}.ant-slider-mark-text{position:absolute;display:inline-block;color:#ffffff73;text-align:center;word-break:keep-all;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-slider-mark-text-active{color:#ffffffd9}.ant-slider-step{position:absolute;width:100%;height:4px;background:transparent}.ant-slider-dot{position:absolute;top:-2px;width:8px;height:8px;margin-left:-4px;background-color:#141414;border:2px solid #303030;border-radius:50%;cursor:pointer}.ant-slider-dot:first-child{margin-left:-4px}.ant-slider-dot:last-child{margin-left:-4px}.ant-slider-dot-active{border-color:#16436e}.ant-slider-disabled{cursor:not-allowed}.ant-slider-disabled .ant-slider-rail{background-color:#262626!important}.ant-slider-disabled .ant-slider-track{background-color:#ffffff4d!important}.ant-slider-disabled .ant-slider-handle,.ant-slider-disabled .ant-slider-dot{background-color:#141414;border-color:#ffffff4d!important;box-shadow:none;cursor:not-allowed}.ant-slider-disabled .ant-slider-mark-text,.ant-slider-disabled .ant-slider-dot{cursor:not-allowed!important}.ant-slider-rtl{direction:rtl}.ant-slider-rtl .ant-slider-mark{right:0;left:auto}.ant-slider-rtl .ant-slider-dot,.ant-slider-rtl .ant-slider-dot:first-child{margin-right:-4px;margin-left:0}.ant-slider-rtl .ant-slider-dot:last-child{margin-right:-4px;margin-left:0}/*!**********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/space/style/index.less ***! + \\**********************************************************************************************************************************************************************************************************************************************************/.ant-space{display:inline-flex}.ant-space-vertical{flex-direction:column}.ant-space-align-center{align-items:center}.ant-space-align-start{align-items:flex-start}.ant-space-align-end{align-items:flex-end}.ant-space-align-baseline{align-items:baseline}.ant-space-item:empty{display:none}.ant-space-rtl{direction:rtl}/*!**************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/statistic/style/index.less ***! + \\**************************************************************************************************************************************************************************************************************************************************************/.ant-statistic{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum"}.ant-statistic-title{margin-bottom:4px;color:#ffffff73;font-size:14px}.ant-statistic-content{color:#ffffffd9;font-size:24px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.ant-statistic-content-value{display:inline-block;direction:ltr}.ant-statistic-content-prefix,.ant-statistic-content-suffix{display:inline-block}.ant-statistic-content-prefix{margin-right:4px}.ant-statistic-content-suffix{margin-left:4px}.ant-statistic-rtl{direction:rtl}.ant-statistic-rtl .ant-statistic-content-prefix{margin-right:0;margin-left:4px}.ant-statistic-rtl .ant-statistic-content-suffix{margin-right:4px;margin-left:0}/*!**********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/steps/style/index.less ***! + \\**********************************************************************************************************************************************************************************************************************************************************/.ant-steps{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:flex;width:100%;font-size:0;text-align:initial}.ant-steps-item{position:relative;display:inline-block;flex:1;overflow:hidden;vertical-align:top}.ant-steps-item-container{outline:none}.ant-steps-item:last-child{flex:none}.ant-steps-item:last-child>.ant-steps-item-container>.ant-steps-item-tail,.ant-steps-item:last-child>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{display:none}.ant-steps-item-icon,.ant-steps-item-content{display:inline-block;vertical-align:top}.ant-steps-item-icon{width:32px;height:32px;margin:0 8px 0 0;font-size:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:32px;text-align:center;border:1px solid rgba(255,255,255,.3);border-radius:32px;transition:background-color .3s,border-color .3s}.ant-steps-item-icon .ant-steps-icon{position:relative;top:-.5px;color:#177ddc;line-height:1}.ant-steps-item-tail{position:absolute;top:12px;left:0;width:100%;padding:0 10px}.ant-steps-item-tail:after{display:inline-block;width:100%;height:1px;background:#303030;border-radius:1px;transition:background .3s;content:""}.ant-steps-item-title{position:relative;display:inline-block;padding-right:16px;color:#ffffffd9;font-size:16px;line-height:32px}.ant-steps-item-title:after{position:absolute;top:16px;left:100%;display:block;width:9999px;height:1px;background:#303030;content:""}.ant-steps-item-subtitle{display:inline;margin-left:8px;color:#ffffff73;font-weight:400;font-size:14px}.ant-steps-item-description{color:#ffffff73;font-size:14px}.ant-steps-item-wait .ant-steps-item-icon{background-color:transparent;border-color:#ffffff4d}.ant-steps-item-wait .ant-steps-item-icon>.ant-steps-icon{color:#ffffff4d}.ant-steps-item-wait .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:rgba(255,255,255,.3)}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:#ffffff73}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#303030}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:#ffffff73}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#303030}.ant-steps-item-process .ant-steps-item-icon{background-color:transparent;border-color:#177ddc}.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon{color:#177ddc}.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#177ddc}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:#ffffffd9}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#303030}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:#ffffffd9}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#303030}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-icon{background:#177ddc}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-icon .ant-steps-icon{color:#fff}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-title{font-weight:500}.ant-steps-item-finish .ant-steps-item-icon{background-color:transparent;border-color:#177ddc}.ant-steps-item-finish .ant-steps-item-icon>.ant-steps-icon{color:#177ddc}.ant-steps-item-finish .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#177ddc}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:#ffffffd9}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#177ddc}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:#ffffff73}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#177ddc}.ant-steps-item-error .ant-steps-item-icon{background-color:transparent;border-color:#a61d24}.ant-steps-item-error .ant-steps-item-icon>.ant-steps-icon{color:#a61d24}.ant-steps-item-error .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#a61d24}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:#a61d24}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#303030}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:#a61d24}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#303030}.ant-steps-item.ant-steps-next-error .ant-steps-item-title:after{background:#a61d24}.ant-steps-item-disabled{cursor:not-allowed}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]{cursor:pointer}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-title,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-subtitle,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-description,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-icon .ant-steps-icon{transition:color .3s}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-title,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-subtitle,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-description{color:#177ddc}.ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process)>.ant-steps-item-container[role=button]:hover .ant-steps-item-icon{border-color:#177ddc}.ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process)>.ant-steps-item-container[role=button]:hover .ant-steps-item-icon .ant-steps-icon{color:#177ddc}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{padding-left:16px;white-space:nowrap}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child{padding-left:0}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child .ant-steps-item-title{padding-right:0}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-tail{display:none}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-description{max-width:140px;white-space:normal}.ant-steps-item-custom>.ant-steps-item-container>.ant-steps-item-icon{height:auto;background:none;border:0}.ant-steps-item-custom>.ant-steps-item-container>.ant-steps-item-icon>.ant-steps-icon{top:0px;left:.5px;width:32px;height:32px;font-size:24px;line-height:32px}.ant-steps-item-custom.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon{color:#177ddc}.ant-steps:not(.ant-steps-vertical) .ant-steps-item-custom .ant-steps-item-icon{width:auto;background:none}.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{padding-left:12px}.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child{padding-left:0}.ant-steps-small .ant-steps-item-icon{width:24px;height:24px;margin:0 8px 0 0;font-size:12px;line-height:24px;text-align:center;border-radius:24px}.ant-steps-small .ant-steps-item-title{padding-right:12px;font-size:14px;line-height:24px}.ant-steps-small .ant-steps-item-title:after{top:12px}.ant-steps-small .ant-steps-item-description{color:#ffffff73;font-size:14px}.ant-steps-small .ant-steps-item-tail{top:8px}.ant-steps-small .ant-steps-item-custom .ant-steps-item-icon{width:inherit;height:inherit;line-height:inherit;background:none;border:0;border-radius:0}.ant-steps-small .ant-steps-item-custom .ant-steps-item-icon>.ant-steps-icon{font-size:24px;line-height:24px;transform:none}.ant-steps-vertical{display:flex;flex-direction:column}.ant-steps-vertical>.ant-steps-item{display:block;flex:1 0 auto;padding-left:0;overflow:visible}.ant-steps-vertical>.ant-steps-item .ant-steps-item-icon{float:left;margin-right:16px}.ant-steps-vertical>.ant-steps-item .ant-steps-item-content{display:block;min-height:48px;overflow:hidden}.ant-steps-vertical>.ant-steps-item .ant-steps-item-title{line-height:32px}.ant-steps-vertical>.ant-steps-item .ant-steps-item-description{padding-bottom:12px}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{position:absolute;top:0;left:16px;width:1px;height:100%;padding:38px 0 6px}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail:after{width:1px;height:100%}.ant-steps-vertical>.ant-steps-item:not(:last-child)>.ant-steps-item-container>.ant-steps-item-tail{display:block}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{display:none}.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-tail{position:absolute;top:0;left:12px;padding:30px 0 6px}.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-title{line-height:24px}.ant-steps-label-vertical .ant-steps-item{overflow:visible}.ant-steps-label-vertical .ant-steps-item-tail{margin-left:58px;padding:3.5px 24px}.ant-steps-label-vertical .ant-steps-item-content{display:block;width:116px;margin-top:8px;text-align:center}.ant-steps-label-vertical .ant-steps-item-icon{display:inline-block;margin-left:42px}.ant-steps-label-vertical .ant-steps-item-title{padding-right:0;padding-left:0}.ant-steps-label-vertical .ant-steps-item-title:after{display:none}.ant-steps-label-vertical .ant-steps-item-subtitle{display:block;margin-bottom:4px;margin-left:0;line-height:1.5715}.ant-steps-label-vertical.ant-steps-small:not(.ant-steps-dot) .ant-steps-item-icon{margin-left:46px}.ant-steps-dot .ant-steps-item-title,.ant-steps-dot.ant-steps-small .ant-steps-item-title{line-height:1.5715}.ant-steps-dot .ant-steps-item-tail,.ant-steps-dot.ant-steps-small .ant-steps-item-tail{top:2px;width:100%;margin:0 0 0 70px;padding:0}.ant-steps-dot .ant-steps-item-tail:after,.ant-steps-dot.ant-steps-small .ant-steps-item-tail:after{width:calc(100% - 20px);height:3px;margin-left:12px}.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item:first-child .ant-steps-icon-dot{left:2px}.ant-steps-dot .ant-steps-item-icon,.ant-steps-dot.ant-steps-small .ant-steps-item-icon{width:8px;height:8px;margin-left:67px;padding-right:0;line-height:8px;background:transparent;border:0}.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot{position:relative;float:left;width:100%;height:100%;border-radius:100px;transition:all .3s}.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot:after,.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot:after{position:absolute;top:-12px;left:-26px;width:60px;height:32px;background:rgba(0,0,0,.001);content:""}.ant-steps-dot .ant-steps-item-content,.ant-steps-dot.ant-steps-small .ant-steps-item-content{width:140px}.ant-steps-dot .ant-steps-item-process .ant-steps-item-icon,.ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-item-icon{position:relative;top:-1px;width:10px;height:10px;line-height:10px;background:none}.ant-steps-dot .ant-steps-item-process .ant-steps-icon:first-child .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-icon:first-child .ant-steps-icon-dot{left:0}.ant-steps-vertical.ant-steps-dot .ant-steps-item-icon{margin-top:13px;margin-left:0;background:none}.ant-steps-vertical.ant-steps-dot .ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{top:6.5px;left:-9px;margin:0;padding:22px 0 4px}.ant-steps-vertical.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot{left:0}.ant-steps-vertical.ant-steps-dot .ant-steps-item-content{width:inherit}.ant-steps-vertical.ant-steps-dot .ant-steps-item-process .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot{top:-1px;left:-1px}.ant-steps-navigation{padding-top:12px}.ant-steps-navigation.ant-steps-small .ant-steps-item-container{margin-left:-12px}.ant-steps-navigation .ant-steps-item{overflow:visible;text-align:center}.ant-steps-navigation .ant-steps-item-container{display:inline-block;height:100%;margin-left:-16px;padding-bottom:12px;text-align:left;transition:opacity .3s}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-content{max-width:auto}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title{max-width:100%;padding-right:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title:after{display:none}.ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role=button]{cursor:pointer}.ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role=button]:hover{opacity:.85}.ant-steps-navigation .ant-steps-item:last-child{flex:1}.ant-steps-navigation .ant-steps-item:last-child:after{display:none}.ant-steps-navigation .ant-steps-item:after{position:absolute;top:50%;left:100%;display:inline-block;width:12px;height:12px;margin-top:-14px;margin-left:-2px;border:1px solid rgba(255,255,255,.2);border-bottom:none;border-left:none;transform:rotate(45deg);content:""}.ant-steps-navigation .ant-steps-item:before{position:absolute;bottom:0;left:50%;display:inline-block;width:0;height:2px;background-color:#177ddc;transition:width .3s,left .3s;transition-timing-function:ease-out;content:""}.ant-steps-navigation .ant-steps-item.ant-steps-item-active:before{left:0;width:100%}.ant-steps-navigation.ant-steps-vertical>.ant-steps-item{margin-right:0!important}.ant-steps-navigation.ant-steps-vertical>.ant-steps-item:before{display:none}.ant-steps-navigation.ant-steps-vertical>.ant-steps-item.ant-steps-item-active:before{top:0;right:0;left:unset;display:block;width:3px;height:calc(100% - 24px)}.ant-steps-navigation.ant-steps-vertical>.ant-steps-item:after{position:relative;top:-2px;left:50%;display:block;width:8px;height:8px;margin-bottom:8px;text-align:center;transform:rotate(135deg)}.ant-steps-navigation.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{visibility:hidden}.ant-steps-navigation.ant-steps-horizontal>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{visibility:hidden}.ant-steps-rtl{direction:rtl}.ant-steps.ant-steps-rtl .ant-steps-item-icon{margin-right:0;margin-left:8px}.ant-steps-rtl .ant-steps-item-tail{right:0;left:auto}.ant-steps-rtl .ant-steps-item-title{padding-right:0;padding-left:16px}.ant-steps-rtl .ant-steps-item-title:after{right:100%;left:auto}.ant-steps-rtl.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{padding-right:16px;padding-left:0}.ant-steps-rtl.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child{padding-right:0}.ant-steps-rtl.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child .ant-steps-item-title{padding-left:0}.ant-steps-rtl .ant-steps-item-custom .ant-steps-item-icon>.ant-steps-icon{right:.5px;left:auto}.ant-steps-rtl.ant-steps-navigation.ant-steps-small .ant-steps-item-container{margin-right:-12px;margin-left:0}.ant-steps-rtl.ant-steps-navigation .ant-steps-item-container{margin-right:-16px;margin-left:0;text-align:right}.ant-steps-rtl.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title{padding-left:0}.ant-steps-rtl.ant-steps-navigation .ant-steps-item:after{right:100%;left:auto;margin-right:-2px;margin-left:0;transform:rotate(225deg)}.ant-steps-rtl.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{padding-right:12px;padding-left:0}.ant-steps-rtl.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child{padding-right:0}.ant-steps-rtl.ant-steps-small .ant-steps-item-title{padding-right:0;padding-left:12px}.ant-steps-rtl.ant-steps-vertical>.ant-steps-item .ant-steps-item-icon{float:right;margin-right:0;margin-left:16px}.ant-steps-rtl.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{right:16px;left:auto}.ant-steps-rtl.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-tail{right:12px;left:auto}.ant-steps-rtl.ant-steps-label-vertical .ant-steps-item-title{padding-left:0}.ant-steps-rtl.ant-steps-dot .ant-steps-item-tail,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-tail{margin:0 70px 0 0}.ant-steps-rtl.ant-steps-dot .ant-steps-item-tail:after,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-tail:after{margin-right:12px;margin-left:0}.ant-steps-rtl.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item:first-child .ant-steps-icon-dot{right:2px;left:auto}.ant-steps-rtl.ant-steps-dot .ant-steps-item-icon,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-icon{margin-right:67px;margin-left:0}.ant-steps-rtl.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot{float:right}.ant-steps-rtl.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot:after,.ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot:after{right:-26px;left:auto}.ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item-icon{margin-right:0;margin-left:16px}.ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{right:-9px;left:auto}.ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot{right:0;left:auto}.ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item-process .ant-steps-icon-dot{right:-2px;left:auto}.ant-steps-rtl.ant-steps-with-progress.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item:first-child.ant-steps-item-active{padding-right:4px}.ant-steps-with-progress .ant-steps-item{padding-top:4px}.ant-steps-with-progress .ant-steps-item .ant-steps-item-tail{top:4px!important}.ant-steps-with-progress.ant-steps-horizontal .ant-steps-item:first-child{padding-bottom:4px;padding-left:4px}.ant-steps-with-progress .ant-steps-item-icon{position:relative}.ant-steps-with-progress .ant-steps-item-icon .ant-progress{position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px}/*!***********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/switch/style/index.less ***! + \\***********************************************************************************************************************************************************************************************************************************************************/.ant-switch{margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;box-sizing:border-box;min-width:44px;height:22px;line-height:22px;vertical-align:middle;background-color:#ffffff4d;border:0;border-radius:100px;cursor:pointer;transition:all .2s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-switch:focus{outline:0;box-shadow:0 0 0 2px #ffffff1a}.ant-switch-checked:focus{box-shadow:0 0 0 2px #111b26}.ant-switch:focus:hover{box-shadow:none}.ant-switch-checked{background-color:#177ddc}.ant-switch-loading,.ant-switch-disabled{cursor:not-allowed;opacity:.4}.ant-switch-loading *,.ant-switch-disabled *{box-shadow:none;cursor:not-allowed}.ant-switch-inner{display:block;margin:0 7px 0 25px;color:#fff;font-size:12px;transition:margin .2s}.ant-switch-checked .ant-switch-inner{margin:0 25px 0 7px}.ant-switch-handle{position:absolute;top:2px;left:2px;width:18px;height:18px;transition:all .2s ease-in-out}.ant-switch-handle:before{position:absolute;top:0;right:0;bottom:0;left:0;background-color:#fff;border-radius:9px;box-shadow:0 2px 4px #00230b33;transition:all .2s ease-in-out;content:""}.ant-switch-checked .ant-switch-handle{left:calc(100% - 20px)}.ant-switch:not(.ant-switch-disabled):active .ant-switch-handle:before{right:-30%;left:0}.ant-switch:not(.ant-switch-disabled):active.ant-switch-checked .ant-switch-handle:before{right:0;left:-30%}.ant-switch-loading-icon.anticon{position:relative;top:2px;color:#000000a6;vertical-align:top}.ant-switch-checked .ant-switch-loading-icon{color:#177ddc}.ant-switch-small{min-width:28px;height:16px;line-height:16px}.ant-switch-small .ant-switch-inner{margin:0 5px 0 18px;font-size:12px}.ant-switch-small .ant-switch-handle{width:12px;height:12px}.ant-switch-small .ant-switch-loading-icon{top:1.5px;font-size:9px}.ant-switch-small.ant-switch-checked .ant-switch-inner{margin:0 18px 0 5px}.ant-switch-small.ant-switch-checked .ant-switch-handle{left:calc(100% - 14px)}.ant-switch-rtl{direction:rtl}.ant-switch-rtl .ant-switch-inner{margin:0 25px 0 7px}.ant-switch-rtl .ant-switch-handle{right:2px;left:auto}.ant-switch-rtl:not(.ant-switch-rtl-disabled):active .ant-switch-handle:before{right:0;left:-30%}.ant-switch-rtl:not(.ant-switch-rtl-disabled):active.ant-switch-checked .ant-switch-handle:before{right:-30%;left:0}.ant-switch-rtl.ant-switch-checked .ant-switch-inner{margin:0 7px 0 25px}.ant-switch-rtl.ant-switch-checked .ant-switch-handle{right:calc(100% - 20px)}.ant-switch-rtl.ant-switch-small.ant-switch-checked .ant-switch-handle{right:calc(100% - 14px)}/*!**********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/table/style/index.less ***! + \\**********************************************************************************************************************************************************************************************************************************************************/.ant-table.ant-table-middle{font-size:14px}.ant-table.ant-table-middle .ant-table-title,.ant-table.ant-table-middle .ant-table-footer,.ant-table.ant-table-middle .ant-table-thead>tr>th,.ant-table.ant-table-middle .ant-table-tbody>tr>td,.ant-table.ant-table-middle tfoot>tr>th,.ant-table.ant-table-middle tfoot>tr>td{padding:12px 8px}.ant-table.ant-table-middle .ant-table-filter-trigger{margin-right:-4px}.ant-table.ant-table-middle .ant-table-expanded-row-fixed{margin:-12px -8px}.ant-table.ant-table-middle .ant-table-tbody .ant-table-wrapper:only-child .ant-table{margin:-12px -8px -12px 25px}.ant-table.ant-table-small{font-size:14px}.ant-table.ant-table-small .ant-table-title,.ant-table.ant-table-small .ant-table-footer,.ant-table.ant-table-small .ant-table-thead>tr>th,.ant-table.ant-table-small .ant-table-tbody>tr>td,.ant-table.ant-table-small tfoot>tr>th,.ant-table.ant-table-small tfoot>tr>td{padding:8px}.ant-table.ant-table-small .ant-table-filter-trigger{margin-right:-4px}.ant-table.ant-table-small .ant-table-expanded-row-fixed{margin:-8px}.ant-table.ant-table-small .ant-table-tbody .ant-table-wrapper:only-child .ant-table{margin:-8px -8px -8px 25px}.ant-table-small .ant-table-thead>tr>th{background-color:#1d1d1d}.ant-table-small .ant-table-selection-column{width:46px;min-width:46px}.ant-table.ant-table-bordered>.ant-table-title{border:1px solid #303030;border-bottom:0}.ant-table.ant-table-bordered>.ant-table-container{border-left:1px solid #303030}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>thead>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>thead>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>thead>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>thead>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tbody>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tbody>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tbody>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tbody>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tfoot>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tfoot>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tfoot>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tfoot>tr>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tfoot>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tfoot>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tfoot>tr>td,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tfoot>tr>td{border-right:1px solid #303030}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>thead>tr:not(:last-child)>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>thead>tr:not(:last-child)>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>thead>tr:not(:last-child)>th,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>thead>tr:not(:last-child)>th{border-bottom:1px solid #303030}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>thead>tr>th:before,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>thead>tr>th:before,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>thead>tr>th:before,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>thead>tr>th:before{background-color:transparent!important}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>thead>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>thead>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>thead>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>thead>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tbody>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tbody>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tbody>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tbody>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tfoot>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tfoot>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tfoot>tr>.ant-table-cell-fix-right-first:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tfoot>tr>.ant-table-cell-fix-right-first:after{border-right:1px solid #303030}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tbody>tr>td>.ant-table-expanded-row-fixed,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tbody>tr>td>.ant-table-expanded-row-fixed,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tbody>tr>td>.ant-table-expanded-row-fixed,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tbody>tr>td>.ant-table-expanded-row-fixed{margin:-16px -17px}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table>tbody>tr>td>.ant-table-expanded-row-fixed:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table>tbody>tr>td>.ant-table-expanded-row-fixed:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-body>table>tbody>tr>td>.ant-table-expanded-row-fixed:after,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-summary>table>tbody>tr>td>.ant-table-expanded-row-fixed:after{position:absolute;top:0;right:1px;bottom:0;border-right:1px solid #303030;content:""}.ant-table.ant-table-bordered>.ant-table-container>.ant-table-content>table,.ant-table.ant-table-bordered>.ant-table-container>.ant-table-header>table{border-top:1px solid #303030}.ant-table.ant-table-bordered.ant-table-scroll-horizontal>.ant-table-container>.ant-table-body>table>tbody>tr.ant-table-expanded-row>td,.ant-table.ant-table-bordered.ant-table-scroll-horizontal>.ant-table-container>.ant-table-body>table>tbody>tr.ant-table-placeholder>td{border-right:0}.ant-table.ant-table-bordered.ant-table-middle>.ant-table-container>.ant-table-content>table>tbody>tr>td>.ant-table-expanded-row-fixed,.ant-table.ant-table-bordered.ant-table-middle>.ant-table-container>.ant-table-body>table>tbody>tr>td>.ant-table-expanded-row-fixed{margin:-12px -9px}.ant-table.ant-table-bordered.ant-table-small>.ant-table-container>.ant-table-content>table>tbody>tr>td>.ant-table-expanded-row-fixed,.ant-table.ant-table-bordered.ant-table-small>.ant-table-container>.ant-table-body>table>tbody>tr>td>.ant-table-expanded-row-fixed{margin:-8px -9px}.ant-table.ant-table-bordered>.ant-table-footer{border:1px solid #303030;border-top:0}.ant-table-cell .ant-table-container:first-child{border-top:0}.ant-table-cell-scrollbar{box-shadow:0 1px 0 1px #1d1d1d}.ant-table-resize-handle{position:absolute;top:0;height:100%!important;bottom:0;left:auto!important;right:-8px;cursor:col-resize;touch-action:none;-webkit-user-select:auto;-moz-user-select:auto;user-select:auto;width:16px;z-index:1}.ant-table-resize-handle-line{display:block;width:1px;margin-left:7px;height:100%!important;background-color:#177ddc;opacity:0}.ant-table-resize-handle:hover .ant-table-resize-handle-line{opacity:1}.ant-table-resize-handle.dragging{overflow:hidden}.ant-table-resize-handle.dragging .ant-table-resize-handle-line{opacity:1}.ant-table-resize-handle.dragging:before{position:absolute;top:0;bottom:0;width:100%;content:" ";width:200vw;transform:translate(-50%);opacity:0}.ant-table-wrapper{clear:both;max-width:100%}.ant-table-wrapper:before{display:table;content:""}.ant-table-wrapper:after{display:table;clear:both;content:""}.ant-table{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;font-size:14px;background:#141414;border-radius:2px}.ant-table table{width:100%;text-align:left;border-radius:2px 2px 0 0;border-collapse:separate;border-spacing:0}.ant-table-thead>tr>th,.ant-table-tbody>tr>td,.ant-table tfoot>tr>th,.ant-table tfoot>tr>td{position:relative;padding:16px;overflow-wrap:break-word}.ant-table-cell-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;word-break:keep-all}.ant-table-cell-ellipsis.ant-table-cell-fix-left-last,.ant-table-cell-ellipsis.ant-table-cell-fix-right-first{overflow:visible}.ant-table-cell-ellipsis.ant-table-cell-fix-left-last .ant-table-cell-content,.ant-table-cell-ellipsis.ant-table-cell-fix-right-first .ant-table-cell-content{display:block;overflow:hidden;text-overflow:ellipsis}.ant-table-cell-ellipsis .ant-table-column-title{overflow:hidden;text-overflow:ellipsis;word-break:keep-all}.ant-table-title{padding:16px}.ant-table-footer{padding:16px;color:#ffffffd9;background:rgba(255,255,255,.04)}.ant-table-thead>tr>th{position:relative;color:#ffffffd9;font-weight:500;text-align:left;background:#1d1d1d;border-bottom:1px solid #303030;transition:background .3s ease}.ant-table-thead>tr>th[colspan]:not([colspan="1"]){text-align:center}.ant-table-thead>tr>th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan]):before{position:absolute;top:50%;right:0;width:1px;height:1.6em;background-color:#ffffff14;transform:translateY(-50%);transition:background-color .3s;content:""}.ant-table-thead>tr:not(:last-child)>th[colspan]{border-bottom:0}.ant-table-tbody>tr>td{border-bottom:1px solid #303030;transition:background .3s}.ant-table-tbody>tr>td>.ant-table-wrapper:only-child .ant-table,.ant-table-tbody>tr>td>.ant-table-expanded-row-fixed>.ant-table-wrapper:only-child .ant-table{margin:-16px -16px -16px 33px}.ant-table-tbody>tr>td>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td,.ant-table-tbody>tr>td>.ant-table-expanded-row-fixed>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td{border-bottom:0}.ant-table-tbody>tr>td>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td:first-child,.ant-table-tbody>tr>td>.ant-table-expanded-row-fixed>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td:first-child,.ant-table-tbody>tr>td>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td:last-child,.ant-table-tbody>tr>td>.ant-table-expanded-row-fixed>.ant-table-wrapper:only-child .ant-table-tbody>tr:last-child>td:last-child{border-radius:0}.ant-table-tbody>tr.ant-table-row:hover>td,.ant-table-tbody>tr>td.ant-table-cell-row-hover{background:#262626}.ant-table-tbody>tr.ant-table-row-selected>td{background:#111b26;border-color:#00000008}.ant-table-tbody>tr.ant-table-row-selected:hover>td{background:#0e161f}.ant-table-summary{position:relative;z-index:2;background:#141414}div.ant-table-summary{box-shadow:0 -1px #303030}.ant-table-summary>tr>th,.ant-table-summary>tr>td{border-bottom:1px solid #303030}.ant-table-pagination.ant-pagination{margin:16px 0}.ant-table-pagination{display:flex;flex-wrap:wrap;row-gap:8px}.ant-table-pagination>*{flex:none}.ant-table-pagination-left{justify-content:flex-start}.ant-table-pagination-center{justify-content:center}.ant-table-pagination-right{justify-content:flex-end}.ant-table-thead th.ant-table-column-has-sorters{cursor:pointer;transition:all .3s}.ant-table-thead th.ant-table-column-has-sorters:hover{background:#303030}.ant-table-thead th.ant-table-column-has-sorters:hover:before{background-color:transparent!important}.ant-table-thead th.ant-table-column-has-sorters.ant-table-cell-fix-left:hover,.ant-table-thead th.ant-table-column-has-sorters.ant-table-cell-fix-right:hover{background:#222}.ant-table-thead th.ant-table-column-sort{background:#262626}.ant-table-thead th.ant-table-column-sort:before{background-color:transparent!important}td.ant-table-column-sort{background:rgba(255,255,255,.01)}.ant-table-column-title{position:relative;z-index:1;flex:1}.ant-table-column-sorters{display:flex;flex:auto;align-items:center;justify-content:space-between}.ant-table-column-sorters:after{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;content:""}.ant-table-column-sorter{margin-left:4px;color:#bfbfbf;font-size:0;transition:color .3s}.ant-table-column-sorter-inner{display:inline-flex;flex-direction:column;align-items:center}.ant-table-column-sorter-up,.ant-table-column-sorter-down{font-size:11px}.ant-table-column-sorter-up.active,.ant-table-column-sorter-down.active{color:#177ddc}.ant-table-column-sorter-up+.ant-table-column-sorter-down{margin-top:-.3em}.ant-table-column-sorters:hover .ant-table-column-sorter{color:#a6a6a6}.ant-table-filter-column{display:flex;justify-content:space-between}.ant-table-filter-trigger{position:relative;display:flex;align-items:center;margin:-4px -8px -4px 4px;padding:0 4px;color:#bfbfbf;font-size:12px;border-radius:2px;cursor:pointer;transition:all .3s}.ant-table-filter-trigger:hover{color:#ffffff73;background:#434343}.ant-table-filter-trigger.active{color:#177ddc}.ant-table-filter-dropdown{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";min-width:120px;background-color:#1f1f1f;border-radius:2px;box-shadow:0 3px 6px -4px #0000007a,0 6px 16px #00000052,0 9px 28px 8px #0003}.ant-table-filter-dropdown .ant-dropdown-menu{max-height:264px;overflow-x:hidden;border:0;box-shadow:none}.ant-table-filter-dropdown .ant-dropdown-menu:empty:after{display:block;padding:8px 0;color:#ffffff4d;font-size:12px;text-align:center;content:"Not Found"}.ant-table-filter-dropdown-tree{padding:8px 8px 0}.ant-table-filter-dropdown-tree .ant-tree-treenode .ant-tree-node-content-wrapper:hover{background-color:#ffffff14}.ant-table-filter-dropdown-tree .ant-tree-treenode-checkbox-checked .ant-tree-node-content-wrapper,.ant-table-filter-dropdown-tree .ant-tree-treenode-checkbox-checked .ant-tree-node-content-wrapper:hover{background-color:#11263c}.ant-table-filter-dropdown-search{padding:8px;border-bottom:1px #303030 solid}.ant-table-filter-dropdown-search-input input{min-width:140px}.ant-table-filter-dropdown-search-input .anticon{color:#ffffff4d}.ant-table-filter-dropdown-checkall{width:100%;margin-bottom:4px;margin-left:4px}.ant-table-filter-dropdown-submenu>ul{max-height:calc(100vh - 130px);overflow-x:hidden;overflow-y:auto}.ant-table-filter-dropdown .ant-checkbox-wrapper+span,.ant-table-filter-dropdown-submenu .ant-checkbox-wrapper+span{padding-left:8px}.ant-table-filter-dropdown-btns{display:flex;justify-content:space-between;padding:7px 8px;overflow:hidden;background-color:#1f1f1f;border-top:1px solid #303030}.ant-table-selection-col{width:32px}.ant-table-bordered .ant-table-selection-col{width:50px}table tr th.ant-table-selection-column,table tr td.ant-table-selection-column{padding-right:8px;padding-left:8px;text-align:center}table tr th.ant-table-selection-column .ant-radio-wrapper,table tr td.ant-table-selection-column .ant-radio-wrapper{margin-right:0}table tr th.ant-table-selection-column.ant-table-cell-fix-left{z-index:3}table tr th.ant-table-selection-column:after{background-color:transparent!important}.ant-table-selection{position:relative;display:inline-flex;flex-direction:column}.ant-table-selection-extra{position:absolute;top:0;z-index:1;cursor:pointer;transition:all .3s;-webkit-margin-start:100%;margin-inline-start:100%;-webkit-padding-start:4px;padding-inline-start:4px}.ant-table-selection-extra .anticon{color:#bfbfbf;font-size:10px}.ant-table-selection-extra .anticon:hover{color:#a6a6a6}.ant-table-expand-icon-col{width:48px}.ant-table-row-expand-icon-cell{text-align:center}.ant-table-row-indent{float:left;height:1px}.ant-table-row-expand-icon{color:#177ddc;text-decoration:none;cursor:pointer;transition:color .3s;position:relative;display:inline-flex;float:left;box-sizing:border-box;width:17px;height:17px;padding:0;color:inherit;line-height:17px;background:transparent;border:1px solid #303030;border-radius:2px;outline:none;transform:scale(.94117647);transition:all .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{color:#165996}.ant-table-row-expand-icon:active{color:#388ed3}.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover,.ant-table-row-expand-icon:active{border-color:currentcolor}.ant-table-row-expand-icon:before,.ant-table-row-expand-icon:after{position:absolute;background:currentcolor;transition:transform .3s ease-out;content:""}.ant-table-row-expand-icon:before{top:7px;right:3px;left:3px;height:1px}.ant-table-row-expand-icon:after{top:3px;bottom:3px;left:7px;width:1px;transform:rotate(90deg)}.ant-table-row-expand-icon-collapsed:before{transform:rotate(-180deg)}.ant-table-row-expand-icon-collapsed:after{transform:rotate(0)}.ant-table-row-expand-icon-spaced{background:transparent;border:0;visibility:hidden}.ant-table-row-expand-icon-spaced:before,.ant-table-row-expand-icon-spaced:after{display:none;content:none}.ant-table-row-indent+.ant-table-row-expand-icon{margin-top:2.5005px;margin-right:8px}tr.ant-table-expanded-row>td,tr.ant-table-expanded-row:hover>td{background:#1d1d1d}tr.ant-table-expanded-row .ant-descriptions-view{display:flex}tr.ant-table-expanded-row .ant-descriptions-view table{flex:auto;width:auto}.ant-table .ant-table-expanded-row-fixed{position:relative;margin:-16px;padding:16px}.ant-table-tbody>tr.ant-table-placeholder{text-align:center}.ant-table-empty .ant-table-tbody>tr.ant-table-placeholder{color:#ffffff4d}.ant-table-tbody>tr.ant-table-placeholder:hover>td{background:#141414}.ant-table-cell-fix-left,.ant-table-cell-fix-right{position:sticky!important;z-index:2;background:#141414}.ant-table-cell-fix-left-first:after,.ant-table-cell-fix-left-last:after{position:absolute;top:0;right:0;bottom:-1px;width:30px;transform:translate(100%);transition:box-shadow .3s;content:"";pointer-events:none}.ant-table-cell-fix-right-first:after,.ant-table-cell-fix-right-last:after{position:absolute;top:0;bottom:-1px;left:0;width:30px;transform:translate(-100%);transition:box-shadow .3s;content:"";pointer-events:none}.ant-table .ant-table-container:before,.ant-table .ant-table-container:after{position:absolute;top:0;bottom:0;z-index:2;width:30px;transition:box-shadow .3s;content:"";pointer-events:none}.ant-table .ant-table-container:before{left:0}.ant-table .ant-table-container:after{right:0}.ant-table-ping-left:not(.ant-table-has-fix-left) .ant-table-container{position:relative}.ant-table-ping-left:not(.ant-table-has-fix-left) .ant-table-container:before{box-shadow:inset 10px 0 8px -8px #00000073}.ant-table-ping-left .ant-table-cell-fix-left-first:after,.ant-table-ping-left .ant-table-cell-fix-left-last:after{box-shadow:inset 10px 0 8px -8px #00000073}.ant-table-ping-left .ant-table-cell-fix-left-last:before{background-color:transparent!important}.ant-table-ping-right:not(.ant-table-has-fix-right) .ant-table-container{position:relative}.ant-table-ping-right:not(.ant-table-has-fix-right) .ant-table-container:after{box-shadow:inset -10px 0 8px -8px #00000073}.ant-table-ping-right .ant-table-cell-fix-right-first:after,.ant-table-ping-right .ant-table-cell-fix-right-last:after{box-shadow:inset -10px 0 8px -8px #00000073}.ant-table-sticky-holder{position:sticky;z-index:3;background:#141414}.ant-table-sticky-scroll{position:sticky;bottom:0;z-index:3;display:flex;align-items:center;background:#fcfcfc;border-top:1px solid #303030;opacity:.6}.ant-table-sticky-scroll:hover{transform-origin:center bottom}.ant-table-sticky-scroll-bar{height:8px;background-color:#00000059;border-radius:4px}.ant-table-sticky-scroll-bar:hover,.ant-table-sticky-scroll-bar-active{background-color:#000c}@media all and (-ms-high-contrast: none){.ant-table-ping-left .ant-table-cell-fix-left-last:after{box-shadow:none!important}.ant-table-ping-right .ant-table-cell-fix-right-first:after{box-shadow:none!important}}.ant-table-title{border-radius:2px 2px 0 0}.ant-table-title+.ant-table-container{border-top-left-radius:0;border-top-right-radius:0}.ant-table-title+.ant-table-container table>thead>tr:first-child th:first-child{border-radius:0}.ant-table-title+.ant-table-container table>thead>tr:first-child th:last-child{border-radius:0}.ant-table-container{border-top-left-radius:2px;border-top-right-radius:2px}.ant-table-container table>thead>tr:first-child th:first-child{border-top-left-radius:2px}.ant-table-container table>thead>tr:first-child th:last-child{border-top-right-radius:2px}.ant-table-footer{border-radius:0 0 2px 2px}.ant-table-wrapper-rtl,.ant-table-rtl{direction:rtl}.ant-table-wrapper-rtl .ant-table table{text-align:right}.ant-table-wrapper-rtl .ant-table-thead>tr>th[colspan]:not([colspan="1"]){text-align:center}.ant-table-wrapper-rtl .ant-table-thead>tr>th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan]):before{right:auto;left:0}.ant-table-wrapper-rtl .ant-table-thead>tr>th{text-align:right}.ant-table-tbody>tr .ant-table-wrapper:only-child .ant-table.ant-table-rtl{margin:-16px 33px -16px -16px}.ant-table-wrapper.ant-table-wrapper-rtl .ant-table-pagination-left{justify-content:flex-end}.ant-table-wrapper.ant-table-wrapper-rtl .ant-table-pagination-right{justify-content:flex-start}.ant-table-wrapper-rtl .ant-table-column-sorter{margin-right:4px;margin-left:0}.ant-table-wrapper-rtl .ant-table-filter-column-title{padding:16px 16px 16px 2.3em}.ant-table-rtl .ant-table-thead tr th.ant-table-column-has-sorters .ant-table-filter-column-title{padding:0 0 0 2.3em}.ant-table-wrapper-rtl .ant-table-filter-trigger{margin:-4px 4px -4px -8px}.ant-dropdown-rtl .ant-table-filter-dropdown .ant-checkbox-wrapper+span,.ant-dropdown-rtl .ant-table-filter-dropdown-submenu .ant-checkbox-wrapper+span,.ant-dropdown-menu-submenu-rtl.ant-table-filter-dropdown .ant-checkbox-wrapper+span,.ant-dropdown-menu-submenu-rtl.ant-table-filter-dropdown-submenu .ant-checkbox-wrapper+span{padding-right:8px;padding-left:0}.ant-table-wrapper-rtl .ant-table-selection{text-align:center}.ant-table-wrapper-rtl .ant-table-row-indent,.ant-table-wrapper-rtl .ant-table-row-expand-icon{float:right}.ant-table-wrapper-rtl .ant-table-row-indent+.ant-table-row-expand-icon{margin-right:0;margin-left:8px}.ant-table-wrapper-rtl .ant-table-row-expand-icon:after{transform:rotate(-90deg)}.ant-table-wrapper-rtl .ant-table-row-expand-icon-collapsed:before{transform:rotate(180deg)}.ant-table-wrapper-rtl .ant-table-row-expand-icon-collapsed:after{transform:rotate(0)}/*!*********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/tree/style/index.less ***! + \\*********************************************************************************************************************************************************************************************************************************************************/.ant-tree.ant-tree-directory .ant-tree-treenode{position:relative}.ant-tree.ant-tree-directory .ant-tree-treenode:before{position:absolute;top:0;right:0;bottom:4px;left:0;transition:background-color .3s;content:"";pointer-events:none}.ant-tree.ant-tree-directory .ant-tree-treenode:hover:before{background:rgba(255,255,255,.08)}.ant-tree.ant-tree-directory .ant-tree-treenode>*{z-index:1}.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-switcher{transition:color .3s}.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper{border-radius:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper:hover{background:transparent}.ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper.ant-tree-node-selected{color:#fff;background:transparent}.ant-tree.ant-tree-directory .ant-tree-treenode-selected:hover:before,.ant-tree.ant-tree-directory .ant-tree-treenode-selected:before{background:#177ddc}.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-switcher{color:#fff}.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-node-content-wrapper{color:#fff;background:transparent}.ant-tree-checkbox{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;line-height:1;white-space:nowrap;outline:none;cursor:pointer}.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-inner,.ant-tree-checkbox:hover .ant-tree-checkbox-inner,.ant-tree-checkbox-input:focus+.ant-tree-checkbox-inner{border-color:#177ddc}.ant-tree-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #177ddc;border-radius:2px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-tree-checkbox:hover:after,.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox:after{visibility:visible}.ant-tree-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;direction:ltr;background-color:transparent;border:1px solid #434343;border-radius:2px;border-collapse:separate;transition:all .3s}.ant-tree-checkbox-inner:after{position:absolute;top:50%;left:21.5%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-tree-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-tree-checkbox-checked .ant-tree-checkbox-inner{background-color:#177ddc;border-color:#177ddc}.ant-tree-checkbox-disabled{cursor:not-allowed}.ant-tree-checkbox-disabled.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after{border-color:#ffffff4d;animation-name:none}.ant-tree-checkbox-disabled .ant-tree-checkbox-input{cursor:not-allowed;pointer-events:none}.ant-tree-checkbox-disabled .ant-tree-checkbox-inner{background-color:#ffffff14;border-color:#434343!important}.ant-tree-checkbox-disabled .ant-tree-checkbox-inner:after{border-color:#ffffff14;border-collapse:separate;animation-name:none}.ant-tree-checkbox-disabled+span{color:#ffffff4d;cursor:not-allowed}.ant-tree-checkbox-disabled:hover:after,.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-disabled:after{visibility:hidden}.ant-tree-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-flex;align-items:baseline;line-height:unset;cursor:pointer}.ant-tree-checkbox-wrapper:after{display:inline-block;width:0;overflow:hidden;content:" "}.ant-tree-checkbox-wrapper.ant-tree-checkbox-wrapper-disabled{cursor:not-allowed}.ant-tree-checkbox-wrapper+.ant-tree-checkbox-wrapper{margin-left:8px}.ant-tree-checkbox+span{padding-right:8px;padding-left:8px}.ant-tree-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-tree-checkbox-group-item{margin-right:8px}.ant-tree-checkbox-group-item:last-child{margin-right:0}.ant-tree-checkbox-group-item+.ant-tree-checkbox-group-item{margin-left:0}.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner{background-color:transparent;border-color:#434343}.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#177ddc;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-tree-checkbox-indeterminate.ant-tree-checkbox-disabled .ant-tree-checkbox-inner:after{background-color:#ffffff4d;border-color:#ffffff4d}.ant-tree{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";background:transparent;border-radius:2px;transition:background-color .3s}.ant-tree-focused:not(:hover):not(.ant-tree-active-focused){background:#111b26}.ant-tree-list-holder-inner{align-items:flex-start}.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner{align-items:stretch}.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-node-content-wrapper{flex:auto}.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-treenode.dragging{position:relative}.ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-treenode.dragging:after{position:absolute;top:0;right:0;bottom:4px;left:0;border:1px solid #177ddc;opacity:0;animation:ant-tree-node-fx-do-not-use .3s;animation-play-state:running;animation-fill-mode:forwards;content:"";pointer-events:none}.ant-tree .ant-tree-treenode{display:flex;align-items:flex-start;padding:0 0 4px;outline:none}.ant-tree .ant-tree-treenode-disabled .ant-tree-node-content-wrapper{color:#ffffff4d;cursor:not-allowed}.ant-tree .ant-tree-treenode-disabled .ant-tree-node-content-wrapper:hover{background:transparent}.ant-tree .ant-tree-treenode-active .ant-tree-node-content-wrapper{background:rgba(255,255,255,.08)}.ant-tree .ant-tree-treenode:not(.ant-tree .ant-tree-treenode-disabled).filter-node .ant-tree-title{color:inherit;font-weight:500}.ant-tree-indent{align-self:stretch;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-tree-indent-unit{display:inline-block;width:24px}.ant-tree-draggable-icon{width:24px;line-height:24px;text-align:center;opacity:.2;transition:opacity .3s}.ant-tree-treenode:hover .ant-tree-draggable-icon{opacity:.45}.ant-tree-switcher{position:relative;flex:none;align-self:stretch;width:24px;margin:0;line-height:24px;text-align:center;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-tree-switcher .ant-tree-switcher-icon,.ant-tree-switcher .ant-select-tree-switcher-icon{display:inline-block;font-size:10px;vertical-align:baseline}.ant-tree-switcher .ant-tree-switcher-icon svg,.ant-tree-switcher .ant-select-tree-switcher-icon svg{transition:transform .3s}.ant-tree-switcher-noop{cursor:default}.ant-tree-switcher_close .ant-tree-switcher-icon svg{transform:rotate(-90deg)}.ant-tree-switcher-loading-icon{color:#177ddc}.ant-tree-switcher-leaf-line{position:relative;z-index:1;display:inline-block;width:100%;height:100%}.ant-tree-switcher-leaf-line:before{position:absolute;top:0;right:12px;bottom:-4px;margin-left:-1px;border-right:1px solid #d9d9d9;content:" "}.ant-tree-switcher-leaf-line:after{position:absolute;width:10px;height:14px;border-bottom:1px solid #d9d9d9;content:" "}.ant-tree-checkbox{top:initial;margin:4px 8px 0 0}.ant-tree .ant-tree-node-content-wrapper{position:relative;z-index:auto;min-height:24px;margin:0;padding:0 4px;color:inherit;line-height:24px;background:transparent;border-radius:2px;cursor:pointer;transition:all .3s,border 0s,line-height 0s,box-shadow 0s}.ant-tree .ant-tree-node-content-wrapper:hover{background-color:#ffffff14}.ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected{background-color:#11263c}.ant-tree .ant-tree-node-content-wrapper .ant-tree-iconEle{display:inline-block;width:24px;height:24px;line-height:24px;text-align:center;vertical-align:top}.ant-tree .ant-tree-node-content-wrapper .ant-tree-iconEle:empty{display:none}.ant-tree-unselectable .ant-tree-node-content-wrapper:hover{background-color:transparent}.ant-tree-node-content-wrapper{line-height:24px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-tree-node-content-wrapper .ant-tree-drop-indicator{position:absolute;z-index:1;height:2px;background-color:#177ddc;border-radius:1px;pointer-events:none}.ant-tree-node-content-wrapper .ant-tree-drop-indicator:after{position:absolute;top:-3px;left:-6px;width:8px;height:8px;background-color:transparent;border:2px solid #177ddc;border-radius:50%;content:""}.ant-tree .ant-tree-treenode.drop-container>[draggable]{box-shadow:0 0 0 2px #177ddc}.ant-tree-show-line .ant-tree-indent-unit{position:relative;height:100%}.ant-tree-show-line .ant-tree-indent-unit:before{position:absolute;top:0;right:12px;bottom:-4px;border-right:1px solid #434343;content:""}.ant-tree-show-line .ant-tree-indent-unit-end:before{display:none}.ant-tree-show-line .ant-tree-switcher{background:#141414}.ant-tree-show-line .ant-tree-switcher-line-icon{vertical-align:-.15em}.ant-tree .ant-tree-treenode-leaf-last .ant-tree-switcher-leaf-line:before{top:auto!important;bottom:auto!important;height:14px!important}.ant-tree-rtl{direction:rtl}.ant-tree-rtl .ant-tree-node-content-wrapper[draggable=true] .ant-tree-drop-indicator:after{right:-6px;left:unset}.ant-tree .ant-tree-treenode-rtl{direction:rtl}.ant-tree-rtl .ant-tree-switcher_close .ant-tree-switcher-icon svg{transform:rotate(90deg)}.ant-tree-rtl.ant-tree-show-line .ant-tree-indent-unit:before{right:auto;left:-13px;border-right:none;border-left:1px solid #434343}.ant-tree-rtl.ant-tree-checkbox,.ant-tree-select-dropdown-rtl .ant-select-tree-checkbox{margin:4px 0 0 8px}/*!****************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/time-picker/style/index.less ***! + \\****************************************************************************************************************************************************************************************************************************************************************//*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/timeline/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-timeline{box-sizing:border-box;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;font-feature-settings:"tnum";margin:0;padding:0;list-style:none}.ant-timeline-item{position:relative;margin:0;padding-bottom:20px;font-size:14px;list-style:none}.ant-timeline-item-tail{position:absolute;top:10px;left:4px;height:calc(100% - 10px);border-left:2px solid #303030}.ant-timeline-item-pending .ant-timeline-item-head{font-size:12px;background-color:transparent}.ant-timeline-item-pending .ant-timeline-item-tail{display:none}.ant-timeline-item-head{position:absolute;width:10px;height:10px;background-color:#141414;border:2px solid transparent;border-radius:100px}.ant-timeline-item-head-blue{color:#177ddc;border-color:#177ddc}.ant-timeline-item-head-red{color:#a61d24;border-color:#a61d24}.ant-timeline-item-head-green{color:#49aa19;border-color:#49aa19}.ant-timeline-item-head-gray{color:#ffffff4d;border-color:#ffffff4d}.ant-timeline-item-head-custom{position:absolute;top:5.5px;left:5px;width:auto;height:auto;margin-top:0;padding:3px 1px;line-height:1;text-align:center;border:0;border-radius:0;transform:translate(-50%,-50%)}.ant-timeline-item-content{position:relative;top:-7.001px;margin:0 0 0 26px;word-break:break-word}.ant-timeline-item-last>.ant-timeline-item-tail{display:none}.ant-timeline-item-last>.ant-timeline-item-content{min-height:48px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-tail,.ant-timeline.ant-timeline-right .ant-timeline-item-tail,.ant-timeline.ant-timeline-label .ant-timeline-item-tail,.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-head,.ant-timeline.ant-timeline-label .ant-timeline-item-head,.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom{left:50%}.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-head,.ant-timeline.ant-timeline-label .ant-timeline-item-head{margin-left:-4px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom{margin-left:1px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-left .ant-timeline-item-content,.ant-timeline.ant-timeline-right .ant-timeline-item-left .ant-timeline-item-content,.ant-timeline.ant-timeline-label .ant-timeline-item-left .ant-timeline-item-content{left:calc(50% - 4px);width:calc(50% - 14px);text-align:left}.ant-timeline.ant-timeline-alternate .ant-timeline-item-right .ant-timeline-item-content,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content,.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-content{width:calc(50% - 12px);margin:0;text-align:right}.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-tail,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head-custom{left:calc(100% - 6px)}.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content{width:calc(100% - 18px)}.ant-timeline.ant-timeline-pending .ant-timeline-item-last .ant-timeline-item-tail{display:block;height:calc(100% - 14px);border-left:2px dotted #303030}.ant-timeline.ant-timeline-reverse .ant-timeline-item-last .ant-timeline-item-tail{display:none}.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-tail{top:15px;display:block;height:calc(100% - 15px);border-left:2px dotted #303030}.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-content{min-height:48px}.ant-timeline.ant-timeline-label .ant-timeline-item-label{position:absolute;top:-7.001px;width:calc(50% - 12px);text-align:right}.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-label{left:calc(50% + 14px);width:calc(50% - 14px);text-align:left}.ant-timeline-rtl{direction:rtl}.ant-timeline-rtl .ant-timeline-item-tail{right:4px;left:auto;border-right:2px solid #303030;border-left:none}.ant-timeline-rtl .ant-timeline-item-head-custom{right:5px;left:auto;transform:translate(50%,-50%)}.ant-timeline-rtl .ant-timeline-item-content{margin:0 18px 0 0}.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-tail,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-tail,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-tail,.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom{right:50%;left:auto}.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head{margin-right:-4px;margin-left:0}.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom{margin-right:1px;margin-left:0}.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-left .ant-timeline-item-content,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-left .ant-timeline-item-content,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-left .ant-timeline-item-content{right:calc(50% - 4px);left:auto;text-align:right}.ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-right .ant-timeline-item-content,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content,.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-content{text-align:left}.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-tail,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head,.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head-custom{right:0;left:auto}.ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content{width:100%;margin-right:18px;text-align:right}.ant-timeline-rtl.ant-timeline.ant-timeline-pending .ant-timeline-item-last .ant-timeline-item-tail,.ant-timeline-rtl.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-tail{border-right:2px dotted #303030;border-left:none}.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-label{text-align:left}.ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-label{right:calc(50% + 14px);text-align:right}/*!*************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/transfer/style/index.less ***! + \\*************************************************************************************************************************************************************************************************************************************************************/.ant-transfer-customize-list .ant-transfer-list{flex:1 1 50%;width:auto;height:auto;min-height:200px}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small{border:0;border-radius:0}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small .ant-table-selection-column{width:40px;min-width:40px}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th{background:#1d1d1d}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small>.ant-table-content .ant-table-row:last-child td{border-bottom:1px solid #303030}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small .ant-table-body{margin:0}.ant-transfer-customize-list .ant-table-wrapper .ant-table-pagination.ant-pagination{margin:16px 0 4px}.ant-transfer-customize-list .ant-input[disabled]{background-color:transparent}.ant-transfer{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:flex;align-items:stretch}.ant-transfer-disabled .ant-transfer-list{background:rgba(255,255,255,.08)}.ant-transfer-list{display:flex;flex-direction:column;width:180px;height:200px;border:1px solid #434343;border-radius:2px}.ant-transfer-list-with-pagination{width:250px;height:auto}.ant-transfer-list-search .anticon-search{color:#ffffff4d}.ant-transfer-list-header{display:flex;flex:none;align-items:center;height:40px;padding:8px 12px 9px;color:#ffffffd9;background:#141414;border-bottom:1px solid #303030;border-radius:2px 2px 0 0}.ant-transfer-list-header>*:not(:last-child){margin-right:4px}.ant-transfer-list-header>*{flex:none}.ant-transfer-list-header-title{flex:auto;overflow:hidden;white-space:nowrap;text-align:right;text-overflow:ellipsis}.ant-transfer-list-header-dropdown{font-size:10px;transform:translateY(10%);cursor:pointer}.ant-transfer-list-header-dropdown[disabled]{cursor:not-allowed}.ant-transfer-list-body{display:flex;flex:auto;flex-direction:column;overflow:hidden;font-size:14px}.ant-transfer-list-body-search-wrapper{position:relative;flex:none;padding:12px}.ant-transfer-list-content{flex:auto;margin:0;padding:0;overflow:auto;list-style:none}.ant-transfer-list-content-item{display:flex;align-items:center;min-height:32px;padding:6px 12px;line-height:20px;transition:all .3s}.ant-transfer-list-content-item>*:not(:last-child){margin-right:8px}.ant-transfer-list-content-item>*{flex:none}.ant-transfer-list-content-item-text{flex:auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-transfer-list-content-item-remove{color:#177ddc;text-decoration:none;outline:none;cursor:pointer;transition:color .3s;position:relative;color:#434343}.ant-transfer-list-content-item-remove:focus,.ant-transfer-list-content-item-remove:hover{color:#165996}.ant-transfer-list-content-item-remove:active{color:#388ed3}.ant-transfer-list-content-item-remove:after{position:absolute;top:-6px;right:-50%;bottom:-6px;left:-50%;content:""}.ant-transfer-list-content-item-remove:hover{color:#165996}.ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover{background-color:#262626;cursor:pointer}.ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled).ant-transfer-list-content-item-checked:hover{background-color:#0e161f}.ant-transfer-list-content-show-remove .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover{background:transparent;cursor:default}.ant-transfer-list-content-item-checked{background-color:#111b26}.ant-transfer-list-content-item-disabled{color:#ffffff4d;cursor:not-allowed}.ant-transfer-list-pagination{padding:8px 0;text-align:right;border-top:1px solid #303030}.ant-transfer-list-body-not-found{flex:none;width:100%;margin:auto 0;color:#ffffff4d;text-align:center}.ant-transfer-list-footer{border-top:1px solid #303030}.ant-transfer-operation{display:flex;flex:none;flex-direction:column;align-self:center;margin:0 8px;vertical-align:middle}.ant-transfer-operation .ant-btn{display:block}.ant-transfer-operation .ant-btn:first-child{margin-bottom:4px}.ant-transfer-operation .ant-btn .anticon{font-size:12px}.ant-transfer .ant-empty-image{max-height:-2px}.ant-transfer-rtl{direction:rtl}.ant-transfer-rtl .ant-transfer-list-search{padding-right:8px;padding-left:24px}.ant-transfer-rtl .ant-transfer-list-search-action{right:auto;left:12px}.ant-transfer-rtl .ant-transfer-list-header>*:not(:last-child){margin-right:0;margin-left:4px}.ant-transfer-rtl .ant-transfer-list-header{right:0;left:auto}.ant-transfer-rtl .ant-transfer-list-header-title{text-align:left}.ant-transfer-rtl .ant-transfer-list-content-item>*:not(:last-child){margin-right:0;margin-left:8px}.ant-transfer-rtl .ant-transfer-list-pagination{text-align:left}.ant-transfer-rtl .ant-transfer-list-footer{right:0;left:auto}/*!****************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/tree-select/style/index.less ***! + \\****************************************************************************************************************************************************************************************************************************************************************/@keyframes ant-tree-node-fx-do-not-use{0%{opacity:0}to{opacity:1}}@keyframes antCheckboxEffect{0%{transform:scale(1);opacity:.5}to{transform:scale(1.6);opacity:0}}.ant-select-tree-checkbox{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;line-height:1;white-space:nowrap;outline:none;cursor:pointer}.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-inner,.ant-select-tree-checkbox:hover .ant-select-tree-checkbox-inner,.ant-select-tree-checkbox-input:focus+.ant-select-tree-checkbox-inner{border-color:#177ddc}.ant-select-tree-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #177ddc;border-radius:2px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-select-tree-checkbox:hover:after,.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox:after{visibility:visible}.ant-select-tree-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;direction:ltr;background-color:transparent;border:1px solid #434343;border-radius:2px;border-collapse:separate;transition:all .3s}.ant-select-tree-checkbox-inner:after{position:absolute;top:50%;left:21.5%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-select-tree-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner{background-color:#177ddc;border-color:#177ddc}.ant-select-tree-checkbox-disabled{cursor:not-allowed}.ant-select-tree-checkbox-disabled.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner:after{border-color:#ffffff4d;animation-name:none}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-input{cursor:not-allowed;pointer-events:none}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner{background-color:#ffffff14;border-color:#434343!important}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner:after{border-color:#ffffff14;border-collapse:separate;animation-name:none}.ant-select-tree-checkbox-disabled+span{color:#ffffff4d;cursor:not-allowed}.ant-select-tree-checkbox-disabled:hover:after,.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-disabled:after{visibility:hidden}.ant-select-tree-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-flex;align-items:baseline;line-height:unset;cursor:pointer}.ant-select-tree-checkbox-wrapper:after{display:inline-block;width:0;overflow:hidden;content:" "}.ant-select-tree-checkbox-wrapper.ant-select-tree-checkbox-wrapper-disabled{cursor:not-allowed}.ant-select-tree-checkbox-wrapper+.ant-select-tree-checkbox-wrapper{margin-left:8px}.ant-select-tree-checkbox+span{padding-right:8px;padding-left:8px}.ant-select-tree-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-select-tree-checkbox-group-item{margin-right:8px}.ant-select-tree-checkbox-group-item:last-child{margin-right:0}.ant-select-tree-checkbox-group-item+.ant-select-tree-checkbox-group-item{margin-left:0}.ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner{background-color:transparent;border-color:#434343}.ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#177ddc;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-select-tree-checkbox-indeterminate.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner:after{background-color:#ffffff4d;border-color:#ffffff4d}.ant-tree-select-dropdown{padding:8px 4px}.ant-tree-select-dropdown-rtl{direction:rtl}.ant-tree-select-dropdown .ant-select-tree{border-radius:0}.ant-tree-select-dropdown .ant-select-tree-list-holder-inner{align-items:stretch}.ant-tree-select-dropdown .ant-select-tree-list-holder-inner .ant-select-tree-treenode .ant-select-tree-node-content-wrapper{flex:auto}.ant-select-tree{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";background:transparent;border-radius:2px;transition:background-color .3s}.ant-select-tree-focused:not(:hover):not(.ant-select-tree-active-focused){background:#111b26}.ant-select-tree-list-holder-inner{align-items:flex-start}.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner{align-items:stretch}.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-node-content-wrapper{flex:auto}.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-treenode.dragging{position:relative}.ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-treenode.dragging:after{position:absolute;top:0;right:0;bottom:4px;left:0;border:1px solid #177ddc;opacity:0;animation:ant-tree-node-fx-do-not-use .3s;animation-play-state:running;animation-fill-mode:forwards;content:"";pointer-events:none}.ant-select-tree .ant-select-tree-treenode{display:flex;align-items:flex-start;padding:0 0 4px;outline:none}.ant-select-tree .ant-select-tree-treenode-disabled .ant-select-tree-node-content-wrapper{color:#ffffff4d;cursor:not-allowed}.ant-select-tree .ant-select-tree-treenode-disabled .ant-select-tree-node-content-wrapper:hover{background:transparent}.ant-select-tree .ant-select-tree-treenode-active .ant-select-tree-node-content-wrapper{background:rgba(255,255,255,.08)}.ant-select-tree .ant-select-tree-treenode:not(.ant-select-tree .ant-select-tree-treenode-disabled).filter-node .ant-select-tree-title{color:inherit;font-weight:500}.ant-select-tree-indent{align-self:stretch;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-select-tree-indent-unit{display:inline-block;width:24px}.ant-select-tree-draggable-icon{width:24px;line-height:24px;text-align:center;opacity:.2;transition:opacity .3s}.ant-select-tree-treenode:hover .ant-select-tree-draggable-icon{opacity:.45}.ant-select-tree-switcher{position:relative;flex:none;align-self:stretch;width:24px;margin:0;line-height:24px;text-align:center;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-select-tree-switcher .ant-tree-switcher-icon,.ant-select-tree-switcher .ant-select-tree-switcher-icon{display:inline-block;font-size:10px;vertical-align:baseline}.ant-select-tree-switcher .ant-tree-switcher-icon svg,.ant-select-tree-switcher .ant-select-tree-switcher-icon svg{transition:transform .3s}.ant-select-tree-switcher-noop{cursor:default}.ant-select-tree-switcher_close .ant-select-tree-switcher-icon svg{transform:rotate(-90deg)}.ant-select-tree-switcher-loading-icon{color:#177ddc}.ant-select-tree-switcher-leaf-line{position:relative;z-index:1;display:inline-block;width:100%;height:100%}.ant-select-tree-switcher-leaf-line:before{position:absolute;top:0;right:12px;bottom:-4px;margin-left:-1px;border-right:1px solid #d9d9d9;content:" "}.ant-select-tree-switcher-leaf-line:after{position:absolute;width:10px;height:14px;border-bottom:1px solid #d9d9d9;content:" "}.ant-select-tree-checkbox{top:initial;margin:4px 8px 0 0}.ant-select-tree .ant-select-tree-node-content-wrapper{position:relative;z-index:auto;min-height:24px;margin:0;padding:0 4px;color:inherit;line-height:24px;background:transparent;border-radius:2px;cursor:pointer;transition:all .3s,border 0s,line-height 0s,box-shadow 0s}.ant-select-tree .ant-select-tree-node-content-wrapper:hover{background-color:#ffffff14}.ant-select-tree .ant-select-tree-node-content-wrapper.ant-select-tree-node-selected{background-color:#11263c}.ant-select-tree .ant-select-tree-node-content-wrapper .ant-select-tree-iconEle{display:inline-block;width:24px;height:24px;line-height:24px;text-align:center;vertical-align:top}.ant-select-tree .ant-select-tree-node-content-wrapper .ant-select-tree-iconEle:empty{display:none}.ant-select-tree-unselectable .ant-select-tree-node-content-wrapper:hover{background-color:transparent}.ant-select-tree-node-content-wrapper{line-height:24px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-select-tree-node-content-wrapper .ant-tree-drop-indicator{position:absolute;z-index:1;height:2px;background-color:#177ddc;border-radius:1px;pointer-events:none}.ant-select-tree-node-content-wrapper .ant-tree-drop-indicator:after{position:absolute;top:-3px;left:-6px;width:8px;height:8px;background-color:transparent;border:2px solid #177ddc;border-radius:50%;content:""}.ant-select-tree .ant-select-tree-treenode.drop-container>[draggable]{box-shadow:0 0 0 2px #177ddc}.ant-select-tree-show-line .ant-select-tree-indent-unit{position:relative;height:100%}.ant-select-tree-show-line .ant-select-tree-indent-unit:before{position:absolute;top:0;right:12px;bottom:-4px;border-right:1px solid #434343;content:""}.ant-select-tree-show-line .ant-select-tree-indent-unit-end:before{display:none}.ant-select-tree-show-line .ant-select-tree-switcher{background:#141414}.ant-select-tree-show-line .ant-select-tree-switcher-line-icon{vertical-align:-.15em}.ant-select-tree .ant-select-tree-treenode-leaf-last .ant-select-tree-switcher-leaf-line:before{top:auto!important;bottom:auto!important;height:14px!important}.ant-tree-select-dropdown-rtl .ant-select-tree .ant-select-tree-switcher_close .ant-select-tree-switcher-icon svg{transform:rotate(90deg)}.ant-tree-select-dropdown-rtl .ant-select-tree .ant-select-tree-switcher-loading-icon{transform:scaleY(-1)}/*!***************************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/typography/style/index.less ***! + \\***************************************************************************************************************************************************************************************************************************************************************/.ant-typography{color:#ffffffd9;overflow-wrap:break-word}.ant-typography.ant-typography-secondary{color:#ffffff73}.ant-typography.ant-typography-success{color:#49aa19}.ant-typography.ant-typography-warning{color:#d89614}.ant-typography.ant-typography-danger{color:#a61d24}a.ant-typography.ant-typography-danger:active,a.ant-typography.ant-typography-danger:focus,a.ant-typography.ant-typography-danger:hover{color:#b33b3d}.ant-typography.ant-typography-disabled{color:#ffffff4d;cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;user-select:none}div.ant-typography,.ant-typography p{margin-bottom:1em}h1.ant-typography,.ant-typography h1{margin-bottom:.5em;color:#ffffffd9;font-weight:600;font-size:38px;line-height:1.23}h2.ant-typography,.ant-typography h2{margin-bottom:.5em;color:#ffffffd9;font-weight:600;font-size:30px;line-height:1.35}h3.ant-typography,.ant-typography h3{margin-bottom:.5em;color:#ffffffd9;font-weight:600;font-size:24px;line-height:1.35}h4.ant-typography,.ant-typography h4{margin-bottom:.5em;color:#ffffffd9;font-weight:600;font-size:20px;line-height:1.4}h5.ant-typography,.ant-typography h5{margin-bottom:.5em;color:#ffffffd9;font-weight:600;font-size:16px;line-height:1.5}.ant-typography+h1.ant-typography,.ant-typography+h2.ant-typography,.ant-typography+h3.ant-typography,.ant-typography+h4.ant-typography,.ant-typography+h5.ant-typography{margin-top:1.2em}.ant-typography div+h1,.ant-typography ul+h1,.ant-typography li+h1,.ant-typography p+h1,.ant-typography h1+h1,.ant-typography h2+h1,.ant-typography h3+h1,.ant-typography h4+h1,.ant-typography h5+h1,.ant-typography div+h2,.ant-typography ul+h2,.ant-typography li+h2,.ant-typography p+h2,.ant-typography h1+h2,.ant-typography h2+h2,.ant-typography h3+h2,.ant-typography h4+h2,.ant-typography h5+h2,.ant-typography div+h3,.ant-typography ul+h3,.ant-typography li+h3,.ant-typography p+h3,.ant-typography h1+h3,.ant-typography h2+h3,.ant-typography h3+h3,.ant-typography h4+h3,.ant-typography h5+h3,.ant-typography div+h4,.ant-typography ul+h4,.ant-typography li+h4,.ant-typography p+h4,.ant-typography h1+h4,.ant-typography h2+h4,.ant-typography h3+h4,.ant-typography h4+h4,.ant-typography h5+h4,.ant-typography div+h5,.ant-typography ul+h5,.ant-typography li+h5,.ant-typography p+h5,.ant-typography h1+h5,.ant-typography h2+h5,.ant-typography h3+h5,.ant-typography h4+h5,.ant-typography h5+h5{margin-top:1.2em}a.ant-typography-ellipsis,span.ant-typography-ellipsis{display:inline-block;max-width:100%}a.ant-typography,.ant-typography a{color:#177ddc;outline:none;cursor:pointer;transition:color .3s;text-decoration:none}a.ant-typography:focus,.ant-typography a:focus,a.ant-typography:hover,.ant-typography a:hover{color:#165996}a.ant-typography:active,.ant-typography a:active{color:#388ed3}a.ant-typography:active,.ant-typography a:active,a.ant-typography:hover,.ant-typography a:hover{text-decoration:none}a.ant-typography[disabled],.ant-typography a[disabled],a.ant-typography.ant-typography-disabled,.ant-typography a.ant-typography-disabled{color:#ffffff4d;cursor:not-allowed}a.ant-typography[disabled]:active,.ant-typography a[disabled]:active,a.ant-typography.ant-typography-disabled:active,.ant-typography a.ant-typography-disabled:active,a.ant-typography[disabled]:hover,.ant-typography a[disabled]:hover,a.ant-typography.ant-typography-disabled:hover,.ant-typography a.ant-typography-disabled:hover{color:#ffffff4d}a.ant-typography[disabled]:active,.ant-typography a[disabled]:active,a.ant-typography.ant-typography-disabled:active,.ant-typography a.ant-typography-disabled:active{pointer-events:none}.ant-typography code{margin:0 .2em;padding:.2em .4em .1em;font-size:85%;background:rgba(150,150,150,.1);border:1px solid rgba(100,100,100,.2);border-radius:3px}.ant-typography kbd{margin:0 .2em;padding:.15em .4em .1em;font-size:90%;background:rgba(150,150,150,.06);border:1px solid rgba(100,100,100,.2);border-bottom-width:2px;border-radius:3px}.ant-typography mark{padding:0;background-color:#594214}.ant-typography u,.ant-typography ins{text-decoration:underline;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}.ant-typography s,.ant-typography del{text-decoration:line-through}.ant-typography strong{font-weight:600}.ant-typography-expand,.ant-typography-edit,.ant-typography-copy{color:#177ddc;text-decoration:none;outline:none;cursor:pointer;transition:color .3s;margin-left:4px}.ant-typography-expand:focus,.ant-typography-edit:focus,.ant-typography-copy:focus,.ant-typography-expand:hover,.ant-typography-edit:hover,.ant-typography-copy:hover{color:#165996}.ant-typography-expand:active,.ant-typography-edit:active,.ant-typography-copy:active{color:#388ed3}.ant-typography-copy-success,.ant-typography-copy-success:hover,.ant-typography-copy-success:focus{color:#49aa19}.ant-typography-edit-content{position:relative}div.ant-typography-edit-content{left:-12px;margin-top:-5px;margin-bottom:calc(1em - 5px)}.ant-typography-edit-content-confirm{position:absolute;right:10px;bottom:8px;color:#ffffff73;pointer-events:none}.ant-typography-edit-content textarea{-moz-transition:none}.ant-typography ul,.ant-typography ol{margin:0 0 1em;padding:0}.ant-typography ul li,.ant-typography ol li{margin:0 0 0 20px;padding:0 0 0 4px}.ant-typography ul{list-style-type:circle}.ant-typography ul ul{list-style-type:disc}.ant-typography ol{list-style-type:decimal}.ant-typography pre,.ant-typography blockquote{margin:1em 0}.ant-typography pre{padding:.4em .6em;white-space:pre-wrap;word-wrap:break-word;background:rgba(150,150,150,.1);border:1px solid rgba(100,100,100,.2);border-radius:3px}.ant-typography pre code{display:inline;margin:0;padding:0;font-size:inherit;font-family:inherit;background:transparent;border:0}.ant-typography blockquote{padding:0 0 0 .6em;border-left:4px solid rgba(100,100,100,.2);opacity:.85}.ant-typography-single-line{white-space:nowrap}.ant-typography-ellipsis-single-line{overflow:hidden;text-overflow:ellipsis}a.ant-typography-ellipsis-single-line,span.ant-typography-ellipsis-single-line{vertical-align:bottom}.ant-typography-ellipsis-multiple-line{display:-webkit-box;overflow:hidden;-webkit-line-clamp:3;-webkit-box-orient:vertical}.ant-typography-rtl{direction:rtl}.ant-typography-rtl .ant-typography-expand,.ant-typography-rtl .ant-typography-edit,.ant-typography-rtl .ant-typography-copy{margin-right:4px;margin-left:0}.ant-typography-rtl .ant-typography-expand{float:left}div.ant-typography-edit-content.ant-typography-rtl{right:-12px;left:auto}.ant-typography-rtl .ant-typography-edit-content-confirm{right:auto;left:10px}.ant-typography-rtl.ant-typography ul li,.ant-typography-rtl.ant-typography ol li{margin:0 20px 0 0;padding:0 4px 0 0}/*!***********************************************************************************************************************************************************************************************************************************************************!*\\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[4].use[2]!./node_modules/less-loader/dist/cjs.js??ruleSet[1].rules[4].use[3]!./components/upload/style/index.less ***! + \\***********************************************************************************************************************************************************************************************************************************************************/.ant-upload{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";outline:0}.ant-upload p{margin:0}.ant-upload-btn{display:block;width:100%;outline:none}.ant-upload input[type=file]{cursor:pointer}.ant-upload.ant-upload-select{display:inline-block}.ant-upload.ant-upload-disabled{cursor:not-allowed}.ant-upload.ant-upload-select-picture-card{width:104px;height:104px;margin-right:8px;margin-bottom:8px;text-align:center;vertical-align:top;background-color:#ffffff0a;border:1px dashed #434343;border-radius:2px;cursor:pointer;transition:border-color .3s}.ant-upload.ant-upload-select-picture-card>.ant-upload{display:flex;align-items:center;justify-content:center;height:100%;text-align:center}.ant-upload.ant-upload-select-picture-card:hover{border-color:#177ddc}.ant-upload-disabled.ant-upload.ant-upload-select-picture-card:hover{border-color:#434343}.ant-upload.ant-upload-drag{position:relative;width:100%;height:100%;text-align:center;background:rgba(255,255,255,.04);border:1px dashed #434343;border-radius:2px;cursor:pointer;transition:border-color .3s}.ant-upload.ant-upload-drag .ant-upload{padding:16px 0}.ant-upload.ant-upload-drag.ant-upload-drag-hover:not(.ant-upload-disabled){border-color:#388ed3}.ant-upload.ant-upload-drag.ant-upload-disabled{cursor:not-allowed}.ant-upload.ant-upload-drag .ant-upload-btn{display:table;height:100%}.ant-upload.ant-upload-drag .ant-upload-drag-container{display:table-cell;vertical-align:middle}.ant-upload.ant-upload-drag:not(.ant-upload-disabled):hover{border-color:#165996}.ant-upload.ant-upload-drag p.ant-upload-drag-icon{margin-bottom:20px}.ant-upload.ant-upload-drag p.ant-upload-drag-icon .anticon{color:#165996;font-size:48px}.ant-upload.ant-upload-drag p.ant-upload-text{margin:0 0 4px;color:#ffffffd9;font-size:16px}.ant-upload.ant-upload-drag p.ant-upload-hint{color:#ffffff73;font-size:14px}.ant-upload.ant-upload-drag .anticon-plus{color:#ffffff4d;font-size:30px;transition:all .3s}.ant-upload.ant-upload-drag .anticon-plus:hover,.ant-upload.ant-upload-drag:hover .anticon-plus{color:#ffffff73}.ant-upload-picture-card-wrapper{display:inline-block;width:100%}.ant-upload-picture-card-wrapper:before{display:table;content:""}.ant-upload-picture-card-wrapper:after{display:table;clear:both;content:""}.ant-upload-list{box-sizing:border-box;margin:0;padding:0;color:#ffffffd9;font-size:14px;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";line-height:1.5715}.ant-upload-list:before{display:table;content:""}.ant-upload-list:after{display:table;clear:both;content:""}.ant-upload-list-item{position:relative;height:22.001px;margin-top:8px;font-size:14px}.ant-upload-list-item-name{display:inline-block;width:100%;padding-left:22px;overflow:hidden;line-height:1.5715;white-space:nowrap;text-overflow:ellipsis}.ant-upload-list-item-card-actions{position:absolute;right:0}.ant-upload-list-item-card-actions-btn{opacity:0}.ant-upload-list-item-card-actions-btn.ant-btn-sm{height:20px;line-height:1}.ant-upload-list-item-card-actions.picture{top:22px;line-height:0}.ant-upload-list-item-card-actions-btn:focus,.ant-upload-list-item-card-actions.picture .ant-upload-list-item-card-actions-btn{opacity:1}.ant-upload-list-item-card-actions .anticon{color:#ffffff73}.ant-upload-list-item-info{height:100%;padding:0 4px;transition:background-color .3s}.ant-upload-list-item-info>span{display:block;width:100%;height:100%}.ant-upload-list-item-info .anticon-loading .anticon,.ant-upload-list-item-info .ant-upload-text-icon .anticon{position:absolute;top:5px;color:#ffffff73;font-size:14px}.ant-upload-list-item .anticon-close{position:absolute;top:6px;right:4px;color:#ffffff73;font-size:10px;line-height:0;cursor:pointer;opacity:0;transition:all .3s}.ant-upload-list-item .anticon-close:hover{color:#ffffffd9}.ant-upload-list-item:hover .ant-upload-list-item-info{background-color:#ffffff14}.ant-upload-list-item:hover .anticon-close,.ant-upload-list-item:hover .ant-upload-list-item-card-actions-btn{opacity:1}.ant-upload-list-item-error,.ant-upload-list-item-error .ant-upload-text-icon>.anticon,.ant-upload-list-item-error .ant-upload-list-item-name{color:#a61d24}.ant-upload-list-item-error .ant-upload-list-item-card-actions .anticon{color:#a61d24}.ant-upload-list-item-error .ant-upload-list-item-card-actions-btn{opacity:1}.ant-upload-list-item-progress{position:absolute;bottom:-12px;width:100%;padding-left:26px;font-size:14px;line-height:0}.ant-upload-list-picture .ant-upload-list-item,.ant-upload-list-picture-card .ant-upload-list-item{position:relative;height:66px;padding:8px;border:1px solid #434343;border-radius:2px}.ant-upload-list-picture .ant-upload-list-item:hover,.ant-upload-list-picture-card .ant-upload-list-item:hover{background:transparent}.ant-upload-list-picture .ant-upload-list-item-error,.ant-upload-list-picture-card .ant-upload-list-item-error{border-color:#a61d24}.ant-upload-list-picture .ant-upload-list-item:hover .ant-upload-list-item-info,.ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info{background:transparent}.ant-upload-list-picture .ant-upload-list-item-uploading,.ant-upload-list-picture-card .ant-upload-list-item-uploading{border-style:dashed}.ant-upload-list-picture .ant-upload-list-item-thumbnail,.ant-upload-list-picture-card .ant-upload-list-item-thumbnail{width:48px;height:48px;line-height:60px;text-align:center;opacity:.8}.ant-upload-list-picture .ant-upload-list-item-thumbnail .anticon,.ant-upload-list-picture-card .ant-upload-list-item-thumbnail .anticon{font-size:26px}.ant-upload-list-picture .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill="#e6f7ff"],.ant-upload-list-picture-card .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill="#e6f7ff"]{fill:#2a1215}.ant-upload-list-picture .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill="#1890ff"],.ant-upload-list-picture-card .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill="#1890ff"]{fill:#a61d24}.ant-upload-list-picture .ant-upload-list-item-icon,.ant-upload-list-picture-card .ant-upload-list-item-icon{position:absolute;top:50%;left:50%;font-size:26px;transform:translate(-50%,-50%)}.ant-upload-list-picture .ant-upload-list-item-icon .anticon,.ant-upload-list-picture-card .ant-upload-list-item-icon .anticon{font-size:26px}.ant-upload-list-picture .ant-upload-list-item-image,.ant-upload-list-picture-card .ant-upload-list-item-image{max-width:100%}.ant-upload-list-picture .ant-upload-list-item-thumbnail img,.ant-upload-list-picture-card .ant-upload-list-item-thumbnail img{display:block;width:48px;height:48px;overflow:hidden}.ant-upload-list-picture .ant-upload-list-item-name,.ant-upload-list-picture-card .ant-upload-list-item-name{display:inline-block;box-sizing:border-box;max-width:100%;margin:0 0 0 8px;padding-right:8px;padding-left:48px;overflow:hidden;line-height:44px;white-space:nowrap;text-overflow:ellipsis;transition:all .3s}.ant-upload-list-picture .ant-upload-list-item-uploading .ant-upload-list-item-name,.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-name{margin-bottom:12px}.ant-upload-list-picture .ant-upload-list-item-progress,.ant-upload-list-picture-card .ant-upload-list-item-progress{bottom:14px;width:calc(100% - 24px);margin-top:0;padding-left:56px}.ant-upload-list-picture .anticon-close,.ant-upload-list-picture-card .anticon-close{position:absolute;top:8px;right:8px;line-height:1;opacity:1}.ant-upload-list-picture-card-container{display:inline-block;width:104px;height:104px;margin:0 8px 8px 0;vertical-align:top}.ant-upload-list-picture-card.ant-upload-list:after{display:none}.ant-upload-list-picture-card .ant-upload-list-item{height:100%;margin:0}.ant-upload-list-picture-card .ant-upload-list-item-info{position:relative;height:100%;overflow:hidden}.ant-upload-list-picture-card .ant-upload-list-item-info:before{position:absolute;z-index:1;width:100%;height:100%;background-color:#00000080;opacity:0;transition:all .3s;content:" "}.ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info:before{opacity:1}.ant-upload-list-picture-card .ant-upload-list-item-actions{position:absolute;top:50%;left:50%;z-index:10;white-space:nowrap;transform:translate(-50%,-50%);opacity:0;transition:all .3s}.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete{z-index:10;width:16px;margin:0 4px;color:#ffffffd9;font-size:16px;cursor:pointer;transition:all .3s}.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye:hover,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download:hover,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete:hover{color:#fff}.ant-upload-list-picture-card .ant-upload-list-item-info:hover+.ant-upload-list-item-actions,.ant-upload-list-picture-card .ant-upload-list-item-actions:hover{opacity:1}.ant-upload-list-picture-card .ant-upload-list-item-thumbnail,.ant-upload-list-picture-card .ant-upload-list-item-thumbnail img{position:static;display:block;width:100%;height:100%;-o-object-fit:contain;object-fit:contain}.ant-upload-list-picture-card .ant-upload-list-item-name{display:none;margin:8px 0 0;padding:0;line-height:1.5715;text-align:center}.ant-upload-list-picture-card .ant-upload-list-item-file+.ant-upload-list-item-name{position:absolute;bottom:10px;display:block}.ant-upload-list-picture-card .ant-upload-list-item-uploading.ant-upload-list-item{background-color:#ffffff0a}.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info{height:auto}.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info:before,.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-eye,.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-delete{display:none}.ant-upload-list-picture-card .ant-upload-list-item-progress{bottom:32px;width:calc(100% - 14px);padding-left:0}.ant-upload-list-text-container,.ant-upload-list-picture-container{transition:opacity .3s,height .3s}.ant-upload-list-text-container:before,.ant-upload-list-picture-container:before{display:table;width:0;height:0;content:""}.ant-upload-list-text-container .ant-upload-span,.ant-upload-list-picture-container .ant-upload-span{display:block;flex:auto}.ant-upload-list-text .ant-upload-span,.ant-upload-list-picture .ant-upload-span{display:flex;align-items:center}.ant-upload-list-text .ant-upload-span>*,.ant-upload-list-picture .ant-upload-span>*{flex:none}.ant-upload-list-text .ant-upload-list-item-name,.ant-upload-list-picture .ant-upload-list-item-name{flex:auto;margin:0;padding:0 8px}.ant-upload-list-text .ant-upload-list-item-card-actions,.ant-upload-list-picture .ant-upload-list-item-card-actions,.ant-upload-list-text .ant-upload-text-icon .anticon{position:static}.ant-upload-list .ant-upload-animate-inline-appear,.ant-upload-list .ant-upload-animate-inline-enter,.ant-upload-list .ant-upload-animate-inline-leave{animation-duration:.3s;animation-fill-mode:cubic-bezier(.78,.14,.15,.86)}.ant-upload-list .ant-upload-animate-inline-appear,.ant-upload-list .ant-upload-animate-inline-enter{animation-name:uploadAnimateInlineIn}.ant-upload-list .ant-upload-animate-inline-leave{animation-name:uploadAnimateInlineOut}@keyframes uploadAnimateInlineIn{0%{width:0;height:0;margin:0;padding:0;opacity:0}}@keyframes uploadAnimateInlineOut{to{width:0;height:0;margin:0;padding:0;opacity:0}}.ant-upload-rtl{direction:rtl}.ant-upload-rtl.ant-upload.ant-upload-select-picture-card{margin-right:auto;margin-left:8px}.ant-upload-list-rtl{direction:rtl}.ant-upload-list-rtl .ant-upload-list-item-list-type-text:hover .ant-upload-list-item-name-icon-count-1{padding-right:22px;padding-left:14px}.ant-upload-list-rtl .ant-upload-list-item-list-type-text:hover .ant-upload-list-item-name-icon-count-2{padding-right:22px;padding-left:28px}.ant-upload-list-rtl .ant-upload-list-item-name{padding-right:22px;padding-left:0}.ant-upload-list-rtl .ant-upload-list-item-name-icon-count-1{padding-left:14px}.ant-upload-list-rtl .ant-upload-list-item-card-actions{right:auto;left:0}.ant-upload-list-rtl .ant-upload-list-item-card-actions .anticon{padding-right:0;padding-left:5px}.ant-upload-list-rtl .ant-upload-list-item-info{padding:0 4px 0 12px}.ant-upload-list-rtl .ant-upload-list-item .anticon-close{right:auto;left:4px}.ant-upload-list-rtl .ant-upload-list-item-error .ant-upload-list-item-card-actions .anticon{padding-right:0;padding-left:5px}.ant-upload-list-rtl .ant-upload-list-item-progress{padding-right:26px;padding-left:0}.ant-upload-list-picture .ant-upload-list-item-info,.ant-upload-list-picture-card .ant-upload-list-item-info{padding:0}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-thumbnail,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-thumbnail{right:8px;left:auto}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-icon,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-icon{right:50%;left:auto;transform:translate(50%,-50%)}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-name,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-name{margin:0 8px 0 0;padding-right:48px;padding-left:8px}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-name-icon-count-1,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-name-icon-count-1{padding-right:48px;padding-left:18px}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-name-icon-count-2,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-name-icon-count-2{padding-right:48px;padding-left:36px}.ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-progress,.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-progress{padding-right:0;padding-left:0}.ant-upload-list-rtl.ant-upload-list-picture .anticon-close,.ant-upload-list-rtl.ant-upload-list-picture-card .anticon-close{right:auto;left:8px}.ant-upload-list-rtl .ant-upload-list-picture-card-container{margin:0 0 8px 8px}.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-actions{right:50%;left:auto;transform:translate(50%,-50%)}.ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-file+.ant-upload-list-item-name{margin:8px 0 0;padding:0} +`;export{t as default}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/batchDownload-08be3fc5.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/batchDownload-08be3fc5.css new file mode 100644 index 0000000000000000000000000000000000000000..12e35668b27ef200160a12fb989c2795df351850 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/batchDownload-08be3fc5.css @@ -0,0 +1 @@ +.container[data-v-aab31da2]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column}.container .actions-panel[data-v-aab31da2]{padding:8px;background-color:var(--zp-primary-background)}.container .file-list[data-v-aab31da2]{flex:1;list-style:none;padding:8px;height:var(--pane-max-height);width:100%}.container .file-list .hint[data-v-aab31da2]{text-align:center;font-size:2em;padding:30vh 128px 0} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/batchDownload-1a890614.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/batchDownload-1a890614.js new file mode 100644 index 0000000000000000000000000000000000000000..8afb88a905f3d12b0570416b09bbd5cebe74985f --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/batchDownload-1a890614.js @@ -0,0 +1 @@ +import{d as v,cy as C,bN as I,S as l,T as _,U as f,c,a1 as r,V as h,W as d,a2 as e,a0 as z,ad as F,b$ as B,cz as $,ai as x,Z as S}from"./index-93a218ca.js";import{u as R,a as V,k as E,F as T,d as A}from"./FileItem-9a94f7dd.js";import"./functionalCallableComp-685da399.js";/* empty css */import"./index-f60e0de3.js";const N={class:"actions-panel actions"},U={key:0,class:"file-list"},L={class:"hint"},H=v({__name:"batchDownload",props:{tabIdx:{},paneIdx:{},id:{}},setup(W){const{stackViewEl:w}=R().toRefs(),{itemSize:p,gridItems:k,cellWidth:b}=V(),n=E(),{selectdFiles:i}=C(n),u=I(),y=async t=>{const s=B(t);s&&n.addFiles(s.nodes)},D=async()=>{u.pushAction(async()=>{const t=await $.value.post("/zip",{paths:i.value.map(o=>o.fullpath)},{responseType:"blob"}),s=window.URL.createObjectURL(new Blob([t.data])),a=document.createElement("a");a.href=s,a.setAttribute("download",`iib_${new Date().toLocaleString()}.zip`),document.body.appendChild(a),a.click()})},g=t=>{i.value.splice(t,1)};return(t,s)=>{const a=x;return l(),_("div",{class:"container",ref_key:"stackViewEl",ref:w,onDrop:y},[f("div",N,[c(a,{onClick:s[0]||(s[0]=o=>e(n).selectdFiles=[])},{default:r(()=>[h(d(t.$t("clear")),1)]),_:1}),c(a,{onClick:D,type:"primary",loading:!e(u).isIdle},{default:r(()=>[h(d(t.$t("zipDownload")),1)]),_:1},8,["loading"])]),e(i).length?(l(),z(e(A),{key:1,ref:"scroller",class:"file-list",items:e(i).slice(),"item-size":e(p).first,"key-field":"fullpath","item-secondary-size":e(p).second,gridItems:e(k)},{default:r(({item:o,index:m})=>[c(T,{idx:m,file:o,"cell-width":e(b),"enable-close-icon":"",onCloseIconClick:j=>g(m),"full-screen-preview-image-url":e(F)(o),"enable-right-click-menu":!1},null,8,["idx","file","cell-width","onCloseIconClick","full-screen-preview-image-url"])]),_:1},8,["items","item-size","item-secondary-size","gridItems"])):(l(),_("div",U,[f("p",L,d(t.$t("batchDownloaDDragAndDropHint")),1)]))],544)}}});const J=S(H,[["__scopeId","data-v-aab31da2"]]);export{J as default}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/emptyStartup-676b6769.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/emptyStartup-676b6769.js new file mode 100644 index 0000000000000000000000000000000000000000..d291b7d9200cbd3ee2601ca76e63f633082ce373 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/emptyStartup-676b6769.js @@ -0,0 +1,3 @@ +import{d as J,E as L,ak as Mr,r as H,m as xe,H as Wr,a as k,c as h,al as le,u as pe,_ as je,am as Kr,an as Tr,P as Z,ao as Jr,h as D,g as Zr,f as Qr,b as Xr,ap as Yr,aq as et,ar as rt,as as tt,j as Er,at as fr,au as st,av as ot,aw as nt,ax as at,A as $e,$ as br,R as ye,z as C,p as I,a7 as Ce,ai as vr,ah as _r,y as ce,ay as it,az as ct,aA as Ee,aB as lt,aC as ut,aD as pt,S as w,a0 as ve,a1 as U,V as $,W as b,aE as dt,aj as gt,M as mt,aF as ht,q as Tt,aG as Et,aH as ft,aI as bt,o as vt,aJ as _t,T as O,U as E,a2 as T,Y as B,a4 as wt,a3 as W,aK as Ke,X as K,a6 as se,O as Oe,L as yt,x as Je,aL as kt,aM as Pt,aN as Gt,aO as Ot,Z as St}from"./index-93a218ca.js";import{V as At,_ as Rt}from"./index-4596f42d.js";/* empty css */import{D as Ct}from"./index-f60e0de3.js";function Ze(r){var e=r.prefixCls,t=r.value,s=r.current,o=r.offset,a=o===void 0?0:o,n;return a&&(n={position:"absolute",top:"".concat(a,"00%"),left:0}),h("p",{style:n,class:le("".concat(e,"-only-unit"),{current:s})},[t])}function Ft(r,e,t){for(var s=r,o=0;(s+10)%10!==e;)s+=t,o+=t;return o}const Ut=J({compatConfig:{MODE:3},name:"SingleNumber",props:{prefixCls:String,value:String,count:Number},setup:function(e){var t=L(function(){return Number(e.value)}),s=L(function(){return Math.abs(e.count)}),o=Mr({prevValue:t.value,prevCount:s.value}),a=function(){o.prevValue=t.value,o.prevCount=s.value},n=H();return xe(t,function(){clearTimeout(n.value),n.value=setTimeout(function(){a()},1e3)},{flush:"post"}),Wr(function(){clearTimeout(n.value)}),function(){var i,l={},p=t.value;if(o.prevValue===p||Number.isNaN(p)||Number.isNaN(o.prevValue))i=[Ze(k(k({},e),{},{current:!0}))],l={transition:"none"};else{i=[];for(var g=p+10,u=[],f=p;f<=g;f+=1)u.push(f);var y=u.findIndex(function(A){return A%10===o.prevValue});i=u.map(function(A,v){var R=A%10;return Ze(k(k({},e),{},{value:R,offset:v-y,current:v===y}))});var S=o.prevCounte.overflowCount?"".concat(e.overflowCount,"+"):e.count}),p=L(function(){return e.status!==null&&e.status!==void 0||e.color!==null&&e.color!==void 0}),g=L(function(){return l.value==="0"||l.value===0}),u=L(function(){return e.dot&&!g.value}),f=L(function(){return u.value?"":l.value}),y=L(function(){var c=f.value===null||f.value===void 0||f.value==="";return(c||g.value&&!e.showZero)&&!u.value}),S=H(e.count),A=H(f.value),v=H(u.value);xe([function(){return e.count},f,u],function(){y.value||(S.value=e.count,A.value=f.value,v.value=u.value)},{immediate:!0});var R=L(function(){var c;return c={},D(c,"".concat(n.value,"-status-dot"),p.value),D(c,"".concat(n.value,"-status-").concat(e.status),!!e.status),D(c,"".concat(n.value,"-status-").concat(e.color),he(e.color)),c}),q=L(function(){return e.color&&!he(e.color)?{background:e.color}:{}}),x=L(function(){var c;return c={},D(c,"".concat(n.value,"-dot"),v.value),D(c,"".concat(n.value,"-count"),!v.value),D(c,"".concat(n.value,"-count-sm"),e.size==="small"),D(c,"".concat(n.value,"-multiple-words"),!v.value&&A.value&&A.value.toString().length>1),D(c,"".concat(n.value,"-status-").concat(e.status),!!e.status),D(c,"".concat(n.value,"-status-").concat(e.color),he(e.color)),c});return function(){var c,m,P,_=e.offset,G=e.title,N=e.color,ee=o.style,Q=Zr(s,e,"text"),j=n.value,V=S.value,M=Qr((c=s.default)===null||c===void 0?void 0:c.call(s));M=M.length?M:null;var re=!!(!y.value||s.count),X=function(){if(!_)return k({},ee);var be={marginTop:qt(_[1])?"".concat(_[1],"px"):_[1]};return i.value==="rtl"?be.left="".concat(parseInt(_[0],10),"px"):be.right="".concat(-parseInt(_[0],10),"px"),k(k({},be),ee)}(),d=G??(typeof V=="string"||typeof V=="number"?V:void 0),F=re||!Q?null:h("span",{class:"".concat(j,"-status-text")},[Q]),te=Xr(V)==="object"||V===void 0&&s.count?Tr(V??((m=s.count)===null||m===void 0?void 0:m.call(s)),{style:X},!1):null,We=le(j,(P={},D(P,"".concat(j,"-status"),p.value),D(P,"".concat(j,"-not-a-wrapper"),!M),D(P,"".concat(j,"-rtl"),i.value==="rtl"),P),o.class);if(!M&&p.value){var Nr=X.color;return h("span",k(k({},o),{},{class:We,style:X}),[h("span",{class:R.value,style:q.value},null),h("span",{style:{color:Nr},class:"".concat(j,"-status-text")},[Q])])}var Vr=Yr(M?"".concat(j,"-zoom"):"",{appear:!1}),fe=k(k({},X),e.numberStyle);return N&&!he(N)&&(fe=fe||{},fe.background=N),h("span",k(k({},o),{},{class:We}),[M,h(et,Vr,{default:function(){return[rt(h(It,{prefixCls:e.scrollNumberPrefixCls,show:re,class:x.value,count:A.value,title:d,style:fe,key:"scrollNumber"},{default:function(){return[te]}}),[[tt,re]])]}}),F])}}});_e.install=function(r){return r.component(_e.name,_e),r.component(Fe.name,Fe),r};var Ht=["prefixCls","id"],wr=function(){return{prefixCls:String,checked:{type:Boolean,default:void 0},disabled:{type:Boolean,default:void 0},isGroup:{type:Boolean,default:void 0},value:Z.any,name:String,id:String,autofocus:{type:Boolean,default:void 0},onChange:Function,onFocus:Function,onBlur:Function,onClick:Function,"onUpdate:checked":Function,"onUpdate:value":Function}};const z=J({compatConfig:{MODE:3},name:"ARadio",props:wr(),setup:function(e,t){var s=t.emit,o=t.expose,a=t.slots,n=Er(),i=H(),l=fr("radioGroupContext",void 0),p=pe("radio",e),g=p.prefixCls,u=p.direction,f=function(){i.value.focus()},y=function(){i.value.blur()};o({focus:f,blur:y});var S=function(R){var q=R.target.checked;s("update:checked",q),s("update:value",q),s("change",R),n.onFieldChange()},A=function(R){s("change",R),l&&l.onRadioChange&&l.onRadioChange(R)};return function(){var v,R=l;e.prefixCls;var q=e.id,x=q===void 0?n.id.value:q,c=je(e,Ht),m=k({prefixCls:g.value,id:x},st(c,["onUpdate:checked","onUpdate:value"]));R?(m.name=R.props.name,m.onChange=A,m.checked=e.value===R.stateValue.value,m.disabled=e.disabled||R.props.disabled):m.onChange=S;var P=le((v={},D(v,"".concat(g.value,"-wrapper"),!0),D(v,"".concat(g.value,"-wrapper-checked"),m.checked),D(v,"".concat(g.value,"-wrapper-disabled"),m.disabled),D(v,"".concat(g.value,"-wrapper-rtl"),u.value==="rtl"),v));return h("label",{class:P},[h(At,k(k({},m),{},{type:"radio",ref:i}),null),a.default&&h("span",null,[a.default()])])}}});var zt=nt("large","default","small"),Nt=function(){return{prefixCls:String,value:Z.any,size:Z.oneOf(zt),options:{type:Array},disabled:{type:Boolean,default:void 0},name:String,buttonStyle:{type:String,default:"outline"},id:String,optionType:{type:String,default:"default"},onChange:Function,"onUpdate:value":Function}};const qe=J({compatConfig:{MODE:3},name:"ARadioGroup",props:Nt(),setup:function(e,t){var s=t.slots,o=t.emit,a=Er(),n=pe("radio",e),i=n.prefixCls,l=n.direction,p=n.size,g=H(e.value),u=H(!1);xe(function(){return e.value},function(y){g.value=y,u.value=!1});var f=function(S){var A=g.value,v=S.target.value;"value"in e||(g.value=v),!u.value&&v!==A&&(u.value=!0,o("update:value",v),o("change",S),a.onFieldChange()),at(function(){u.value=!1})};return ot("radioGroupContext",{onRadioChange:f,stateValue:g,props:e}),function(){var y,S=e.options,A=e.optionType,v=e.buttonStyle,R=e.id,q=R===void 0?a.id.value:R,x="".concat(i.value,"-group"),c=le(x,"".concat(x,"-").concat(v),(y={},D(y,"".concat(x,"-").concat(p.value),p.value),D(y,"".concat(x,"-rtl"),l.value==="rtl"),y)),m=null;if(S&&S.length>0){var P=A==="button"?"".concat(i.value,"-button"):i.value;m=S.map(function(G){if(typeof G=="string"||typeof G=="number")return h(z,{key:G,prefixCls:P,disabled:e.disabled,value:G,checked:g.value===G},{default:function(){return[G]}});var N=G.value,ee=G.disabled,Q=G.label;return h(z,{key:"radio-group-value-options-".concat(N),prefixCls:P,disabled:ee||e.disabled,value:N,checked:g.value===N},{default:function(){return[Q]}})})}else{var _;m=(_=s.default)===null||_===void 0?void 0:_.call(s)}return h("div",{class:c,id:q},[m])}}}),Te=J({compatConfig:{MODE:3},name:"ARadioButton",props:wr(),setup:function(e,t){var s=t.slots,o=pe("radio-button",e),a=o.prefixCls,n=fr("radioGroupContext",void 0);return function(){var i,l=k(k({},e),{},{prefixCls:a.value});return n&&(l.onChange=n.onRadioChange,l.checked=l.value===n.stateValue.value,l.disabled=l.disabled||n.props.disabled),h(z,l,{default:function(){return[(i=s.default)===null||i===void 0?void 0:i.call(s)]}})}}});z.Group=qe;z.Button=Te;z.install=function(r){return r.component(z.name,z),r.component(z.Group.name,z.Group),r.component(z.Button.name,z.Button),r};var Vt={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M688 312v-48c0-4.4-3.6-8-8-8H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8zm-392 88c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H296zm376 116c-119.3 0-216 96.7-216 216s96.7 216 216 216 216-96.7 216-216-96.7-216-216-216zm107.5 323.5C750.8 868.2 712.6 884 672 884s-78.8-15.8-107.5-44.5C535.8 810.8 520 772.6 520 732s15.8-78.8 44.5-107.5C593.2 595.8 631.4 580 672 580s78.8 15.8 107.5 44.5C808.2 653.2 824 691.4 824 732s-15.8 78.8-44.5 107.5zM761 656h-44.3c-2.6 0-5 1.2-6.5 3.3l-63.5 87.8-23.1-31.9a7.92 7.92 0 00-6.5-3.3H573c-6.5 0-10.3 7.4-6.5 12.7l73.8 102.1c3.2 4.4 9.7 4.4 12.9 0l114.2-158c3.9-5.3.1-12.7-6.4-12.7zM440 852H208V148h560v344c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V108c0-17.7-14.3-32-32-32H168c-17.7 0-32 14.3-32 32v784c0 17.7 14.3 32 32 32h272c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z"}}]},name:"file-done",theme:"outlined"};const Mt=Vt;function Qe(r){for(var e=1;e{const t=br(),s=H(e??""),o=H(r),a=async()=>{const n=await pt({directory:!0,defaultPath:e});if(typeof n=="string")s.value=n;else return};s.value=await new Promise(n=>{ye.confirm({title:C("inputTargetFolderPath"),width:"800px",content:()=>{var i;return I("div",[(i=t.conf)!=null&&i.enable_access_control?I("a",{style:{"word-break":"break-all","margin-bottom":"4px",display:"block"},target:"_blank",href:"https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/518"},"Please open this link first (Access Control mode only)"):"",Ce?I(vr,{onClick:a,style:{margin:"4px 0"}},C("selectFolder")):"",I(_r,{value:s.value,"onUpdate:value":l=>s.value=l}),I("div",[I("span",C("type")+": "),I(qe,{value:o.value,"onUpdate:value":l=>o.value=l,buttonStyle:"solid",style:{margin:"16px 0 32px"}},[I(Te,{value:"walk"},"Walk"),I(Te,{value:"scanned"},"Normal"),I(Te,{value:"scanned-fixed"},"Fixed")])]),I("p","Walk: "+C("walkModeDoc")),I("p","Normal: "+C("normalModelDoc")),I("p","Fixed: "+C("fixedModeDoc"))])},async onOk(){if(!s.value)throw ce.error(C("pathIsEmpty")),new Error("pathIsEmpty");(await it([s.value]))[s.value]?n(s.value):ce.error(C("pathDoesNotExist"))}})}),ye.confirm({content:C("confirmToAddToExtraPath"),async onOk(){await ct({types:[o.value],path:s.value}),ce.success(C("addCompleted")),Ee.emit("searchIndexExpired"),Ee.emit("updateGlobalSetting")}})},rr=(r,e)=>{ye.confirm({content:C("confirmDelete"),closable:!0,async onOk(){await lt({types:[e],path:r}),ce.success(C("removeCompleted")),Ee.emit("searchIndexExpired"),Ee.emit("updateGlobalSetting")}})},tr=r=>{const e=H("");ye.confirm({title:C("inputAlias"),content:()=>I("div",[I("div",{style:{"word-break":"break-all","margin-bottom":"4px"}},"Path: "+r),I(_r,{value:e.value,"onUpdate:value":t=>e.value=t})]),async onOk(){await ut({alias:e.value,path:r}),ce.success(C("addAliasCompleted")),Ee.emit("updateGlobalSetting")}})},sr=J({__name:"actionContextMenu",emits:["openOnTheRight","openInNewTab"],setup(r,{emit:e}){const t=s=>{switch(s.key.toString()){case"openOnTheRight":e("openOnTheRight");break;case"openInNewTab":e("openInNewTab");break}};return(s,o)=>{const a=gt,n=mt,i=Ct;return w(),ve(i,{trigger:["contextmenu"]},{overlay:U(()=>[h(n,{onClick:t},{default:U(()=>[h(a,{key:"openOnTheRight"},{default:U(()=>[$(b(s.$t("openOnTheRight")),1)]),_:1}),h(a,{key:"openInNewTab"},{default:U(()=>[$(b(s.$t("openInNewTab")),1)]),_:1})]),_:1})]),default:U(()=>[dt(s.$slots,"default")]),_:3})}}});function ke(){return typeof navigator=="object"&&"userAgent"in navigator?navigator.userAgent:typeof process=="object"&&process.version!==void 0?`Node.js/${process.version.substr(1)} (${process.platform}; ${process.arch})`:""}var Pe={exports:{}},ts=yr;function yr(r,e,t,s){if(typeof t!="function")throw new Error("method for before hook must be a function");return s||(s={}),Array.isArray(e)?e.reverse().reduce(function(o,a){return yr.bind(null,r,a,o,s)},t)():Promise.resolve().then(function(){return r.registry[e]?r.registry[e].reduce(function(o,a){return a.hook.bind(null,o,s)},t)():t(s)})}var ss=os;function os(r,e,t,s){var o=s;r.registry[t]||(r.registry[t]=[]),e==="before"&&(s=function(a,n){return Promise.resolve().then(o.bind(null,n)).then(a.bind(null,n))}),e==="after"&&(s=function(a,n){var i;return Promise.resolve().then(a.bind(null,n)).then(function(l){return i=l,o(i,n)}).then(function(){return i})}),e==="error"&&(s=function(a,n){return Promise.resolve().then(a.bind(null,n)).catch(function(i){return o(i,n)})}),r.registry[t].push({hook:s,orig:o})}var ns=as;function as(r,e,t){if(r.registry[e]){var s=r.registry[e].map(function(o){return o.orig}).indexOf(t);s!==-1&&r.registry[e].splice(s,1)}}var kr=ts,is=ss,cs=ns,or=Function.bind,nr=or.bind(or);function Pr(r,e,t){var s=nr(cs,null).apply(null,t?[e,t]:[e]);r.api={remove:s},r.remove=s,["before","error","after","wrap"].forEach(function(o){var a=t?[e,o,t]:[e,o];r[o]=r.api[o]=nr(is,null).apply(null,a)})}function ls(){var r="h",e={registry:{}},t=kr.bind(null,e,r);return Pr(t,e,r),t}function Gr(){var r={registry:{}},e=kr.bind(null,r);return Pr(e,r),e}var ar=!1;function de(){return ar||(console.warn('[before-after-hook]: "Hook()" repurposing warning, use "Hook.Collection()". Read more: https://git.io/upgrade-before-after-hook-to-1.4'),ar=!0),Gr()}de.Singular=ls.bind();de.Collection=Gr.bind();Pe.exports=de;Pe.exports.Hook=de;Pe.exports.Singular=de.Singular;var us=Pe.exports.Collection=de.Collection,ps="9.0.5",ds=`octokit-endpoint.js/${ps} ${ke()}`,gs={method:"GET",baseUrl:"https://api.github.com",headers:{accept:"application/vnd.github.v3+json","user-agent":ds},mediaType:{format:""}};function ms(r){return r?Object.keys(r).reduce((e,t)=>(e[t.toLowerCase()]=r[t],e),{}):{}}function hs(r){if(typeof r!="object"||r===null||Object.prototype.toString.call(r)!=="[object Object]")return!1;const e=Object.getPrototypeOf(r);if(e===null)return!0;const t=Object.prototype.hasOwnProperty.call(e,"constructor")&&e.constructor;return typeof t=="function"&&t instanceof t&&Function.prototype.call(t)===Function.prototype.call(r)}function Or(r,e){const t=Object.assign({},r);return Object.keys(e).forEach(s=>{hs(e[s])?s in r?t[s]=Or(r[s],e[s]):Object.assign(t,{[s]:e[s]}):Object.assign(t,{[s]:e[s]})}),t}function ir(r){for(const e in r)r[e]===void 0&&delete r[e];return r}function Ue(r,e,t){var o;if(typeof e=="string"){let[a,n]=e.split(" ");t=Object.assign(n?{method:a,url:n}:{url:a},t)}else t=Object.assign({},e);t.headers=ms(t.headers),ir(t),ir(t.headers);const s=Or(r||{},t);return t.url==="/graphql"&&(r&&((o=r.mediaType.previews)!=null&&o.length)&&(s.mediaType.previews=r.mediaType.previews.filter(a=>!s.mediaType.previews.includes(a)).concat(s.mediaType.previews)),s.mediaType.previews=(s.mediaType.previews||[]).map(a=>a.replace(/-preview/,""))),s}function Ts(r,e){const t=/\?/.test(r)?"&":"?",s=Object.keys(e);return s.length===0?r:r+t+s.map(o=>o==="q"?"q="+e.q.split("+").map(encodeURIComponent).join("+"):`${o}=${encodeURIComponent(e[o])}`).join("&")}var Es=/\{[^}]+\}/g;function fs(r){return r.replace(/^\W+|\W+$/g,"").split(/,/)}function bs(r){const e=r.match(Es);return e?e.map(fs).reduce((t,s)=>t.concat(s),[]):[]}function cr(r,e){const t={__proto__:null};for(const s of Object.keys(r))e.indexOf(s)===-1&&(t[s]=r[s]);return t}function Sr(r){return r.split(/(%[0-9A-Fa-f]{2})/g).map(function(e){return/%[0-9A-Fa-f]/.test(e)||(e=encodeURI(e).replace(/%5B/g,"[").replace(/%5D/g,"]")),e}).join("")}function ie(r){return encodeURIComponent(r).replace(/[!'()*]/g,function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()})}function ge(r,e,t){return e=r==="+"||r==="#"?Sr(e):ie(e),t?ie(t)+"="+e:e}function oe(r){return r!=null}function Ae(r){return r===";"||r==="&"||r==="?"}function vs(r,e,t,s){var o=r[t],a=[];if(oe(o)&&o!=="")if(typeof o=="string"||typeof o=="number"||typeof o=="boolean")o=o.toString(),s&&s!=="*"&&(o=o.substring(0,parseInt(s,10))),a.push(ge(e,o,Ae(e)?t:""));else if(s==="*")Array.isArray(o)?o.filter(oe).forEach(function(n){a.push(ge(e,n,Ae(e)?t:""))}):Object.keys(o).forEach(function(n){oe(o[n])&&a.push(ge(e,o[n],n))});else{const n=[];Array.isArray(o)?o.filter(oe).forEach(function(i){n.push(ge(e,i))}):Object.keys(o).forEach(function(i){oe(o[i])&&(n.push(ie(i)),n.push(ge(e,o[i].toString())))}),Ae(e)?a.push(ie(t)+"="+n.join(",")):n.length!==0&&a.push(n.join(","))}else e===";"?oe(o)&&a.push(ie(t)):o===""&&(e==="&"||e==="?")?a.push(ie(t)+"="):o===""&&a.push("");return a}function _s(r){return{expand:ws.bind(null,r)}}function ws(r,e){var t=["+","#",".","/",";","?","&"];return r=r.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g,function(s,o,a){if(o){let i="";const l=[];if(t.indexOf(o.charAt(0))!==-1&&(i=o.charAt(0),o=o.substr(1)),o.split(/,/g).forEach(function(p){var g=/([^:\*]*)(?::(\d+)|(\*))?/.exec(p);l.push(vs(e,i,g[1],g[2]||g[3]))}),i&&i!=="+"){var n=",";return i==="?"?n="&":i!=="#"&&(n=i),(l.length!==0?i:"")+l.join(n)}else return l.join(",")}else return Sr(a)}),r==="/"?r:r.replace(/\/$/,"")}function Ar(r){var g;let e=r.method.toUpperCase(),t=(r.url||"/").replace(/:([a-z]\w+)/g,"{$1}"),s=Object.assign({},r.headers),o,a=cr(r,["method","baseUrl","url","headers","request","mediaType"]);const n=bs(t);t=_s(t).expand(a),/^http/.test(t)||(t=r.baseUrl+t);const i=Object.keys(r).filter(u=>n.includes(u)).concat("baseUrl"),l=cr(a,i);if(!/application\/octet-stream/i.test(s.accept)&&(r.mediaType.format&&(s.accept=s.accept.split(/,/).map(u=>u.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/,`application/vnd$1$2.${r.mediaType.format}`)).join(",")),t.endsWith("/graphql")&&(g=r.mediaType.previews)!=null&&g.length)){const u=s.accept.match(/[\w-]+(?=-preview)/g)||[];s.accept=u.concat(r.mediaType.previews).map(f=>{const y=r.mediaType.format?`.${r.mediaType.format}`:"+json";return`application/vnd.github.${f}-preview${y}`}).join(",")}return["GET","HEAD"].includes(e)?t=Ts(t,l):"data"in l?o=l.data:Object.keys(l).length&&(o=l),!s["content-type"]&&typeof o<"u"&&(s["content-type"]="application/json; charset=utf-8"),["PATCH","PUT"].includes(e)&&typeof o>"u"&&(o=""),Object.assign({method:e,url:t,headers:s},typeof o<"u"?{body:o}:null,r.request?{request:r.request}:null)}function ys(r,e,t){return Ar(Ue(r,e,t))}function Rr(r,e){const t=Ue(r,e),s=ys.bind(null,t);return Object.assign(s,{DEFAULTS:t,defaults:Rr.bind(null,t),merge:Ue.bind(null,t),parse:Ar})}var ks=Rr(null,gs);class lr extends Error{constructor(e){super(e),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name="Deprecation"}}var Ne={exports:{}},Ps=Cr;function Cr(r,e){if(r&&e)return Cr(r)(e);if(typeof r!="function")throw new TypeError("need wrapper function");return Object.keys(r).forEach(function(s){t[s]=r[s]}),t;function t(){for(var s=new Array(arguments.length),o=0;oconsole.warn(r)),Ss=Dr(r=>console.warn(r)),me=class extends Error{constructor(r,e,t){super(r),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name="HttpError",this.status=e;let s;"headers"in t&&typeof t.headers<"u"&&(s=t.headers),"response"in t&&(this.response=t.response,s=t.response.headers);const o=Object.assign({},t.request);t.request.headers.authorization&&(o.headers=Object.assign({},t.request.headers,{authorization:t.request.headers.authorization.replace(/ .*$/," [REDACTED]")})),o.url=o.url.replace(/\bclient_secret=\w+/g,"client_secret=[REDACTED]").replace(/\baccess_token=\w+/g,"access_token=[REDACTED]"),this.request=o,Object.defineProperty(this,"code",{get(){return Os(new lr("[@octokit/request-error] `error.code` is deprecated, use `error.status`.")),e}}),Object.defineProperty(this,"headers",{get(){return Ss(new lr("[@octokit/request-error] `error.headers` is deprecated, use `error.response.headers`.")),s||{}}})}},As="8.4.0";function Rs(r){if(typeof r!="object"||r===null||Object.prototype.toString.call(r)!=="[object Object]")return!1;const e=Object.getPrototypeOf(r);if(e===null)return!0;const t=Object.prototype.hasOwnProperty.call(e,"constructor")&&e.constructor;return typeof t=="function"&&t instanceof t&&Function.prototype.call(t)===Function.prototype.call(r)}function Cs(r){return r.arrayBuffer()}function ur(r){var i,l,p,g;const e=r.request&&r.request.log?r.request.log:console,t=((i=r.request)==null?void 0:i.parseSuccessResponseBody)!==!1;(Rs(r.body)||Array.isArray(r.body))&&(r.body=JSON.stringify(r.body));let s={},o,a,{fetch:n}=globalThis;if((l=r.request)!=null&&l.fetch&&(n=r.request.fetch),!n)throw new Error("fetch is not set. Please pass a fetch implementation as new Octokit({ request: { fetch }}). Learn more at https://github.com/octokit/octokit.js/#fetch-missing");return n(r.url,{method:r.method,body:r.body,redirect:(p=r.request)==null?void 0:p.redirect,headers:r.headers,signal:(g=r.request)==null?void 0:g.signal,...r.body&&{duplex:"half"}}).then(async u=>{a=u.url,o=u.status;for(const f of u.headers)s[f[0]]=f[1];if("deprecation"in s){const f=s.link&&s.link.match(/<([^>]+)>; rel="deprecation"/),y=f&&f.pop();e.warn(`[@octokit/request] "${r.method} ${r.url}" is deprecated. It is scheduled to be removed on ${s.sunset}${y?`. See ${y}`:""}`)}if(!(o===204||o===205)){if(r.method==="HEAD"){if(o<400)return;throw new me(u.statusText,o,{response:{url:a,status:o,headers:s,data:void 0},request:r})}if(o===304)throw new me("Not modified",o,{response:{url:a,status:o,headers:s,data:await Re(u)},request:r});if(o>=400){const f=await Re(u);throw new me(Fs(f),o,{response:{url:a,status:o,headers:s,data:f},request:r})}return t?await Re(u):u.body}}).then(u=>({status:o,url:a,headers:s,data:u})).catch(u=>{if(u instanceof me)throw u;if(u.name==="AbortError")throw u;let f=u.message;throw u.name==="TypeError"&&"cause"in u&&(u.cause instanceof Error?f=u.cause.message:typeof u.cause=="string"&&(f=u.cause)),new me(f,500,{request:r})})}async function Re(r){const e=r.headers.get("content-type");return/application\/json/.test(e)?r.json().catch(()=>r.text()).catch(()=>""):!e||/^text\/|charset=utf-8$/.test(e)?r.text():Cs(r)}function Fs(r){if(typeof r=="string")return r;let e;return"documentation_url"in r?e=` - ${r.documentation_url}`:e="","message"in r?Array.isArray(r.errors)?`${r.message}: ${r.errors.map(JSON.stringify).join(", ")}${e}`:`${r.message}${e}`:`Unknown error: ${JSON.stringify(r)}`}function De(r,e){const t=r.defaults(e);return Object.assign(function(o,a){const n=t.merge(o,a);if(!n.request||!n.request.hook)return ur(t.parse(n));const i=(l,p)=>ur(t.parse(t.merge(l,p)));return Object.assign(i,{endpoint:t,defaults:De.bind(null,t)}),n.request.hook(i,n)},{endpoint:t,defaults:De.bind(null,t)})}var Le=De(ks,{headers:{"user-agent":`octokit-request.js/${As} ${ke()}`}}),Us="7.1.0";function Ds(r){return`Request failed due to following response errors: +`+r.errors.map(e=>` - ${e.message}`).join(` +`)}var Ls=class extends Error{constructor(r,e,t){super(Ds(t)),this.request=r,this.headers=e,this.response=t,this.name="GraphqlResponseError",this.errors=t.errors,this.data=t.data,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}},Is=["method","baseUrl","url","headers","request","query","mediaType"],xs=["query","method","url"],pr=/\/api\/v3\/?$/;function js(r,e,t){if(t){if(typeof e=="string"&&"query"in t)return Promise.reject(new Error('[@octokit/graphql] "query" cannot be used as variable name'));for(const n in t)if(xs.includes(n))return Promise.reject(new Error(`[@octokit/graphql] "${n}" cannot be used as variable name`))}const s=typeof e=="string"?Object.assign({query:e},t):e,o=Object.keys(s).reduce((n,i)=>Is.includes(i)?(n[i]=s[i],n):(n.variables||(n.variables={}),n.variables[i]=s[i],n),{}),a=s.baseUrl||r.endpoint.DEFAULTS.baseUrl;return pr.test(a)&&(o.url=a.replace(pr,"/api/graphql")),r(o).then(n=>{if(n.data.errors){const i={};for(const l of Object.keys(n.headers))i[l]=n.headers[l];throw new Ls(o,i,n.data)}return n.data.data})}function Ve(r,e){const t=r.defaults(e);return Object.assign((o,a)=>js(t,o,a),{defaults:Ve.bind(null,t),endpoint:t.endpoint})}Ve(Le,{headers:{"user-agent":`octokit-graphql.js/${Us} ${ke()}`},method:"POST",url:"/graphql"});function $s(r){return Ve(r,{method:"POST",url:"/graphql"})}var qs=/^v1\./,Bs=/^ghs_/,Hs=/^ghu_/;async function zs(r){const e=r.split(/\./).length===3,t=qs.test(r)||Bs.test(r),s=Hs.test(r);return{type:"token",token:r,tokenType:e?"app":t?"installation":s?"user-to-server":"oauth"}}function Ns(r){return r.split(/\./).length===3?`bearer ${r}`:`token ${r}`}async function Vs(r,e,t,s){const o=e.endpoint.merge(t,s);return o.headers.authorization=Ns(r),e(o)}var Ms=function(e){if(!e)throw new Error("[@octokit/auth-token] No token passed to createTokenAuth");if(typeof e!="string")throw new Error("[@octokit/auth-token] Token passed to createTokenAuth is not a string");return e=e.replace(/^(token|bearer) +/i,""),Object.assign(zs.bind(null,e),{hook:Vs.bind(null,e)})},Lr="5.2.0",dr=()=>{},Ws=console.warn.bind(console),Ks=console.error.bind(console),gr=`octokit-core.js/${Lr} ${ke()}`,ue,Js=(ue=class{static defaults(e){return class extends this{constructor(...s){const o=s[0]||{};if(typeof e=="function"){super(e(o));return}super(Object.assign({},e,o,o.userAgent&&e.userAgent?{userAgent:`${o.userAgent} ${e.userAgent}`}:null))}}}static plugin(...e){var o;const t=this.plugins;return o=class extends this{},(()=>{o.plugins=t.concat(e.filter(n=>!t.includes(n)))})(),o}constructor(e={}){const t=new us,s={baseUrl:Le.endpoint.DEFAULTS.baseUrl,headers:{},request:Object.assign({},e.request,{hook:t.bind(null,"request")}),mediaType:{previews:[],format:""}};if(s.headers["user-agent"]=e.userAgent?`${e.userAgent} ${gr}`:gr,e.baseUrl&&(s.baseUrl=e.baseUrl),e.previews&&(s.mediaType.previews=e.previews),e.timeZone&&(s.headers["time-zone"]=e.timeZone),this.request=Le.defaults(s),this.graphql=$s(this.request).defaults(s),this.log=Object.assign({debug:dr,info:dr,warn:Ws,error:Ks},e.log),this.hook=t,e.authStrategy){const{authStrategy:a,...n}=e,i=a(Object.assign({request:this.request,log:this.log,octokit:this,octokitOptions:n},e.auth));t.wrap("request",i.hook),this.auth=i}else if(!e.auth)this.auth=async()=>({type:"unauthenticated"});else{const a=Ms(e.auth);t.wrap("request",a.hook),this.auth=a}const o=this.constructor;for(let a=0;a{ue.VERSION=Lr})(),(()=>{ue.plugins=[]})(),ue),Zs="4.0.1";function Ir(r){r.hook.wrap("request",(e,t)=>{r.log.debug("request",t);const s=Date.now(),o=r.request.endpoint.parse(t),a=o.url.replace(t.baseUrl,"");return e(t).then(n=>(r.log.info(`${o.method} ${a} - ${n.status} in ${Date.now()-s}ms`),n)).catch(n=>{throw r.log.info(`${o.method} ${a} - ${n.status} in ${Date.now()-s}ms`),n})})}Ir.VERSION=Zs;var Qs="11.3.1";function Xs(r){if(!r.data)return{...r,data:[]};if(!("total_count"in r.data&&!("url"in r.data)))return r;const t=r.data.incomplete_results,s=r.data.repository_selection,o=r.data.total_count;delete r.data.incomplete_results,delete r.data.repository_selection,delete r.data.total_count;const a=Object.keys(r.data)[0],n=r.data[a];return r.data=n,typeof t<"u"&&(r.data.incomplete_results=t),typeof s<"u"&&(r.data.repository_selection=s),r.data.total_count=o,r}function Me(r,e,t){const s=typeof e=="function"?e.endpoint(t):r.request.endpoint(e,t),o=typeof e=="function"?e:r.request,a=s.method,n=s.headers;let i=s.url;return{[Symbol.asyncIterator]:()=>({async next(){if(!i)return{done:!0};try{const l=await o({method:a,url:i,headers:n}),p=Xs(l);return i=((p.headers.link||"").match(/<([^>]+)>;\s*rel="next"/)||[])[1],{value:p}}catch(l){if(l.status!==409)throw l;return i="",{value:{status:200,headers:{},data:[]}}}}})}}function xr(r,e,t,s){return typeof t=="function"&&(s=t,t=void 0),jr(r,[],Me(r,e,t)[Symbol.asyncIterator](),s)}function jr(r,e,t,s){return t.next().then(o=>{if(o.done)return e;let a=!1;function n(){a=!0}return e=e.concat(s?s(o.value,n):o.value.data),a?e:jr(r,e,t,s)})}Object.assign(xr,{iterator:Me});function $r(r){return{paginate:Object.assign(xr.bind(null,r),{iterator:Me.bind(null,r)})}}$r.VERSION=Qs;var Ys="13.2.2",eo={actions:{addCustomLabelsToSelfHostedRunnerForOrg:["POST /orgs/{org}/actions/runners/{runner_id}/labels"],addCustomLabelsToSelfHostedRunnerForRepo:["POST /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],addSelectedRepoToOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"],addSelectedRepoToOrgVariable:["PUT /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"],approveWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/approve"],cancelWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel"],createEnvironmentVariable:["POST /repos/{owner}/{repo}/environments/{environment_name}/variables"],createOrUpdateEnvironmentSecret:["PUT /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"],createOrUpdateOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}"],createOrgVariable:["POST /orgs/{org}/actions/variables"],createRegistrationTokenForOrg:["POST /orgs/{org}/actions/runners/registration-token"],createRegistrationTokenForRepo:["POST /repos/{owner}/{repo}/actions/runners/registration-token"],createRemoveTokenForOrg:["POST /orgs/{org}/actions/runners/remove-token"],createRemoveTokenForRepo:["POST /repos/{owner}/{repo}/actions/runners/remove-token"],createRepoVariable:["POST /repos/{owner}/{repo}/actions/variables"],createWorkflowDispatch:["POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches"],deleteActionsCacheById:["DELETE /repos/{owner}/{repo}/actions/caches/{cache_id}"],deleteActionsCacheByKey:["DELETE /repos/{owner}/{repo}/actions/caches{?key,ref}"],deleteArtifact:["DELETE /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"],deleteEnvironmentSecret:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"],deleteEnvironmentVariable:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"],deleteOrgSecret:["DELETE /orgs/{org}/actions/secrets/{secret_name}"],deleteOrgVariable:["DELETE /orgs/{org}/actions/variables/{name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}"],deleteRepoVariable:["DELETE /repos/{owner}/{repo}/actions/variables/{name}"],deleteSelfHostedRunnerFromOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}"],deleteSelfHostedRunnerFromRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}"],deleteWorkflowRun:["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}"],deleteWorkflowRunLogs:["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}/logs"],disableSelectedRepositoryGithubActionsOrganization:["DELETE /orgs/{org}/actions/permissions/repositories/{repository_id}"],disableWorkflow:["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable"],downloadArtifact:["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}"],downloadJobLogsForWorkflowRun:["GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs"],downloadWorkflowRunAttemptLogs:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/logs"],downloadWorkflowRunLogs:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/logs"],enableSelectedRepositoryGithubActionsOrganization:["PUT /orgs/{org}/actions/permissions/repositories/{repository_id}"],enableWorkflow:["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/enable"],forceCancelWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/force-cancel"],generateRunnerJitconfigForOrg:["POST /orgs/{org}/actions/runners/generate-jitconfig"],generateRunnerJitconfigForRepo:["POST /repos/{owner}/{repo}/actions/runners/generate-jitconfig"],getActionsCacheList:["GET /repos/{owner}/{repo}/actions/caches"],getActionsCacheUsage:["GET /repos/{owner}/{repo}/actions/cache/usage"],getActionsCacheUsageByRepoForOrg:["GET /orgs/{org}/actions/cache/usage-by-repository"],getActionsCacheUsageForOrg:["GET /orgs/{org}/actions/cache/usage"],getAllowedActionsOrganization:["GET /orgs/{org}/actions/permissions/selected-actions"],getAllowedActionsRepository:["GET /repos/{owner}/{repo}/actions/permissions/selected-actions"],getArtifact:["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"],getCustomOidcSubClaimForRepo:["GET /repos/{owner}/{repo}/actions/oidc/customization/sub"],getEnvironmentPublicKey:["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets/public-key"],getEnvironmentSecret:["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"],getEnvironmentVariable:["GET /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"],getGithubActionsDefaultWorkflowPermissionsOrganization:["GET /orgs/{org}/actions/permissions/workflow"],getGithubActionsDefaultWorkflowPermissionsRepository:["GET /repos/{owner}/{repo}/actions/permissions/workflow"],getGithubActionsPermissionsOrganization:["GET /orgs/{org}/actions/permissions"],getGithubActionsPermissionsRepository:["GET /repos/{owner}/{repo}/actions/permissions"],getJobForWorkflowRun:["GET /repos/{owner}/{repo}/actions/jobs/{job_id}"],getOrgPublicKey:["GET /orgs/{org}/actions/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/actions/secrets/{secret_name}"],getOrgVariable:["GET /orgs/{org}/actions/variables/{name}"],getPendingDeploymentsForRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"],getRepoPermissions:["GET /repos/{owner}/{repo}/actions/permissions",{},{renamed:["actions","getGithubActionsPermissionsRepository"]}],getRepoPublicKey:["GET /repos/{owner}/{repo}/actions/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/actions/secrets/{secret_name}"],getRepoVariable:["GET /repos/{owner}/{repo}/actions/variables/{name}"],getReviewsForRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/approvals"],getSelfHostedRunnerForOrg:["GET /orgs/{org}/actions/runners/{runner_id}"],getSelfHostedRunnerForRepo:["GET /repos/{owner}/{repo}/actions/runners/{runner_id}"],getWorkflow:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}"],getWorkflowAccessToRepository:["GET /repos/{owner}/{repo}/actions/permissions/access"],getWorkflowRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}"],getWorkflowRunAttempt:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}"],getWorkflowRunUsage:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/timing"],getWorkflowUsage:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/timing"],listArtifactsForRepo:["GET /repos/{owner}/{repo}/actions/artifacts"],listEnvironmentSecrets:["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets"],listEnvironmentVariables:["GET /repos/{owner}/{repo}/environments/{environment_name}/variables"],listJobsForWorkflowRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs"],listJobsForWorkflowRunAttempt:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs"],listLabelsForSelfHostedRunnerForOrg:["GET /orgs/{org}/actions/runners/{runner_id}/labels"],listLabelsForSelfHostedRunnerForRepo:["GET /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],listOrgSecrets:["GET /orgs/{org}/actions/secrets"],listOrgVariables:["GET /orgs/{org}/actions/variables"],listRepoOrganizationSecrets:["GET /repos/{owner}/{repo}/actions/organization-secrets"],listRepoOrganizationVariables:["GET /repos/{owner}/{repo}/actions/organization-variables"],listRepoSecrets:["GET /repos/{owner}/{repo}/actions/secrets"],listRepoVariables:["GET /repos/{owner}/{repo}/actions/variables"],listRepoWorkflows:["GET /repos/{owner}/{repo}/actions/workflows"],listRunnerApplicationsForOrg:["GET /orgs/{org}/actions/runners/downloads"],listRunnerApplicationsForRepo:["GET /repos/{owner}/{repo}/actions/runners/downloads"],listSelectedReposForOrgSecret:["GET /orgs/{org}/actions/secrets/{secret_name}/repositories"],listSelectedReposForOrgVariable:["GET /orgs/{org}/actions/variables/{name}/repositories"],listSelectedRepositoriesEnabledGithubActionsOrganization:["GET /orgs/{org}/actions/permissions/repositories"],listSelfHostedRunnersForOrg:["GET /orgs/{org}/actions/runners"],listSelfHostedRunnersForRepo:["GET /repos/{owner}/{repo}/actions/runners"],listWorkflowRunArtifacts:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts"],listWorkflowRuns:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs"],listWorkflowRunsForRepo:["GET /repos/{owner}/{repo}/actions/runs"],reRunJobForWorkflowRun:["POST /repos/{owner}/{repo}/actions/jobs/{job_id}/rerun"],reRunWorkflow:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun"],reRunWorkflowFailedJobs:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun-failed-jobs"],removeAllCustomLabelsFromSelfHostedRunnerForOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}/labels"],removeAllCustomLabelsFromSelfHostedRunnerForRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],removeCustomLabelFromSelfHostedRunnerForOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}/labels/{name}"],removeCustomLabelFromSelfHostedRunnerForRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels/{name}"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"],removeSelectedRepoFromOrgVariable:["DELETE /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"],reviewCustomGatesForRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/deployment_protection_rule"],reviewPendingDeploymentsForRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"],setAllowedActionsOrganization:["PUT /orgs/{org}/actions/permissions/selected-actions"],setAllowedActionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions/selected-actions"],setCustomLabelsForSelfHostedRunnerForOrg:["PUT /orgs/{org}/actions/runners/{runner_id}/labels"],setCustomLabelsForSelfHostedRunnerForRepo:["PUT /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],setCustomOidcSubClaimForRepo:["PUT /repos/{owner}/{repo}/actions/oidc/customization/sub"],setGithubActionsDefaultWorkflowPermissionsOrganization:["PUT /orgs/{org}/actions/permissions/workflow"],setGithubActionsDefaultWorkflowPermissionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions/workflow"],setGithubActionsPermissionsOrganization:["PUT /orgs/{org}/actions/permissions"],setGithubActionsPermissionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories"],setSelectedReposForOrgVariable:["PUT /orgs/{org}/actions/variables/{name}/repositories"],setSelectedRepositoriesEnabledGithubActionsOrganization:["PUT /orgs/{org}/actions/permissions/repositories"],setWorkflowAccessToRepository:["PUT /repos/{owner}/{repo}/actions/permissions/access"],updateEnvironmentVariable:["PATCH /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"],updateOrgVariable:["PATCH /orgs/{org}/actions/variables/{name}"],updateRepoVariable:["PATCH /repos/{owner}/{repo}/actions/variables/{name}"]},activity:{checkRepoIsStarredByAuthenticatedUser:["GET /user/starred/{owner}/{repo}"],deleteRepoSubscription:["DELETE /repos/{owner}/{repo}/subscription"],deleteThreadSubscription:["DELETE /notifications/threads/{thread_id}/subscription"],getFeeds:["GET /feeds"],getRepoSubscription:["GET /repos/{owner}/{repo}/subscription"],getThread:["GET /notifications/threads/{thread_id}"],getThreadSubscriptionForAuthenticatedUser:["GET /notifications/threads/{thread_id}/subscription"],listEventsForAuthenticatedUser:["GET /users/{username}/events"],listNotificationsForAuthenticatedUser:["GET /notifications"],listOrgEventsForAuthenticatedUser:["GET /users/{username}/events/orgs/{org}"],listPublicEvents:["GET /events"],listPublicEventsForRepoNetwork:["GET /networks/{owner}/{repo}/events"],listPublicEventsForUser:["GET /users/{username}/events/public"],listPublicOrgEvents:["GET /orgs/{org}/events"],listReceivedEventsForUser:["GET /users/{username}/received_events"],listReceivedPublicEventsForUser:["GET /users/{username}/received_events/public"],listRepoEvents:["GET /repos/{owner}/{repo}/events"],listRepoNotificationsForAuthenticatedUser:["GET /repos/{owner}/{repo}/notifications"],listReposStarredByAuthenticatedUser:["GET /user/starred"],listReposStarredByUser:["GET /users/{username}/starred"],listReposWatchedByUser:["GET /users/{username}/subscriptions"],listStargazersForRepo:["GET /repos/{owner}/{repo}/stargazers"],listWatchedReposForAuthenticatedUser:["GET /user/subscriptions"],listWatchersForRepo:["GET /repos/{owner}/{repo}/subscribers"],markNotificationsAsRead:["PUT /notifications"],markRepoNotificationsAsRead:["PUT /repos/{owner}/{repo}/notifications"],markThreadAsDone:["DELETE /notifications/threads/{thread_id}"],markThreadAsRead:["PATCH /notifications/threads/{thread_id}"],setRepoSubscription:["PUT /repos/{owner}/{repo}/subscription"],setThreadSubscription:["PUT /notifications/threads/{thread_id}/subscription"],starRepoForAuthenticatedUser:["PUT /user/starred/{owner}/{repo}"],unstarRepoForAuthenticatedUser:["DELETE /user/starred/{owner}/{repo}"]},apps:{addRepoToInstallation:["PUT /user/installations/{installation_id}/repositories/{repository_id}",{},{renamed:["apps","addRepoToInstallationForAuthenticatedUser"]}],addRepoToInstallationForAuthenticatedUser:["PUT /user/installations/{installation_id}/repositories/{repository_id}"],checkToken:["POST /applications/{client_id}/token"],createFromManifest:["POST /app-manifests/{code}/conversions"],createInstallationAccessToken:["POST /app/installations/{installation_id}/access_tokens"],deleteAuthorization:["DELETE /applications/{client_id}/grant"],deleteInstallation:["DELETE /app/installations/{installation_id}"],deleteToken:["DELETE /applications/{client_id}/token"],getAuthenticated:["GET /app"],getBySlug:["GET /apps/{app_slug}"],getInstallation:["GET /app/installations/{installation_id}"],getOrgInstallation:["GET /orgs/{org}/installation"],getRepoInstallation:["GET /repos/{owner}/{repo}/installation"],getSubscriptionPlanForAccount:["GET /marketplace_listing/accounts/{account_id}"],getSubscriptionPlanForAccountStubbed:["GET /marketplace_listing/stubbed/accounts/{account_id}"],getUserInstallation:["GET /users/{username}/installation"],getWebhookConfigForApp:["GET /app/hook/config"],getWebhookDelivery:["GET /app/hook/deliveries/{delivery_id}"],listAccountsForPlan:["GET /marketplace_listing/plans/{plan_id}/accounts"],listAccountsForPlanStubbed:["GET /marketplace_listing/stubbed/plans/{plan_id}/accounts"],listInstallationReposForAuthenticatedUser:["GET /user/installations/{installation_id}/repositories"],listInstallationRequestsForAuthenticatedApp:["GET /app/installation-requests"],listInstallations:["GET /app/installations"],listInstallationsForAuthenticatedUser:["GET /user/installations"],listPlans:["GET /marketplace_listing/plans"],listPlansStubbed:["GET /marketplace_listing/stubbed/plans"],listReposAccessibleToInstallation:["GET /installation/repositories"],listSubscriptionsForAuthenticatedUser:["GET /user/marketplace_purchases"],listSubscriptionsForAuthenticatedUserStubbed:["GET /user/marketplace_purchases/stubbed"],listWebhookDeliveries:["GET /app/hook/deliveries"],redeliverWebhookDelivery:["POST /app/hook/deliveries/{delivery_id}/attempts"],removeRepoFromInstallation:["DELETE /user/installations/{installation_id}/repositories/{repository_id}",{},{renamed:["apps","removeRepoFromInstallationForAuthenticatedUser"]}],removeRepoFromInstallationForAuthenticatedUser:["DELETE /user/installations/{installation_id}/repositories/{repository_id}"],resetToken:["PATCH /applications/{client_id}/token"],revokeInstallationAccessToken:["DELETE /installation/token"],scopeToken:["POST /applications/{client_id}/token/scoped"],suspendInstallation:["PUT /app/installations/{installation_id}/suspended"],unsuspendInstallation:["DELETE /app/installations/{installation_id}/suspended"],updateWebhookConfigForApp:["PATCH /app/hook/config"]},billing:{getGithubActionsBillingOrg:["GET /orgs/{org}/settings/billing/actions"],getGithubActionsBillingUser:["GET /users/{username}/settings/billing/actions"],getGithubPackagesBillingOrg:["GET /orgs/{org}/settings/billing/packages"],getGithubPackagesBillingUser:["GET /users/{username}/settings/billing/packages"],getSharedStorageBillingOrg:["GET /orgs/{org}/settings/billing/shared-storage"],getSharedStorageBillingUser:["GET /users/{username}/settings/billing/shared-storage"]},checks:{create:["POST /repos/{owner}/{repo}/check-runs"],createSuite:["POST /repos/{owner}/{repo}/check-suites"],get:["GET /repos/{owner}/{repo}/check-runs/{check_run_id}"],getSuite:["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}"],listAnnotations:["GET /repos/{owner}/{repo}/check-runs/{check_run_id}/annotations"],listForRef:["GET /repos/{owner}/{repo}/commits/{ref}/check-runs"],listForSuite:["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runs"],listSuitesForRef:["GET /repos/{owner}/{repo}/commits/{ref}/check-suites"],rerequestRun:["POST /repos/{owner}/{repo}/check-runs/{check_run_id}/rerequest"],rerequestSuite:["POST /repos/{owner}/{repo}/check-suites/{check_suite_id}/rerequest"],setSuitesPreferences:["PATCH /repos/{owner}/{repo}/check-suites/preferences"],update:["PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}"]},codeScanning:{deleteAnalysis:["DELETE /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}{?confirm_delete}"],getAlert:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}",{},{renamedParameters:{alert_id:"alert_number"}}],getAnalysis:["GET /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}"],getCodeqlDatabase:["GET /repos/{owner}/{repo}/code-scanning/codeql/databases/{language}"],getDefaultSetup:["GET /repos/{owner}/{repo}/code-scanning/default-setup"],getSarif:["GET /repos/{owner}/{repo}/code-scanning/sarifs/{sarif_id}"],listAlertInstances:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances"],listAlertsForOrg:["GET /orgs/{org}/code-scanning/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/code-scanning/alerts"],listAlertsInstances:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances",{},{renamed:["codeScanning","listAlertInstances"]}],listCodeqlDatabases:["GET /repos/{owner}/{repo}/code-scanning/codeql/databases"],listRecentAnalyses:["GET /repos/{owner}/{repo}/code-scanning/analyses"],updateAlert:["PATCH /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}"],updateDefaultSetup:["PATCH /repos/{owner}/{repo}/code-scanning/default-setup"],uploadSarif:["POST /repos/{owner}/{repo}/code-scanning/sarifs"]},codesOfConduct:{getAllCodesOfConduct:["GET /codes_of_conduct"],getConductCode:["GET /codes_of_conduct/{key}"]},codespaces:{addRepositoryForSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"],addSelectedRepoToOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"],checkPermissionsForDevcontainer:["GET /repos/{owner}/{repo}/codespaces/permissions_check"],codespaceMachinesForAuthenticatedUser:["GET /user/codespaces/{codespace_name}/machines"],createForAuthenticatedUser:["POST /user/codespaces"],createOrUpdateOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],createOrUpdateSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}"],createWithPrForAuthenticatedUser:["POST /repos/{owner}/{repo}/pulls/{pull_number}/codespaces"],createWithRepoForAuthenticatedUser:["POST /repos/{owner}/{repo}/codespaces"],deleteForAuthenticatedUser:["DELETE /user/codespaces/{codespace_name}"],deleteFromOrganization:["DELETE /orgs/{org}/members/{username}/codespaces/{codespace_name}"],deleteOrgSecret:["DELETE /orgs/{org}/codespaces/secrets/{secret_name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],deleteSecretForAuthenticatedUser:["DELETE /user/codespaces/secrets/{secret_name}"],exportForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/exports"],getCodespacesForUserInOrg:["GET /orgs/{org}/members/{username}/codespaces"],getExportDetailsForAuthenticatedUser:["GET /user/codespaces/{codespace_name}/exports/{export_id}"],getForAuthenticatedUser:["GET /user/codespaces/{codespace_name}"],getOrgPublicKey:["GET /orgs/{org}/codespaces/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/codespaces/secrets/{secret_name}"],getPublicKeyForAuthenticatedUser:["GET /user/codespaces/secrets/public-key"],getRepoPublicKey:["GET /repos/{owner}/{repo}/codespaces/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],getSecretForAuthenticatedUser:["GET /user/codespaces/secrets/{secret_name}"],listDevcontainersInRepositoryForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/devcontainers"],listForAuthenticatedUser:["GET /user/codespaces"],listInOrganization:["GET /orgs/{org}/codespaces",{},{renamedParameters:{org_id:"org"}}],listInRepositoryForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces"],listOrgSecrets:["GET /orgs/{org}/codespaces/secrets"],listRepoSecrets:["GET /repos/{owner}/{repo}/codespaces/secrets"],listRepositoriesForSecretForAuthenticatedUser:["GET /user/codespaces/secrets/{secret_name}/repositories"],listSecretsForAuthenticatedUser:["GET /user/codespaces/secrets"],listSelectedReposForOrgSecret:["GET /orgs/{org}/codespaces/secrets/{secret_name}/repositories"],preFlightWithRepoForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/new"],publishForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/publish"],removeRepositoryForSecretForAuthenticatedUser:["DELETE /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"],repoMachinesForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/machines"],setRepositoriesForSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}/repositories"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories"],startForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/start"],stopForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/stop"],stopInOrganization:["POST /orgs/{org}/members/{username}/codespaces/{codespace_name}/stop"],updateForAuthenticatedUser:["PATCH /user/codespaces/{codespace_name}"]},copilot:{addCopilotSeatsForTeams:["POST /orgs/{org}/copilot/billing/selected_teams"],addCopilotSeatsForUsers:["POST /orgs/{org}/copilot/billing/selected_users"],cancelCopilotSeatAssignmentForTeams:["DELETE /orgs/{org}/copilot/billing/selected_teams"],cancelCopilotSeatAssignmentForUsers:["DELETE /orgs/{org}/copilot/billing/selected_users"],getCopilotOrganizationDetails:["GET /orgs/{org}/copilot/billing"],getCopilotSeatDetailsForUser:["GET /orgs/{org}/members/{username}/copilot"],listCopilotSeats:["GET /orgs/{org}/copilot/billing/seats"],usageMetricsForEnterprise:["GET /enterprises/{enterprise}/copilot/usage"],usageMetricsForOrg:["GET /orgs/{org}/copilot/usage"],usageMetricsForTeam:["GET /orgs/{org}/team/{team_slug}/copilot/usage"]},dependabot:{addSelectedRepoToOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"],createOrUpdateOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],deleteOrgSecret:["DELETE /orgs/{org}/dependabot/secrets/{secret_name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],getAlert:["GET /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"],getOrgPublicKey:["GET /orgs/{org}/dependabot/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/dependabot/secrets/{secret_name}"],getRepoPublicKey:["GET /repos/{owner}/{repo}/dependabot/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],listAlertsForEnterprise:["GET /enterprises/{enterprise}/dependabot/alerts"],listAlertsForOrg:["GET /orgs/{org}/dependabot/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/dependabot/alerts"],listOrgSecrets:["GET /orgs/{org}/dependabot/secrets"],listRepoSecrets:["GET /repos/{owner}/{repo}/dependabot/secrets"],listSelectedReposForOrgSecret:["GET /orgs/{org}/dependabot/secrets/{secret_name}/repositories"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories"],updateAlert:["PATCH /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"]},dependencyGraph:{createRepositorySnapshot:["POST /repos/{owner}/{repo}/dependency-graph/snapshots"],diffRange:["GET /repos/{owner}/{repo}/dependency-graph/compare/{basehead}"],exportSbom:["GET /repos/{owner}/{repo}/dependency-graph/sbom"]},emojis:{get:["GET /emojis"]},gists:{checkIsStarred:["GET /gists/{gist_id}/star"],create:["POST /gists"],createComment:["POST /gists/{gist_id}/comments"],delete:["DELETE /gists/{gist_id}"],deleteComment:["DELETE /gists/{gist_id}/comments/{comment_id}"],fork:["POST /gists/{gist_id}/forks"],get:["GET /gists/{gist_id}"],getComment:["GET /gists/{gist_id}/comments/{comment_id}"],getRevision:["GET /gists/{gist_id}/{sha}"],list:["GET /gists"],listComments:["GET /gists/{gist_id}/comments"],listCommits:["GET /gists/{gist_id}/commits"],listForUser:["GET /users/{username}/gists"],listForks:["GET /gists/{gist_id}/forks"],listPublic:["GET /gists/public"],listStarred:["GET /gists/starred"],star:["PUT /gists/{gist_id}/star"],unstar:["DELETE /gists/{gist_id}/star"],update:["PATCH /gists/{gist_id}"],updateComment:["PATCH /gists/{gist_id}/comments/{comment_id}"]},git:{createBlob:["POST /repos/{owner}/{repo}/git/blobs"],createCommit:["POST /repos/{owner}/{repo}/git/commits"],createRef:["POST /repos/{owner}/{repo}/git/refs"],createTag:["POST /repos/{owner}/{repo}/git/tags"],createTree:["POST /repos/{owner}/{repo}/git/trees"],deleteRef:["DELETE /repos/{owner}/{repo}/git/refs/{ref}"],getBlob:["GET /repos/{owner}/{repo}/git/blobs/{file_sha}"],getCommit:["GET /repos/{owner}/{repo}/git/commits/{commit_sha}"],getRef:["GET /repos/{owner}/{repo}/git/ref/{ref}"],getTag:["GET /repos/{owner}/{repo}/git/tags/{tag_sha}"],getTree:["GET /repos/{owner}/{repo}/git/trees/{tree_sha}"],listMatchingRefs:["GET /repos/{owner}/{repo}/git/matching-refs/{ref}"],updateRef:["PATCH /repos/{owner}/{repo}/git/refs/{ref}"]},gitignore:{getAllTemplates:["GET /gitignore/templates"],getTemplate:["GET /gitignore/templates/{name}"]},interactions:{getRestrictionsForAuthenticatedUser:["GET /user/interaction-limits"],getRestrictionsForOrg:["GET /orgs/{org}/interaction-limits"],getRestrictionsForRepo:["GET /repos/{owner}/{repo}/interaction-limits"],getRestrictionsForYourPublicRepos:["GET /user/interaction-limits",{},{renamed:["interactions","getRestrictionsForAuthenticatedUser"]}],removeRestrictionsForAuthenticatedUser:["DELETE /user/interaction-limits"],removeRestrictionsForOrg:["DELETE /orgs/{org}/interaction-limits"],removeRestrictionsForRepo:["DELETE /repos/{owner}/{repo}/interaction-limits"],removeRestrictionsForYourPublicRepos:["DELETE /user/interaction-limits",{},{renamed:["interactions","removeRestrictionsForAuthenticatedUser"]}],setRestrictionsForAuthenticatedUser:["PUT /user/interaction-limits"],setRestrictionsForOrg:["PUT /orgs/{org}/interaction-limits"],setRestrictionsForRepo:["PUT /repos/{owner}/{repo}/interaction-limits"],setRestrictionsForYourPublicRepos:["PUT /user/interaction-limits",{},{renamed:["interactions","setRestrictionsForAuthenticatedUser"]}]},issues:{addAssignees:["POST /repos/{owner}/{repo}/issues/{issue_number}/assignees"],addLabels:["POST /repos/{owner}/{repo}/issues/{issue_number}/labels"],checkUserCanBeAssigned:["GET /repos/{owner}/{repo}/assignees/{assignee}"],checkUserCanBeAssignedToIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/assignees/{assignee}"],create:["POST /repos/{owner}/{repo}/issues"],createComment:["POST /repos/{owner}/{repo}/issues/{issue_number}/comments"],createLabel:["POST /repos/{owner}/{repo}/labels"],createMilestone:["POST /repos/{owner}/{repo}/milestones"],deleteComment:["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}"],deleteLabel:["DELETE /repos/{owner}/{repo}/labels/{name}"],deleteMilestone:["DELETE /repos/{owner}/{repo}/milestones/{milestone_number}"],get:["GET /repos/{owner}/{repo}/issues/{issue_number}"],getComment:["GET /repos/{owner}/{repo}/issues/comments/{comment_id}"],getEvent:["GET /repos/{owner}/{repo}/issues/events/{event_id}"],getLabel:["GET /repos/{owner}/{repo}/labels/{name}"],getMilestone:["GET /repos/{owner}/{repo}/milestones/{milestone_number}"],list:["GET /issues"],listAssignees:["GET /repos/{owner}/{repo}/assignees"],listComments:["GET /repos/{owner}/{repo}/issues/{issue_number}/comments"],listCommentsForRepo:["GET /repos/{owner}/{repo}/issues/comments"],listEvents:["GET /repos/{owner}/{repo}/issues/{issue_number}/events"],listEventsForRepo:["GET /repos/{owner}/{repo}/issues/events"],listEventsForTimeline:["GET /repos/{owner}/{repo}/issues/{issue_number}/timeline"],listForAuthenticatedUser:["GET /user/issues"],listForOrg:["GET /orgs/{org}/issues"],listForRepo:["GET /repos/{owner}/{repo}/issues"],listLabelsForMilestone:["GET /repos/{owner}/{repo}/milestones/{milestone_number}/labels"],listLabelsForRepo:["GET /repos/{owner}/{repo}/labels"],listLabelsOnIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/labels"],listMilestones:["GET /repos/{owner}/{repo}/milestones"],lock:["PUT /repos/{owner}/{repo}/issues/{issue_number}/lock"],removeAllLabels:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels"],removeAssignees:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/assignees"],removeLabel:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}"],setLabels:["PUT /repos/{owner}/{repo}/issues/{issue_number}/labels"],unlock:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/lock"],update:["PATCH /repos/{owner}/{repo}/issues/{issue_number}"],updateComment:["PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}"],updateLabel:["PATCH /repos/{owner}/{repo}/labels/{name}"],updateMilestone:["PATCH /repos/{owner}/{repo}/milestones/{milestone_number}"]},licenses:{get:["GET /licenses/{license}"],getAllCommonlyUsed:["GET /licenses"],getForRepo:["GET /repos/{owner}/{repo}/license"]},markdown:{render:["POST /markdown"],renderRaw:["POST /markdown/raw",{headers:{"content-type":"text/plain; charset=utf-8"}}]},meta:{get:["GET /meta"],getAllVersions:["GET /versions"],getOctocat:["GET /octocat"],getZen:["GET /zen"],root:["GET /"]},migrations:{deleteArchiveForAuthenticatedUser:["DELETE /user/migrations/{migration_id}/archive"],deleteArchiveForOrg:["DELETE /orgs/{org}/migrations/{migration_id}/archive"],downloadArchiveForOrg:["GET /orgs/{org}/migrations/{migration_id}/archive"],getArchiveForAuthenticatedUser:["GET /user/migrations/{migration_id}/archive"],getStatusForAuthenticatedUser:["GET /user/migrations/{migration_id}"],getStatusForOrg:["GET /orgs/{org}/migrations/{migration_id}"],listForAuthenticatedUser:["GET /user/migrations"],listForOrg:["GET /orgs/{org}/migrations"],listReposForAuthenticatedUser:["GET /user/migrations/{migration_id}/repositories"],listReposForOrg:["GET /orgs/{org}/migrations/{migration_id}/repositories"],listReposForUser:["GET /user/migrations/{migration_id}/repositories",{},{renamed:["migrations","listReposForAuthenticatedUser"]}],startForAuthenticatedUser:["POST /user/migrations"],startForOrg:["POST /orgs/{org}/migrations"],unlockRepoForAuthenticatedUser:["DELETE /user/migrations/{migration_id}/repos/{repo_name}/lock"],unlockRepoForOrg:["DELETE /orgs/{org}/migrations/{migration_id}/repos/{repo_name}/lock"]},oidc:{getOidcCustomSubTemplateForOrg:["GET /orgs/{org}/actions/oidc/customization/sub"],updateOidcCustomSubTemplateForOrg:["PUT /orgs/{org}/actions/oidc/customization/sub"]},orgs:{addSecurityManagerTeam:["PUT /orgs/{org}/security-managers/teams/{team_slug}"],assignTeamToOrgRole:["PUT /orgs/{org}/organization-roles/teams/{team_slug}/{role_id}"],assignUserToOrgRole:["PUT /orgs/{org}/organization-roles/users/{username}/{role_id}"],blockUser:["PUT /orgs/{org}/blocks/{username}"],cancelInvitation:["DELETE /orgs/{org}/invitations/{invitation_id}"],checkBlockedUser:["GET /orgs/{org}/blocks/{username}"],checkMembershipForUser:["GET /orgs/{org}/members/{username}"],checkPublicMembershipForUser:["GET /orgs/{org}/public_members/{username}"],convertMemberToOutsideCollaborator:["PUT /orgs/{org}/outside_collaborators/{username}"],createCustomOrganizationRole:["POST /orgs/{org}/organization-roles"],createInvitation:["POST /orgs/{org}/invitations"],createOrUpdateCustomProperties:["PATCH /orgs/{org}/properties/schema"],createOrUpdateCustomPropertiesValuesForRepos:["PATCH /orgs/{org}/properties/values"],createOrUpdateCustomProperty:["PUT /orgs/{org}/properties/schema/{custom_property_name}"],createWebhook:["POST /orgs/{org}/hooks"],delete:["DELETE /orgs/{org}"],deleteCustomOrganizationRole:["DELETE /orgs/{org}/organization-roles/{role_id}"],deleteWebhook:["DELETE /orgs/{org}/hooks/{hook_id}"],enableOrDisableSecurityProductOnAllOrgRepos:["POST /orgs/{org}/{security_product}/{enablement}"],get:["GET /orgs/{org}"],getAllCustomProperties:["GET /orgs/{org}/properties/schema"],getCustomProperty:["GET /orgs/{org}/properties/schema/{custom_property_name}"],getMembershipForAuthenticatedUser:["GET /user/memberships/orgs/{org}"],getMembershipForUser:["GET /orgs/{org}/memberships/{username}"],getOrgRole:["GET /orgs/{org}/organization-roles/{role_id}"],getWebhook:["GET /orgs/{org}/hooks/{hook_id}"],getWebhookConfigForOrg:["GET /orgs/{org}/hooks/{hook_id}/config"],getWebhookDelivery:["GET /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}"],list:["GET /organizations"],listAppInstallations:["GET /orgs/{org}/installations"],listBlockedUsers:["GET /orgs/{org}/blocks"],listCustomPropertiesValuesForRepos:["GET /orgs/{org}/properties/values"],listFailedInvitations:["GET /orgs/{org}/failed_invitations"],listForAuthenticatedUser:["GET /user/orgs"],listForUser:["GET /users/{username}/orgs"],listInvitationTeams:["GET /orgs/{org}/invitations/{invitation_id}/teams"],listMembers:["GET /orgs/{org}/members"],listMembershipsForAuthenticatedUser:["GET /user/memberships/orgs"],listOrgRoleTeams:["GET /orgs/{org}/organization-roles/{role_id}/teams"],listOrgRoleUsers:["GET /orgs/{org}/organization-roles/{role_id}/users"],listOrgRoles:["GET /orgs/{org}/organization-roles"],listOrganizationFineGrainedPermissions:["GET /orgs/{org}/organization-fine-grained-permissions"],listOutsideCollaborators:["GET /orgs/{org}/outside_collaborators"],listPatGrantRepositories:["GET /orgs/{org}/personal-access-tokens/{pat_id}/repositories"],listPatGrantRequestRepositories:["GET /orgs/{org}/personal-access-token-requests/{pat_request_id}/repositories"],listPatGrantRequests:["GET /orgs/{org}/personal-access-token-requests"],listPatGrants:["GET /orgs/{org}/personal-access-tokens"],listPendingInvitations:["GET /orgs/{org}/invitations"],listPublicMembers:["GET /orgs/{org}/public_members"],listSecurityManagerTeams:["GET /orgs/{org}/security-managers"],listWebhookDeliveries:["GET /orgs/{org}/hooks/{hook_id}/deliveries"],listWebhooks:["GET /orgs/{org}/hooks"],patchCustomOrganizationRole:["PATCH /orgs/{org}/organization-roles/{role_id}"],pingWebhook:["POST /orgs/{org}/hooks/{hook_id}/pings"],redeliverWebhookDelivery:["POST /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"],removeCustomProperty:["DELETE /orgs/{org}/properties/schema/{custom_property_name}"],removeMember:["DELETE /orgs/{org}/members/{username}"],removeMembershipForUser:["DELETE /orgs/{org}/memberships/{username}"],removeOutsideCollaborator:["DELETE /orgs/{org}/outside_collaborators/{username}"],removePublicMembershipForAuthenticatedUser:["DELETE /orgs/{org}/public_members/{username}"],removeSecurityManagerTeam:["DELETE /orgs/{org}/security-managers/teams/{team_slug}"],reviewPatGrantRequest:["POST /orgs/{org}/personal-access-token-requests/{pat_request_id}"],reviewPatGrantRequestsInBulk:["POST /orgs/{org}/personal-access-token-requests"],revokeAllOrgRolesTeam:["DELETE /orgs/{org}/organization-roles/teams/{team_slug}"],revokeAllOrgRolesUser:["DELETE /orgs/{org}/organization-roles/users/{username}"],revokeOrgRoleTeam:["DELETE /orgs/{org}/organization-roles/teams/{team_slug}/{role_id}"],revokeOrgRoleUser:["DELETE /orgs/{org}/organization-roles/users/{username}/{role_id}"],setMembershipForUser:["PUT /orgs/{org}/memberships/{username}"],setPublicMembershipForAuthenticatedUser:["PUT /orgs/{org}/public_members/{username}"],unblockUser:["DELETE /orgs/{org}/blocks/{username}"],update:["PATCH /orgs/{org}"],updateMembershipForAuthenticatedUser:["PATCH /user/memberships/orgs/{org}"],updatePatAccess:["POST /orgs/{org}/personal-access-tokens/{pat_id}"],updatePatAccesses:["POST /orgs/{org}/personal-access-tokens"],updateWebhook:["PATCH /orgs/{org}/hooks/{hook_id}"],updateWebhookConfigForOrg:["PATCH /orgs/{org}/hooks/{hook_id}/config"]},packages:{deletePackageForAuthenticatedUser:["DELETE /user/packages/{package_type}/{package_name}"],deletePackageForOrg:["DELETE /orgs/{org}/packages/{package_type}/{package_name}"],deletePackageForUser:["DELETE /users/{username}/packages/{package_type}/{package_name}"],deletePackageVersionForAuthenticatedUser:["DELETE /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],deletePackageVersionForOrg:["DELETE /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],deletePackageVersionForUser:["DELETE /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"],getAllPackageVersionsForAPackageOwnedByAnOrg:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions",{},{renamed:["packages","getAllPackageVersionsForPackageOwnedByOrg"]}],getAllPackageVersionsForAPackageOwnedByTheAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions",{},{renamed:["packages","getAllPackageVersionsForPackageOwnedByAuthenticatedUser"]}],getAllPackageVersionsForPackageOwnedByAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions"],getAllPackageVersionsForPackageOwnedByOrg:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions"],getAllPackageVersionsForPackageOwnedByUser:["GET /users/{username}/packages/{package_type}/{package_name}/versions"],getPackageForAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}"],getPackageForOrganization:["GET /orgs/{org}/packages/{package_type}/{package_name}"],getPackageForUser:["GET /users/{username}/packages/{package_type}/{package_name}"],getPackageVersionForAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],getPackageVersionForOrganization:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],getPackageVersionForUser:["GET /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"],listDockerMigrationConflictingPackagesForAuthenticatedUser:["GET /user/docker/conflicts"],listDockerMigrationConflictingPackagesForOrganization:["GET /orgs/{org}/docker/conflicts"],listDockerMigrationConflictingPackagesForUser:["GET /users/{username}/docker/conflicts"],listPackagesForAuthenticatedUser:["GET /user/packages"],listPackagesForOrganization:["GET /orgs/{org}/packages"],listPackagesForUser:["GET /users/{username}/packages"],restorePackageForAuthenticatedUser:["POST /user/packages/{package_type}/{package_name}/restore{?token}"],restorePackageForOrg:["POST /orgs/{org}/packages/{package_type}/{package_name}/restore{?token}"],restorePackageForUser:["POST /users/{username}/packages/{package_type}/{package_name}/restore{?token}"],restorePackageVersionForAuthenticatedUser:["POST /user/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"],restorePackageVersionForOrg:["POST /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"],restorePackageVersionForUser:["POST /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"]},projects:{addCollaborator:["PUT /projects/{project_id}/collaborators/{username}"],createCard:["POST /projects/columns/{column_id}/cards"],createColumn:["POST /projects/{project_id}/columns"],createForAuthenticatedUser:["POST /user/projects"],createForOrg:["POST /orgs/{org}/projects"],createForRepo:["POST /repos/{owner}/{repo}/projects"],delete:["DELETE /projects/{project_id}"],deleteCard:["DELETE /projects/columns/cards/{card_id}"],deleteColumn:["DELETE /projects/columns/{column_id}"],get:["GET /projects/{project_id}"],getCard:["GET /projects/columns/cards/{card_id}"],getColumn:["GET /projects/columns/{column_id}"],getPermissionForUser:["GET /projects/{project_id}/collaborators/{username}/permission"],listCards:["GET /projects/columns/{column_id}/cards"],listCollaborators:["GET /projects/{project_id}/collaborators"],listColumns:["GET /projects/{project_id}/columns"],listForOrg:["GET /orgs/{org}/projects"],listForRepo:["GET /repos/{owner}/{repo}/projects"],listForUser:["GET /users/{username}/projects"],moveCard:["POST /projects/columns/cards/{card_id}/moves"],moveColumn:["POST /projects/columns/{column_id}/moves"],removeCollaborator:["DELETE /projects/{project_id}/collaborators/{username}"],update:["PATCH /projects/{project_id}"],updateCard:["PATCH /projects/columns/cards/{card_id}"],updateColumn:["PATCH /projects/columns/{column_id}"]},pulls:{checkIfMerged:["GET /repos/{owner}/{repo}/pulls/{pull_number}/merge"],create:["POST /repos/{owner}/{repo}/pulls"],createReplyForReviewComment:["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies"],createReview:["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews"],createReviewComment:["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments"],deletePendingReview:["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],deleteReviewComment:["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}"],dismissReview:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/dismissals"],get:["GET /repos/{owner}/{repo}/pulls/{pull_number}"],getReview:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],getReviewComment:["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}"],list:["GET /repos/{owner}/{repo}/pulls"],listCommentsForReview:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments"],listCommits:["GET /repos/{owner}/{repo}/pulls/{pull_number}/commits"],listFiles:["GET /repos/{owner}/{repo}/pulls/{pull_number}/files"],listRequestedReviewers:["GET /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],listReviewComments:["GET /repos/{owner}/{repo}/pulls/{pull_number}/comments"],listReviewCommentsForRepo:["GET /repos/{owner}/{repo}/pulls/comments"],listReviews:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews"],merge:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge"],removeRequestedReviewers:["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],requestReviewers:["POST /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],submitReview:["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events"],update:["PATCH /repos/{owner}/{repo}/pulls/{pull_number}"],updateBranch:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch"],updateReview:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],updateReviewComment:["PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}"]},rateLimit:{get:["GET /rate_limit"]},reactions:{createForCommitComment:["POST /repos/{owner}/{repo}/comments/{comment_id}/reactions"],createForIssue:["POST /repos/{owner}/{repo}/issues/{issue_number}/reactions"],createForIssueComment:["POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"],createForPullRequestReviewComment:["POST /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"],createForRelease:["POST /repos/{owner}/{repo}/releases/{release_id}/reactions"],createForTeamDiscussionCommentInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"],createForTeamDiscussionInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"],deleteForCommitComment:["DELETE /repos/{owner}/{repo}/comments/{comment_id}/reactions/{reaction_id}"],deleteForIssue:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/reactions/{reaction_id}"],deleteForIssueComment:["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions/{reaction_id}"],deleteForPullRequestComment:["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions/{reaction_id}"],deleteForRelease:["DELETE /repos/{owner}/{repo}/releases/{release_id}/reactions/{reaction_id}"],deleteForTeamDiscussion:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions/{reaction_id}"],deleteForTeamDiscussionComment:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions/{reaction_id}"],listForCommitComment:["GET /repos/{owner}/{repo}/comments/{comment_id}/reactions"],listForIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/reactions"],listForIssueComment:["GET /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"],listForPullRequestReviewComment:["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"],listForRelease:["GET /repos/{owner}/{repo}/releases/{release_id}/reactions"],listForTeamDiscussionCommentInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"],listForTeamDiscussionInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"]},repos:{acceptInvitation:["PATCH /user/repository_invitations/{invitation_id}",{},{renamed:["repos","acceptInvitationForAuthenticatedUser"]}],acceptInvitationForAuthenticatedUser:["PATCH /user/repository_invitations/{invitation_id}"],addAppAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],addCollaborator:["PUT /repos/{owner}/{repo}/collaborators/{username}"],addStatusCheckContexts:["POST /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],addTeamAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],addUserAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],cancelPagesDeployment:["POST /repos/{owner}/{repo}/pages/deployments/{pages_deployment_id}/cancel"],checkAutomatedSecurityFixes:["GET /repos/{owner}/{repo}/automated-security-fixes"],checkCollaborator:["GET /repos/{owner}/{repo}/collaborators/{username}"],checkPrivateVulnerabilityReporting:["GET /repos/{owner}/{repo}/private-vulnerability-reporting"],checkVulnerabilityAlerts:["GET /repos/{owner}/{repo}/vulnerability-alerts"],codeownersErrors:["GET /repos/{owner}/{repo}/codeowners/errors"],compareCommits:["GET /repos/{owner}/{repo}/compare/{base}...{head}"],compareCommitsWithBasehead:["GET /repos/{owner}/{repo}/compare/{basehead}"],createAutolink:["POST /repos/{owner}/{repo}/autolinks"],createCommitComment:["POST /repos/{owner}/{repo}/commits/{commit_sha}/comments"],createCommitSignatureProtection:["POST /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],createCommitStatus:["POST /repos/{owner}/{repo}/statuses/{sha}"],createDeployKey:["POST /repos/{owner}/{repo}/keys"],createDeployment:["POST /repos/{owner}/{repo}/deployments"],createDeploymentBranchPolicy:["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"],createDeploymentProtectionRule:["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"],createDeploymentStatus:["POST /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"],createDispatchEvent:["POST /repos/{owner}/{repo}/dispatches"],createForAuthenticatedUser:["POST /user/repos"],createFork:["POST /repos/{owner}/{repo}/forks"],createInOrg:["POST /orgs/{org}/repos"],createOrUpdateCustomPropertiesValues:["PATCH /repos/{owner}/{repo}/properties/values"],createOrUpdateEnvironment:["PUT /repos/{owner}/{repo}/environments/{environment_name}"],createOrUpdateFileContents:["PUT /repos/{owner}/{repo}/contents/{path}"],createOrgRuleset:["POST /orgs/{org}/rulesets"],createPagesDeployment:["POST /repos/{owner}/{repo}/pages/deployments"],createPagesSite:["POST /repos/{owner}/{repo}/pages"],createRelease:["POST /repos/{owner}/{repo}/releases"],createRepoRuleset:["POST /repos/{owner}/{repo}/rulesets"],createTagProtection:["POST /repos/{owner}/{repo}/tags/protection"],createUsingTemplate:["POST /repos/{template_owner}/{template_repo}/generate"],createWebhook:["POST /repos/{owner}/{repo}/hooks"],declineInvitation:["DELETE /user/repository_invitations/{invitation_id}",{},{renamed:["repos","declineInvitationForAuthenticatedUser"]}],declineInvitationForAuthenticatedUser:["DELETE /user/repository_invitations/{invitation_id}"],delete:["DELETE /repos/{owner}/{repo}"],deleteAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"],deleteAdminBranchProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],deleteAnEnvironment:["DELETE /repos/{owner}/{repo}/environments/{environment_name}"],deleteAutolink:["DELETE /repos/{owner}/{repo}/autolinks/{autolink_id}"],deleteBranchProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection"],deleteCommitComment:["DELETE /repos/{owner}/{repo}/comments/{comment_id}"],deleteCommitSignatureProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],deleteDeployKey:["DELETE /repos/{owner}/{repo}/keys/{key_id}"],deleteDeployment:["DELETE /repos/{owner}/{repo}/deployments/{deployment_id}"],deleteDeploymentBranchPolicy:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],deleteFile:["DELETE /repos/{owner}/{repo}/contents/{path}"],deleteInvitation:["DELETE /repos/{owner}/{repo}/invitations/{invitation_id}"],deleteOrgRuleset:["DELETE /orgs/{org}/rulesets/{ruleset_id}"],deletePagesSite:["DELETE /repos/{owner}/{repo}/pages"],deletePullRequestReviewProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],deleteRelease:["DELETE /repos/{owner}/{repo}/releases/{release_id}"],deleteReleaseAsset:["DELETE /repos/{owner}/{repo}/releases/assets/{asset_id}"],deleteRepoRuleset:["DELETE /repos/{owner}/{repo}/rulesets/{ruleset_id}"],deleteTagProtection:["DELETE /repos/{owner}/{repo}/tags/protection/{tag_protection_id}"],deleteWebhook:["DELETE /repos/{owner}/{repo}/hooks/{hook_id}"],disableAutomatedSecurityFixes:["DELETE /repos/{owner}/{repo}/automated-security-fixes"],disableDeploymentProtectionRule:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"],disablePrivateVulnerabilityReporting:["DELETE /repos/{owner}/{repo}/private-vulnerability-reporting"],disableVulnerabilityAlerts:["DELETE /repos/{owner}/{repo}/vulnerability-alerts"],downloadArchive:["GET /repos/{owner}/{repo}/zipball/{ref}",{},{renamed:["repos","downloadZipballArchive"]}],downloadTarballArchive:["GET /repos/{owner}/{repo}/tarball/{ref}"],downloadZipballArchive:["GET /repos/{owner}/{repo}/zipball/{ref}"],enableAutomatedSecurityFixes:["PUT /repos/{owner}/{repo}/automated-security-fixes"],enablePrivateVulnerabilityReporting:["PUT /repos/{owner}/{repo}/private-vulnerability-reporting"],enableVulnerabilityAlerts:["PUT /repos/{owner}/{repo}/vulnerability-alerts"],generateReleaseNotes:["POST /repos/{owner}/{repo}/releases/generate-notes"],get:["GET /repos/{owner}/{repo}"],getAccessRestrictions:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"],getAdminBranchProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],getAllDeploymentProtectionRules:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"],getAllEnvironments:["GET /repos/{owner}/{repo}/environments"],getAllStatusCheckContexts:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts"],getAllTopics:["GET /repos/{owner}/{repo}/topics"],getAppsWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps"],getAutolink:["GET /repos/{owner}/{repo}/autolinks/{autolink_id}"],getBranch:["GET /repos/{owner}/{repo}/branches/{branch}"],getBranchProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection"],getBranchRules:["GET /repos/{owner}/{repo}/rules/branches/{branch}"],getClones:["GET /repos/{owner}/{repo}/traffic/clones"],getCodeFrequencyStats:["GET /repos/{owner}/{repo}/stats/code_frequency"],getCollaboratorPermissionLevel:["GET /repos/{owner}/{repo}/collaborators/{username}/permission"],getCombinedStatusForRef:["GET /repos/{owner}/{repo}/commits/{ref}/status"],getCommit:["GET /repos/{owner}/{repo}/commits/{ref}"],getCommitActivityStats:["GET /repos/{owner}/{repo}/stats/commit_activity"],getCommitComment:["GET /repos/{owner}/{repo}/comments/{comment_id}"],getCommitSignatureProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],getCommunityProfileMetrics:["GET /repos/{owner}/{repo}/community/profile"],getContent:["GET /repos/{owner}/{repo}/contents/{path}"],getContributorsStats:["GET /repos/{owner}/{repo}/stats/contributors"],getCustomDeploymentProtectionRule:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"],getCustomPropertiesValues:["GET /repos/{owner}/{repo}/properties/values"],getDeployKey:["GET /repos/{owner}/{repo}/keys/{key_id}"],getDeployment:["GET /repos/{owner}/{repo}/deployments/{deployment_id}"],getDeploymentBranchPolicy:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],getDeploymentStatus:["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses/{status_id}"],getEnvironment:["GET /repos/{owner}/{repo}/environments/{environment_name}"],getLatestPagesBuild:["GET /repos/{owner}/{repo}/pages/builds/latest"],getLatestRelease:["GET /repos/{owner}/{repo}/releases/latest"],getOrgRuleSuite:["GET /orgs/{org}/rulesets/rule-suites/{rule_suite_id}"],getOrgRuleSuites:["GET /orgs/{org}/rulesets/rule-suites"],getOrgRuleset:["GET /orgs/{org}/rulesets/{ruleset_id}"],getOrgRulesets:["GET /orgs/{org}/rulesets"],getPages:["GET /repos/{owner}/{repo}/pages"],getPagesBuild:["GET /repos/{owner}/{repo}/pages/builds/{build_id}"],getPagesDeployment:["GET /repos/{owner}/{repo}/pages/deployments/{pages_deployment_id}"],getPagesHealthCheck:["GET /repos/{owner}/{repo}/pages/health"],getParticipationStats:["GET /repos/{owner}/{repo}/stats/participation"],getPullRequestReviewProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],getPunchCardStats:["GET /repos/{owner}/{repo}/stats/punch_card"],getReadme:["GET /repos/{owner}/{repo}/readme"],getReadmeInDirectory:["GET /repos/{owner}/{repo}/readme/{dir}"],getRelease:["GET /repos/{owner}/{repo}/releases/{release_id}"],getReleaseAsset:["GET /repos/{owner}/{repo}/releases/assets/{asset_id}"],getReleaseByTag:["GET /repos/{owner}/{repo}/releases/tags/{tag}"],getRepoRuleSuite:["GET /repos/{owner}/{repo}/rulesets/rule-suites/{rule_suite_id}"],getRepoRuleSuites:["GET /repos/{owner}/{repo}/rulesets/rule-suites"],getRepoRuleset:["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}"],getRepoRulesets:["GET /repos/{owner}/{repo}/rulesets"],getStatusChecksProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],getTeamsWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams"],getTopPaths:["GET /repos/{owner}/{repo}/traffic/popular/paths"],getTopReferrers:["GET /repos/{owner}/{repo}/traffic/popular/referrers"],getUsersWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users"],getViews:["GET /repos/{owner}/{repo}/traffic/views"],getWebhook:["GET /repos/{owner}/{repo}/hooks/{hook_id}"],getWebhookConfigForRepo:["GET /repos/{owner}/{repo}/hooks/{hook_id}/config"],getWebhookDelivery:["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}"],listActivities:["GET /repos/{owner}/{repo}/activity"],listAutolinks:["GET /repos/{owner}/{repo}/autolinks"],listBranches:["GET /repos/{owner}/{repo}/branches"],listBranchesForHeadCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/branches-where-head"],listCollaborators:["GET /repos/{owner}/{repo}/collaborators"],listCommentsForCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/comments"],listCommitCommentsForRepo:["GET /repos/{owner}/{repo}/comments"],listCommitStatusesForRef:["GET /repos/{owner}/{repo}/commits/{ref}/statuses"],listCommits:["GET /repos/{owner}/{repo}/commits"],listContributors:["GET /repos/{owner}/{repo}/contributors"],listCustomDeploymentRuleIntegrations:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/apps"],listDeployKeys:["GET /repos/{owner}/{repo}/keys"],listDeploymentBranchPolicies:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"],listDeploymentStatuses:["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"],listDeployments:["GET /repos/{owner}/{repo}/deployments"],listForAuthenticatedUser:["GET /user/repos"],listForOrg:["GET /orgs/{org}/repos"],listForUser:["GET /users/{username}/repos"],listForks:["GET /repos/{owner}/{repo}/forks"],listInvitations:["GET /repos/{owner}/{repo}/invitations"],listInvitationsForAuthenticatedUser:["GET /user/repository_invitations"],listLanguages:["GET /repos/{owner}/{repo}/languages"],listPagesBuilds:["GET /repos/{owner}/{repo}/pages/builds"],listPublic:["GET /repositories"],listPullRequestsAssociatedWithCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls"],listReleaseAssets:["GET /repos/{owner}/{repo}/releases/{release_id}/assets"],listReleases:["GET /repos/{owner}/{repo}/releases"],listTagProtection:["GET /repos/{owner}/{repo}/tags/protection"],listTags:["GET /repos/{owner}/{repo}/tags"],listTeams:["GET /repos/{owner}/{repo}/teams"],listWebhookDeliveries:["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries"],listWebhooks:["GET /repos/{owner}/{repo}/hooks"],merge:["POST /repos/{owner}/{repo}/merges"],mergeUpstream:["POST /repos/{owner}/{repo}/merge-upstream"],pingWebhook:["POST /repos/{owner}/{repo}/hooks/{hook_id}/pings"],redeliverWebhookDelivery:["POST /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"],removeAppAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],removeCollaborator:["DELETE /repos/{owner}/{repo}/collaborators/{username}"],removeStatusCheckContexts:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],removeStatusCheckProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],removeTeamAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],removeUserAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],renameBranch:["POST /repos/{owner}/{repo}/branches/{branch}/rename"],replaceAllTopics:["PUT /repos/{owner}/{repo}/topics"],requestPagesBuild:["POST /repos/{owner}/{repo}/pages/builds"],setAdminBranchProtection:["POST /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],setAppAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],setStatusCheckContexts:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],setTeamAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],setUserAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],testPushWebhook:["POST /repos/{owner}/{repo}/hooks/{hook_id}/tests"],transfer:["POST /repos/{owner}/{repo}/transfer"],update:["PATCH /repos/{owner}/{repo}"],updateBranchProtection:["PUT /repos/{owner}/{repo}/branches/{branch}/protection"],updateCommitComment:["PATCH /repos/{owner}/{repo}/comments/{comment_id}"],updateDeploymentBranchPolicy:["PUT /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],updateInformationAboutPagesSite:["PUT /repos/{owner}/{repo}/pages"],updateInvitation:["PATCH /repos/{owner}/{repo}/invitations/{invitation_id}"],updateOrgRuleset:["PUT /orgs/{org}/rulesets/{ruleset_id}"],updatePullRequestReviewProtection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],updateRelease:["PATCH /repos/{owner}/{repo}/releases/{release_id}"],updateReleaseAsset:["PATCH /repos/{owner}/{repo}/releases/assets/{asset_id}"],updateRepoRuleset:["PUT /repos/{owner}/{repo}/rulesets/{ruleset_id}"],updateStatusCheckPotection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks",{},{renamed:["repos","updateStatusCheckProtection"]}],updateStatusCheckProtection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],updateWebhook:["PATCH /repos/{owner}/{repo}/hooks/{hook_id}"],updateWebhookConfigForRepo:["PATCH /repos/{owner}/{repo}/hooks/{hook_id}/config"],uploadReleaseAsset:["POST /repos/{owner}/{repo}/releases/{release_id}/assets{?name,label}",{baseUrl:"https://uploads.github.com"}]},search:{code:["GET /search/code"],commits:["GET /search/commits"],issuesAndPullRequests:["GET /search/issues"],labels:["GET /search/labels"],repos:["GET /search/repositories"],topics:["GET /search/topics"],users:["GET /search/users"]},secretScanning:{getAlert:["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"],listAlertsForEnterprise:["GET /enterprises/{enterprise}/secret-scanning/alerts"],listAlertsForOrg:["GET /orgs/{org}/secret-scanning/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/secret-scanning/alerts"],listLocationsForAlert:["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}/locations"],updateAlert:["PATCH /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"]},securityAdvisories:{createFork:["POST /repos/{owner}/{repo}/security-advisories/{ghsa_id}/forks"],createPrivateVulnerabilityReport:["POST /repos/{owner}/{repo}/security-advisories/reports"],createRepositoryAdvisory:["POST /repos/{owner}/{repo}/security-advisories"],createRepositoryAdvisoryCveRequest:["POST /repos/{owner}/{repo}/security-advisories/{ghsa_id}/cve"],getGlobalAdvisory:["GET /advisories/{ghsa_id}"],getRepositoryAdvisory:["GET /repos/{owner}/{repo}/security-advisories/{ghsa_id}"],listGlobalAdvisories:["GET /advisories"],listOrgRepositoryAdvisories:["GET /orgs/{org}/security-advisories"],listRepositoryAdvisories:["GET /repos/{owner}/{repo}/security-advisories"],updateRepositoryAdvisory:["PATCH /repos/{owner}/{repo}/security-advisories/{ghsa_id}"]},teams:{addOrUpdateMembershipForUserInOrg:["PUT /orgs/{org}/teams/{team_slug}/memberships/{username}"],addOrUpdateProjectPermissionsInOrg:["PUT /orgs/{org}/teams/{team_slug}/projects/{project_id}"],addOrUpdateRepoPermissionsInOrg:["PUT /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],checkPermissionsForProjectInOrg:["GET /orgs/{org}/teams/{team_slug}/projects/{project_id}"],checkPermissionsForRepoInOrg:["GET /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],create:["POST /orgs/{org}/teams"],createDiscussionCommentInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"],createDiscussionInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions"],deleteDiscussionCommentInOrg:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],deleteDiscussionInOrg:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],deleteInOrg:["DELETE /orgs/{org}/teams/{team_slug}"],getByName:["GET /orgs/{org}/teams/{team_slug}"],getDiscussionCommentInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],getDiscussionInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],getMembershipForUserInOrg:["GET /orgs/{org}/teams/{team_slug}/memberships/{username}"],list:["GET /orgs/{org}/teams"],listChildInOrg:["GET /orgs/{org}/teams/{team_slug}/teams"],listDiscussionCommentsInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"],listDiscussionsInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions"],listForAuthenticatedUser:["GET /user/teams"],listMembersInOrg:["GET /orgs/{org}/teams/{team_slug}/members"],listPendingInvitationsInOrg:["GET /orgs/{org}/teams/{team_slug}/invitations"],listProjectsInOrg:["GET /orgs/{org}/teams/{team_slug}/projects"],listReposInOrg:["GET /orgs/{org}/teams/{team_slug}/repos"],removeMembershipForUserInOrg:["DELETE /orgs/{org}/teams/{team_slug}/memberships/{username}"],removeProjectInOrg:["DELETE /orgs/{org}/teams/{team_slug}/projects/{project_id}"],removeRepoInOrg:["DELETE /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],updateDiscussionCommentInOrg:["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],updateDiscussionInOrg:["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],updateInOrg:["PATCH /orgs/{org}/teams/{team_slug}"]},users:{addEmailForAuthenticated:["POST /user/emails",{},{renamed:["users","addEmailForAuthenticatedUser"]}],addEmailForAuthenticatedUser:["POST /user/emails"],addSocialAccountForAuthenticatedUser:["POST /user/social_accounts"],block:["PUT /user/blocks/{username}"],checkBlocked:["GET /user/blocks/{username}"],checkFollowingForUser:["GET /users/{username}/following/{target_user}"],checkPersonIsFollowedByAuthenticated:["GET /user/following/{username}"],createGpgKeyForAuthenticated:["POST /user/gpg_keys",{},{renamed:["users","createGpgKeyForAuthenticatedUser"]}],createGpgKeyForAuthenticatedUser:["POST /user/gpg_keys"],createPublicSshKeyForAuthenticated:["POST /user/keys",{},{renamed:["users","createPublicSshKeyForAuthenticatedUser"]}],createPublicSshKeyForAuthenticatedUser:["POST /user/keys"],createSshSigningKeyForAuthenticatedUser:["POST /user/ssh_signing_keys"],deleteEmailForAuthenticated:["DELETE /user/emails",{},{renamed:["users","deleteEmailForAuthenticatedUser"]}],deleteEmailForAuthenticatedUser:["DELETE /user/emails"],deleteGpgKeyForAuthenticated:["DELETE /user/gpg_keys/{gpg_key_id}",{},{renamed:["users","deleteGpgKeyForAuthenticatedUser"]}],deleteGpgKeyForAuthenticatedUser:["DELETE /user/gpg_keys/{gpg_key_id}"],deletePublicSshKeyForAuthenticated:["DELETE /user/keys/{key_id}",{},{renamed:["users","deletePublicSshKeyForAuthenticatedUser"]}],deletePublicSshKeyForAuthenticatedUser:["DELETE /user/keys/{key_id}"],deleteSocialAccountForAuthenticatedUser:["DELETE /user/social_accounts"],deleteSshSigningKeyForAuthenticatedUser:["DELETE /user/ssh_signing_keys/{ssh_signing_key_id}"],follow:["PUT /user/following/{username}"],getAuthenticated:["GET /user"],getByUsername:["GET /users/{username}"],getContextForUser:["GET /users/{username}/hovercard"],getGpgKeyForAuthenticated:["GET /user/gpg_keys/{gpg_key_id}",{},{renamed:["users","getGpgKeyForAuthenticatedUser"]}],getGpgKeyForAuthenticatedUser:["GET /user/gpg_keys/{gpg_key_id}"],getPublicSshKeyForAuthenticated:["GET /user/keys/{key_id}",{},{renamed:["users","getPublicSshKeyForAuthenticatedUser"]}],getPublicSshKeyForAuthenticatedUser:["GET /user/keys/{key_id}"],getSshSigningKeyForAuthenticatedUser:["GET /user/ssh_signing_keys/{ssh_signing_key_id}"],list:["GET /users"],listBlockedByAuthenticated:["GET /user/blocks",{},{renamed:["users","listBlockedByAuthenticatedUser"]}],listBlockedByAuthenticatedUser:["GET /user/blocks"],listEmailsForAuthenticated:["GET /user/emails",{},{renamed:["users","listEmailsForAuthenticatedUser"]}],listEmailsForAuthenticatedUser:["GET /user/emails"],listFollowedByAuthenticated:["GET /user/following",{},{renamed:["users","listFollowedByAuthenticatedUser"]}],listFollowedByAuthenticatedUser:["GET /user/following"],listFollowersForAuthenticatedUser:["GET /user/followers"],listFollowersForUser:["GET /users/{username}/followers"],listFollowingForUser:["GET /users/{username}/following"],listGpgKeysForAuthenticated:["GET /user/gpg_keys",{},{renamed:["users","listGpgKeysForAuthenticatedUser"]}],listGpgKeysForAuthenticatedUser:["GET /user/gpg_keys"],listGpgKeysForUser:["GET /users/{username}/gpg_keys"],listPublicEmailsForAuthenticated:["GET /user/public_emails",{},{renamed:["users","listPublicEmailsForAuthenticatedUser"]}],listPublicEmailsForAuthenticatedUser:["GET /user/public_emails"],listPublicKeysForUser:["GET /users/{username}/keys"],listPublicSshKeysForAuthenticated:["GET /user/keys",{},{renamed:["users","listPublicSshKeysForAuthenticatedUser"]}],listPublicSshKeysForAuthenticatedUser:["GET /user/keys"],listSocialAccountsForAuthenticatedUser:["GET /user/social_accounts"],listSocialAccountsForUser:["GET /users/{username}/social_accounts"],listSshSigningKeysForAuthenticatedUser:["GET /user/ssh_signing_keys"],listSshSigningKeysForUser:["GET /users/{username}/ssh_signing_keys"],setPrimaryEmailVisibilityForAuthenticated:["PATCH /user/email/visibility",{},{renamed:["users","setPrimaryEmailVisibilityForAuthenticatedUser"]}],setPrimaryEmailVisibilityForAuthenticatedUser:["PATCH /user/email/visibility"],unblock:["DELETE /user/blocks/{username}"],unfollow:["DELETE /user/following/{username}"],updateAuthenticated:["PATCH /user"]}},ro=eo,Y=new Map;for(const[r,e]of Object.entries(ro))for(const[t,s]of Object.entries(e)){const[o,a,n]=s,[i,l]=o.split(/ /),p=Object.assign({method:i,url:l},a);Y.has(r)||Y.set(r,new Map),Y.get(r).set(t,{scope:r,methodName:t,endpointDefaults:p,decorations:n})}var to={has({scope:r},e){return Y.get(r).has(e)},getOwnPropertyDescriptor(r,e){return{value:this.get(r,e),configurable:!0,writable:!0,enumerable:!0}},defineProperty(r,e,t){return Object.defineProperty(r.cache,e,t),!0},deleteProperty(r,e){return delete r.cache[e],!0},ownKeys({scope:r}){return[...Y.get(r).keys()]},set(r,e,t){return r.cache[e]=t},get({octokit:r,scope:e,cache:t},s){if(t[s])return t[s];const o=Y.get(e).get(s);if(!o)return;const{endpointDefaults:a,decorations:n}=o;return n?t[s]=oo(r,e,s,a,n):t[s]=r.request.defaults(a),t[s]}};function so(r){const e={};for(const t of Y.keys())e[t]=new Proxy({octokit:r,scope:t,cache:{}},to);return e}function oo(r,e,t,s,o){const a=r.request.defaults(s);function n(...i){let l=a.endpoint.merge(...i);if(o.mapToData)return l=Object.assign({},l,{data:l[o.mapToData],[o.mapToData]:void 0}),a(l);if(o.renamed){const[p,g]=o.renamed;r.log.warn(`octokit.${e}.${t}() has been renamed to octokit.${p}.${g}()`)}if(o.deprecated&&r.log.warn(o.deprecated),o.renamedParameters){const p=a.endpoint.merge(...i);for(const[g,u]of Object.entries(o.renamedParameters))g in p&&(r.log.warn(`"${g}" parameter is deprecated for "octokit.${e}.${t}()". Use "${u}" instead`),u in p||(p[u]=p[g]),delete p[g]);return a(p)}return a(...i)}return Object.assign(n,a)}function qr(r){const e=so(r);return{...e,rest:e}}qr.VERSION=Ys;var no="20.1.1",ao=Js.plugin(Ir,qr,$r).defaults({userAgent:`octokit-rest.js/${no}`});const io={beforeDevCommand:"yarn dev",beforeBuildCommand:"yarn build",devPath:"http://localhost:5173",distDir:"../dist",withGlobalTauri:!1},co={allowlist:{all:!0,fs:{all:!0,scope:["**"]},shell:{all:!0,open:!0,sidecar:!0,scope:[{name:"iib_api_server",sidecar:!0}]}},bundle:{active:!0,targets:"all",identifier:"com.zanllp.iib",icon:["icons/32x32.png","icons/128x128.png","icons/128x128@2x.png","icons/icon.icns","icons/icon.ico"],externalBin:["iib_api_server"]},security:{csp:null},windows:[{fullscreen:!1,resizable:!0,fileDropEnabled:!1,title:"Infinite Image Browsing",width:800,height:600,maximized:!0}]},lo={build:io,package:{productName:"Infinite Image Browsing",version:"1.0.0"},tauri:co},Br=new ao,uo="v"+lo.package.version,ne=H(),Ie=H(""),Hr=H(""),zr=H(""),ae=L(()=>({tag:zr.value||uo,hash:Hr.value})),po=L(()=>Ie.value?Ie.value!==ae.value.tag:!1);async function go(r,e){try{return(await Br.repos.listCommits({owner:r,repo:e,per_page:1})).data[0]}catch(t){console.error("Error fetching the latest commit:",t)}}async function mo(r,e){try{return(await Br.repos.getLatestRelease({owner:r,repo:e})).data}catch(t){console.error("Error fetching the latest release:",t)}}const mr="zanllp",hr="sd-webui-infinite-image-browsing";Tt(500+500*Math.random()).then(async()=>{Et().then(e=>{zr.value=e.tag??"",Hr.value=e.hash??""}),ne.value=await go(mr,hr);const r=await mo(mr,hr);Ie.value=(r==null?void 0:r.tag_name)??""});const Ge=r=>(Gt("data-v-29c5fa3e"),r=r(),Ot(),r),ho={class:"container"},To={class:"header"},Eo={key:0,style:{"margin-left":"16px","font-size":"1.5em"}},fo=Ge(()=>E("div",{"flex-placeholder":""},null,-1)),bo=Ge(()=>E("a",{href:"https://github.com/zanllp/sd-webui-infinite-image-browsing",target:"_blank",class:"quick-action"},"Github",-1)),vo={href:"https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/.env.example",target:"_blank",class:"quick-action"},_o=Ge(()=>E("a",{href:"https://github.com/zanllp/sd-webui-infinite-image-browsing/releases",target:"_blank",class:"quick-action"},"Releases",-1)),wo={href:"https://github.com/zanllp/sd-webui-infinite-image-browsing/wiki/Change-log",target:"_blank",class:"quick-action"},yo={href:"https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/90",target:"_blank",class:"quick-action"},ko={key:1,class:"quick-action"},Po={class:"access-mode-message"},Go=Ge(()=>E("div",{"flex-placeholder":""},null,-1)),Oo={class:"content"},So={class:"feature-item"},Ao={class:"text line-clamp-1"},Ro=["onClick"],Co={class:"text line-clamp-2"},Fo={key:0,class:"feature-item"},Uo={class:"text line-clamp-1"},Do=["onClick"],Lo={class:"text line-clamp-2"},Io={key:0,class:"fixed"},xo={class:"feature-item"},jo=["onClick"],$o={class:"text line-clamp-1"},qo={class:"text line-clamp-1"},Bo={class:"text line-clamp-1"},Ho={class:"text line-clamp-1"},zo=["onClick"],No={class:"text line-clamp-1"},Vo={key:1,class:"feature-item recent"},Mo={class:"title"},Wo=["onClick"],Ko={class:"text line-clamp-1"},Jo={key:0},Zo={key:1},Qo={key:2},Xo={key:3},Yo=J({__name:"emptyStartup",props:{tabIdx:{},paneIdx:{},popAddPathModal:{}},setup(r){const e=r,t=br(),s=ft(),o=bt();vt(()=>{e.popAddPathModal&&Se(e.popAddPathModal.type,e.popAddPathModal.path)});const a=_t(),n={local:C("local"),"tag-search":C("imgSearch"),"fuzzy-search":C("fuzzy-search"),"batch-download":C("batchDownload")+" / "+C("archive"),"workspace-snapshot":C("WorkspaceSnapshot"),"global-setting":C("globalSettings")},i=(c,m,P)=>{let _;switch(c){case"grid-view":case"tag-search-matched-image-grid":case"img-sli":return;case"global-setting":case"tag-search":case"batch-download":case"workspace-snapshot":case"fuzzy-search":case"empty":_={type:c,name:n[c],key:Date.now()+Oe()};break;case"local":_={type:c,name:n[c],key:Date.now()+Oe(),path:m,mode:P==="scanned-fixed"||P==="walk"?P:"scanned"}}return _},l=(c,m,P)=>{const _=i(c,m,P);if(!_)return;const G=t.tabList[e.tabIdx];G.panes.splice(e.paneIdx,1,_),G.key=_.key},p=(c,m,P)=>{const _=i(c,m,P);if(!_)return;t.tabList[e.tabIdx].panes.push(_)},g=(c,m,P)=>{const _=i(c,m,P);if(!_)return;let G=t.tabList[e.tabIdx+1];G||(G={panes:[],key:"",id:Oe()},t.tabList[e.tabIdx+1]=G),G.panes.push(_),G.key=_.key},u=L(()=>{var c;return(c=t.tabListHistoryRecord)==null?void 0:c[1]}),f=L(()=>t.quickMovePaths.filter(({key:c,types:m})=>c==="outdir_txt2img_samples"||c==="outdir_img2img_samples"||c==="outdir_txt2img_grids"||c==="outdir_img2img_grids"||m.includes("walk"))),y=window.parent!==window,S=()=>window.parent.open("/infinite_image_browsing"+(window.parent.location.href.includes("theme=dark")?"?__theme=dark":"")),A=()=>{yt(u.value),t.tabList=Je(u.value.tabs)},v=c=>{t.tabList=Je(c.tabs)},R=L(()=>{var c;return Ce?"desktop application":((c=t.conf)==null?void 0:c.launch_mode)==="sd"?"sd-webui extension":"standalone"}),q=c=>!c||c==="scanned"?"":c==="walk"?"Walk: ":"Fixed: ",x=L(()=>{var m,P;const c=[];return(m=t.conf)!=null&&m.enable_access_control&&c.push("accessLimited"),(P=t.conf)!=null&&P.is_readonly&&c.push("readonly"),c.map(_=>C(_)).join(" + ")});return(c,m)=>{var V,M,re,X;const P=_e,_=kt,G=Rt,N=Te,ee=qe,Q=Pt,j=vr;return w(),O("div",ho,[E("div",To,[E("h1",null,b(c.$t("welcome")),1),(V=T(t).conf)!=null&&V.enable_access_control&&T(t).dontShowAgain?(w(),O("div",Eo,[h(T(Ye),{title:"Access Control mode",style:{"vertical-align":"text-bottom"}})])):B("",!0),fo,bo,E("a",vo,b(c.$t("privacyAndSecurity")),1),h(P,{count:T(po)?"new":null,offset:[2,0],color:"geekblue"},{default:U(()=>[_o]),_:1},8,["count"]),E("a",wo,b(c.$t("changlog")),1),E("a",yo,b(c.$t("faq")),1),T(Ce)?B("",!0):(w(),O("div",ko,[$(b(c.$t("sync"))+" ",1),h(_,{title:c.$t("syncDesc")},{default:U(()=>[h(T(rs))]),_:1},8,["title"]),$(" : "),h(G,{checked:T(a),"onUpdate:checked":m[0]||(m[0]=d=>wt(a)?a.value=d:null)},null,8,["checked"])])),h(ee,{value:T(t).darkModeControl,"onUpdate:value":m[1]||(m[1]=d=>T(t).darkModeControl=d),"button-style":"solid"},{default:U(()=>[h(N,{value:"light"},{default:U(()=>[$("Light")]),_:1}),h(N,{value:"auto"},{default:U(()=>[$("Auto")]),_:1}),h(N,{value:"dark"},{default:U(()=>[$("Dark")]),_:1})]),_:1},8,["value"])]),(M=T(t).conf)!=null&&M.enable_access_control&&!T(t).dontShowAgain?(w(),ve(Q,{key:0,"show-icon":""},{message:U(()=>[E("div",Po,[E("div",null,b(c.$t("accessControlModeTips")),1),Go,E("a",{onClick:m[2]||(m[2]=W(d=>T(t).dontShowAgain=!0,["prevent"]))},b(c.$t("dontShowAgain")),1)])]),icon:U(()=>[h(T(Ye))]),_:1})):B("",!0),E("div",Oo,[E("div",So,[E("h2",null,b(c.$t("walkMode")),1),E("ul",null,[E("li",{onClick:m[3]||(m[3]=d=>T(Se)("walk")),class:"item"},[E("span",Ao,[h(T(Ke)),$(" "+b(c.$t("add")),1)])]),(w(!0),O(K,null,se(f.value,d=>(w(),ve(sr,{key:d.key,onOpenInNewTab:F=>p("local",d.dir,"walk"),onOpenOnTheRight:F=>g("local",d.dir,"walk")},{default:U(()=>[E("li",{class:"item rem",onClick:W(F=>l("local",d.dir,"walk"),["prevent"])},[E("span",Co,b(d.zh),1),d.can_delete?(w(),O(K,{key:0},[h(j,{type:"link",onClick:W(F=>T(tr)(d.dir),["stop"])},{default:U(()=>[$(b(c.$t("alias")),1)]),_:2},1032,["onClick"]),h(j,{type:"link",onClick:W(F=>T(rr)(d.dir,"walk"),["stop"])},{default:U(()=>[$(b(c.$t("remove")),1)]),_:2},1032,["onClick"])],64)):B("",!0)],8,Ro)]),_:2},1032,["onOpenInNewTab","onOpenOnTheRight"]))),128))])]),T(t).quickMovePaths.length?(w(),O("div",Fo,[E("h2",null,b(c.$t("launchFromNormalAndFixed")),1),E("ul",null,[E("li",{onClick:m[4]||(m[4]=d=>T(Se)("scanned")),class:"item"},[E("span",Uo,[h(T(Ke)),$(" "+b(c.$t("add")),1)])]),(w(!0),O(K,null,se(T(t).quickMovePaths.filter(({types:d})=>d.includes("cli_access_only")||d.includes("preset")||d.includes("scanned")||d.includes("scanned-fixed")),d=>(w(),O(K,{key:d.key},[(w(!0),O(K,null,se(d.types.filter(F=>F!=="walk"),F=>(w(),ve(sr,{key:F,onOpenInNewTab:te=>p("local",d.dir,F),onOpenOnTheRight:te=>g("local",d.dir,F)},{default:U(()=>[E("li",{class:"item rem",onClick:W(te=>l("local",d.dir,F),["prevent"])},[E("span",Lo,[F=="scanned-fixed"?(w(),O("span",Io,"Fixed")):B("",!0),$(b(d.zh),1)]),d.can_delete&&(F==="scanned-fixed"||F==="scanned")?(w(),O(K,{key:0},[h(j,{type:"link",onClick:W(te=>T(tr)(d.dir),["stop"])},{default:U(()=>[$(b(c.$t("alias")),1)]),_:2},1032,["onClick"]),h(j,{type:"link",onClick:W(te=>T(rr)(d.dir,F),["stop"])},{default:U(()=>[$(b(c.$t("remove")),1)]),_:2},1032,["onClick"])],64)):B("",!0)],8,Do)]),_:2},1032,["onOpenInNewTab","onOpenOnTheRight"]))),128))],64))),128))])])):B("",!0),E("div",xo,[E("h2",null,b(c.$t("launch")),1),E("ul",null,[(w(!0),O(K,null,se(Object.keys(n),d=>(w(),O("li",{key:d,class:"item",onClick:W(F=>l(d),["prevent"])},[E("span",$o,b(n[d]),1)],8,jo))),128)),E("li",{class:"item",onClick:m[5]||(m[5]=d=>T(s).opened=!0)},[E("span",qo,b(c.$t("imgCompare")),1)]),y?(w(),O("li",{key:0,class:"item",onClick:S},[E("span",Bo,b(c.$t("openThisAppInNewWindow")),1)])):B("",!0),(re=u.value)!=null&&re.tabs.length?(w(),O("li",{key:1,class:"item",onClick:A},[E("span",Ho,b(c.$t("restoreLastWorkspaceState")),1)])):B("",!0),(w(!0),O(K,null,se(T(o).snapshots,d=>(w(),O("li",{class:"item",key:d.id,onClick:F=>v(d)},[E("span",No,b(c.$t("restoreWorkspaceSnapshot",[d.name])),1)],8,zo))),128))])]),T(t).recent.length?(w(),O("div",Vo,[E("div",Mo,[E("h2",null,b(c.$t("recent")),1),h(j,{onClick:m[6]||(m[6]=d=>T(t).recent=[]),type:"link"},{default:U(()=>[$(b(c.$t("clear")),1)]),_:1})]),E("ul",null,[(w(!0),O(K,null,se(T(t).recent,d=>(w(),O("li",{key:d.key,class:"item",onClick:W(F=>l("local",d.path,d.mode),["prevent"])},[h(T(Kt),{class:"icon"}),E("span",Ko,b(q(d.mode))+b(T(t).getShortPath(d.path)),1)],8,Wo))),128))])])):B("",!0)]),E("div",{class:"ver-info",onDblclick:m[7]||(m[7]=d=>T(ce).info("Ciallo~(∠・ω< )⌒☆"))},[x.value?(w(),O("div",Jo," Mode: "+b(x.value),1)):B("",!0),E("div",null," Version: "+b(T(ae).tag)+" ("+b(R.value)+") ",1),T(ae).hash?(w(),O("div",Zo," Hash: "+b(T(ae).hash),1)):B("",!0),T(ne)&&T(ae).hash&&T(ne).sha!==T(ae).hash?(w(),O("div",Qo," Not the latest commit ")):B("",!0),T(ne)?(w(),O("div",Xo," Latest Commit: "+b(T(ne).sha)+" (Updated at "+b((X=T(ne).commit.author)==null?void 0:X.date)+") ",1)):B("",!0)],32)])}}});const nn=St(Yo,[["__scopeId","data-v-29c5fa3e"]]);export{nn as default}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/emptyStartup-954396a3.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/emptyStartup-954396a3.css new file mode 100644 index 0000000000000000000000000000000000000000..3bba15eb611cf7b006467964d47ddaba4b6b663b --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/emptyStartup-954396a3.css @@ -0,0 +1 @@ +.ant-radio-group{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block;font-size:0}.ant-radio-group .ant-badge-count{z-index:1}.ant-radio-group>.ant-badge:not(:first-child)>.ant-radio-button-wrapper{border-left:none}.ant-radio-wrapper{box-sizing:border-box;margin:0 8px 0 0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-flex;align-items:baseline;cursor:pointer}.ant-radio-wrapper-disabled{cursor:not-allowed}.ant-radio-wrapper:after{display:inline-block;width:0;overflow:hidden;content:" "}.ant-radio{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;display:inline-block;outline:none;cursor:pointer}.ant-radio-wrapper:hover .ant-radio,.ant-radio:hover .ant-radio-inner,.ant-radio-input:focus+.ant-radio-inner{border-color:#d03f0a}.ant-radio-input:focus+.ant-radio-inner{box-shadow:0 0 0 3px #fff1e6}.ant-radio-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #d03f0a;border-radius:50%;visibility:hidden;animation:antRadioEffect .36s ease-in-out;animation-fill-mode:both;content:""}.ant-radio:hover:after,.ant-radio-wrapper:hover .ant-radio:after{visibility:visible}.ant-radio-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border-color:#d9d9d9;border-style:solid;border-width:1px;border-radius:50%;transition:all .3s}.ant-radio-inner:after{position:absolute;top:50%;left:50%;display:block;width:16px;height:16px;margin-top:-8px;margin-left:-8px;background-color:#d03f0a;border-top:0;border-left:0;border-radius:16px;transform:scale(0);opacity:0;transition:all .3s cubic-bezier(.78,.14,.15,.86);content:" "}.ant-radio-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;cursor:pointer;opacity:0}.ant-radio-checked .ant-radio-inner{border-color:#d03f0a}.ant-radio-checked .ant-radio-inner:after{transform:scale(.5);opacity:1;transition:all .3s cubic-bezier(.78,.14,.15,.86)}.ant-radio-disabled{cursor:not-allowed}.ant-radio-disabled .ant-radio-inner{background-color:#f5f5f5;border-color:#d9d9d9!important;cursor:not-allowed}.ant-radio-disabled .ant-radio-inner:after{background-color:#0003}.ant-radio-disabled .ant-radio-input{cursor:not-allowed}.ant-radio-disabled+span{color:#00000040;cursor:not-allowed}span.ant-radio+*{padding-right:8px;padding-left:8px}.ant-radio-button-wrapper{position:relative;display:inline-block;height:32px;margin:0;padding:0 15px;color:#000000d9;font-size:14px;line-height:30px;background:#fff;border:1px solid #d9d9d9;border-top-width:1.02px;border-left-width:0;cursor:pointer;transition:color .3s,background .3s,border-color .3s,box-shadow .3s}.ant-radio-button-wrapper a{color:#000000d9}.ant-radio-button-wrapper>.ant-radio-button{position:absolute;top:0;left:0;z-index:-1;width:100%;height:100%}.ant-radio-group-large .ant-radio-button-wrapper{height:40px;font-size:16px;line-height:38px}.ant-radio-group-small .ant-radio-button-wrapper{height:24px;padding:0 7px;line-height:22px}.ant-radio-button-wrapper:not(:first-child):before{position:absolute;top:-1px;left:-1px;display:block;box-sizing:content-box;width:1px;height:100%;padding:1px 0;background-color:#d9d9d9;transition:background-color .3s;content:""}.ant-radio-button-wrapper:first-child{border-left:1px solid #d9d9d9;border-radius:2px 0 0 2px}.ant-radio-button-wrapper:last-child{border-radius:0 2px 2px 0}.ant-radio-button-wrapper:first-child:last-child{border-radius:2px}.ant-radio-button-wrapper:hover{position:relative;color:#d03f0a}.ant-radio-button-wrapper:focus-within{box-shadow:0 0 0 3px #fff1e6}.ant-radio-button-wrapper .ant-radio-inner,.ant-radio-button-wrapper input[type=checkbox],.ant-radio-button-wrapper input[type=radio]{width:0;height:0;opacity:0;pointer-events:none}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){z-index:1;color:#d03f0a;background:#fff;border-color:#d03f0a}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):before{background-color:#d03f0a}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child{border-color:#d03f0a}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#de632f;border-color:#de632f}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover:before{background-color:#de632f}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#ab2800;border-color:#ab2800}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active:before{background-color:#ab2800}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{box-shadow:0 0 0 3px #fff1e6}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){color:#fff;background:#d03f0a;border-color:#d03f0a}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#fff;background:#de632f;border-color:#de632f}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#fff;background:#ab2800;border-color:#ab2800}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{box-shadow:0 0 0 3px #fff1e6}.ant-radio-button-wrapper-disabled{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.ant-radio-button-wrapper-disabled:first-child,.ant-radio-button-wrapper-disabled:hover{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9}.ant-radio-button-wrapper-disabled:first-child{border-left-color:#d9d9d9}.ant-radio-button-wrapper-disabled.ant-radio-button-wrapper-checked{color:#00000040;background-color:#e6e6e6;border-color:#d9d9d9;box-shadow:none}@keyframes antRadioEffect{0%{transform:scale(1);opacity:.5}to{transform:scale(1.6);opacity:0}}.ant-radio-group.ant-radio-group-rtl{direction:rtl}.ant-radio-wrapper.ant-radio-wrapper-rtl{margin-right:0;margin-left:8px;direction:rtl}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl{border-right-width:0;border-left-width:1px}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:not(:first-child):before{right:-1px;left:0}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:first-child{border-right:1px solid #d9d9d9;border-radius:0 2px 2px 0}.ant-radio-button-wrapper-checked:not([class*=" ant-radio-button-wrapper-disabled"]).ant-radio-button-wrapper:first-child{border-right-color:#de632f}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:last-child{border-radius:2px 0 0 2px}.ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper-disabled:first-child{border-right-color:#d9d9d9}.ant-badge{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;line-height:1}.ant-badge-count{z-index:auto;min-width:20px;height:20px;padding:0 6px;color:#fff;font-weight:400;font-size:12px;line-height:20px;white-space:nowrap;text-align:center;background:#ff4d4f;border-radius:10px;box-shadow:0 0 0 1px #fff}.ant-badge-count a,.ant-badge-count a:hover{color:#fff}.ant-badge-count-sm{min-width:14px;height:14px;padding:0;font-size:12px;line-height:14px;border-radius:7px}.ant-badge-multiple-words{padding:0 8px}.ant-badge-dot{z-index:auto;width:6px;min-width:6px;height:6px;background:#ff4d4f;border-radius:100%;box-shadow:0 0 0 1px #fff}.ant-badge-dot.ant-scroll-number{transition:background 1.5s}.ant-badge-count,.ant-badge-dot,.ant-badge .ant-scroll-number-custom-component{position:absolute;top:0;right:0;transform:translate(50%,-50%);transform-origin:100% 0%}.ant-badge-count.anticon-spin,.ant-badge-dot.anticon-spin,.ant-badge .ant-scroll-number-custom-component.anticon-spin{animation:antBadgeLoadingCircle 1s infinite linear}.ant-badge-status{line-height:inherit;vertical-align:baseline}.ant-badge-status-dot{position:relative;top:-1px;display:inline-block;width:6px;height:6px;vertical-align:middle;border-radius:50%}.ant-badge-status-success{background-color:#52c41a}.ant-badge-status-processing{position:relative;background-color:#1890ff}.ant-badge-status-processing:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:50%;animation:antStatusProcessing 1.2s infinite ease-in-out;content:""}.ant-badge-status-default{background-color:#d9d9d9}.ant-badge-status-error{background-color:#ff4d4f}.ant-badge-status-warning{background-color:#faad14}.ant-badge-status-pink,.ant-badge-status-magenta{background:#eb2f96}.ant-badge-status-red{background:#f5222d}.ant-badge-status-volcano{background:#fa541c}.ant-badge-status-orange{background:#fa8c16}.ant-badge-status-yellow{background:#fadb14}.ant-badge-status-gold{background:#faad14}.ant-badge-status-cyan{background:#13c2c2}.ant-badge-status-lime{background:#a0d911}.ant-badge-status-green{background:#52c41a}.ant-badge-status-blue{background:#1890ff}.ant-badge-status-geekblue{background:#2f54eb}.ant-badge-status-purple{background:#722ed1}.ant-badge-status-text{margin-left:8px;color:#000000d9;font-size:14px}.ant-badge-zoom-appear,.ant-badge-zoom-enter{animation:antZoomBadgeIn .3s cubic-bezier(.12,.4,.29,1.46);animation-fill-mode:both}.ant-badge-zoom-leave{animation:antZoomBadgeOut .3s cubic-bezier(.71,-.46,.88,.6);animation-fill-mode:both}.ant-badge-not-a-wrapper .ant-badge-zoom-appear,.ant-badge-not-a-wrapper .ant-badge-zoom-enter{animation:antNoWrapperZoomBadgeIn .3s cubic-bezier(.12,.4,.29,1.46)}.ant-badge-not-a-wrapper .ant-badge-zoom-leave{animation:antNoWrapperZoomBadgeOut .3s cubic-bezier(.71,-.46,.88,.6)}.ant-badge-not-a-wrapper:not(.ant-badge-status){vertical-align:middle}.ant-badge-not-a-wrapper .ant-scroll-number-custom-component,.ant-badge-not-a-wrapper .ant-badge-count{transform:none}.ant-badge-not-a-wrapper .ant-scroll-number-custom-component,.ant-badge-not-a-wrapper .ant-scroll-number{position:relative;top:auto;display:block;transform-origin:50% 50%}@keyframes antStatusProcessing{0%{transform:scale(.8);opacity:.5}to{transform:scale(2.4);opacity:0}}.ant-scroll-number{overflow:hidden;direction:ltr}.ant-scroll-number-only{position:relative;display:inline-block;height:20px;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-transform-style:preserve-3d;-webkit-backface-visibility:hidden}.ant-scroll-number-only>p.ant-scroll-number-only-unit{height:20px;margin:0;-webkit-transform-style:preserve-3d;-webkit-backface-visibility:hidden}.ant-scroll-number-symbol{vertical-align:top}@keyframes antZoomBadgeIn{0%{transform:scale(0) translate(50%,-50%);opacity:0}to{transform:scale(1) translate(50%,-50%)}}@keyframes antZoomBadgeOut{0%{transform:scale(1) translate(50%,-50%)}to{transform:scale(0) translate(50%,-50%);opacity:0}}@keyframes antNoWrapperZoomBadgeIn{0%{transform:scale(0);opacity:0}to{transform:scale(1)}}@keyframes antNoWrapperZoomBadgeOut{0%{transform:scale(1)}to{transform:scale(0);opacity:0}}@keyframes antBadgeLoadingCircle{0%{transform-origin:50%}to{transform:translate(50%,-50%) rotate(360deg);transform-origin:50%}}.ant-ribbon-wrapper{position:relative}.ant-ribbon{box-sizing:border-box;margin:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:8px;height:22px;padding:0 8px;color:#fff;line-height:22px;white-space:nowrap;background-color:#d03f0a;border-radius:2px}.ant-ribbon-text{color:#fff}.ant-ribbon-corner{position:absolute;top:100%;width:8px;height:8px;color:currentcolor;border:4px solid;transform:scaleY(.75);transform-origin:top}.ant-ribbon-corner:after{position:absolute;top:-4px;left:-4px;width:inherit;height:inherit;color:#00000040;border:inherit;content:""}.ant-ribbon-color-pink,.ant-ribbon-color-magenta{color:#eb2f96;background:#eb2f96}.ant-ribbon-color-red{color:#f5222d;background:#f5222d}.ant-ribbon-color-volcano{color:#fa541c;background:#fa541c}.ant-ribbon-color-orange{color:#fa8c16;background:#fa8c16}.ant-ribbon-color-yellow{color:#fadb14;background:#fadb14}.ant-ribbon-color-gold{color:#faad14;background:#faad14}.ant-ribbon-color-cyan{color:#13c2c2;background:#13c2c2}.ant-ribbon-color-lime{color:#a0d911;background:#a0d911}.ant-ribbon-color-green{color:#52c41a;background:#52c41a}.ant-ribbon-color-blue{color:#1890ff;background:#1890ff}.ant-ribbon-color-geekblue{color:#2f54eb;background:#2f54eb}.ant-ribbon-color-purple{color:#722ed1;background:#722ed1}.ant-ribbon.ant-ribbon-placement-end{right:-8px;border-bottom-right-radius:0}.ant-ribbon.ant-ribbon-placement-end .ant-ribbon-corner{right:0;border-color:currentcolor transparent transparent currentcolor}.ant-ribbon.ant-ribbon-placement-start{left:-8px;border-bottom-left-radius:0}.ant-ribbon.ant-ribbon-placement-start .ant-ribbon-corner{left:0;border-color:currentcolor currentcolor transparent transparent}.ant-badge-rtl{direction:rtl}.ant-badge-rtl .ant-badge:not(.ant-badge-not-a-wrapper) .ant-badge-count,.ant-badge-rtl .ant-badge:not(.ant-badge-not-a-wrapper) .ant-badge-dot,.ant-badge-rtl .ant-badge:not(.ant-badge-not-a-wrapper) .ant-scroll-number-custom-component{right:auto;left:0;direction:ltr;transform:translate(-50%,-50%);transform-origin:0% 0%}.ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-scroll-number-custom-component{right:auto;left:0;transform:translate(-50%,-50%);transform-origin:0% 0%}.ant-badge-rtl .ant-badge-status-text{margin-right:8px;margin-left:0}.ant-ribbon-rtl{direction:rtl}.ant-ribbon-rtl.ant-ribbon-placement-end{right:unset;left:-8px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.ant-ribbon-rtl.ant-ribbon-placement-end .ant-ribbon-corner{right:unset;left:0;border-color:currentcolor currentcolor transparent transparent}.ant-ribbon-rtl.ant-ribbon-placement-end .ant-ribbon-corner:after{border-color:currentcolor currentcolor transparent transparent}.ant-ribbon-rtl.ant-ribbon-placement-start{right:-8px;left:unset;border-bottom-right-radius:0;border-bottom-left-radius:2px}.ant-ribbon-rtl.ant-ribbon-placement-start .ant-ribbon-corner{right:0;left:unset;border-color:currentcolor transparent transparent currentcolor}.ant-ribbon-rtl.ant-ribbon-placement-start .ant-ribbon-corner:after{border-color:currentcolor transparent transparent currentcolor}.access-mode-message[data-v-29c5fa3e]{display:flex;flex-direction:row;align-items:center}.access-mode-message a[data-v-29c5fa3e]{margin-left:16px}.container[data-v-29c5fa3e]{padding:20px;background-color:var(--zp-secondary-background);height:100%;overflow:auto}.header[data-v-29c5fa3e]{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap}.header h1[data-v-29c5fa3e]{font-size:28px;font-weight:700;color:var(--zp-primary);margin:0}.quick-action[data-v-29c5fa3e]{margin-right:16px;font-size:14px;color:var(--zp-secondary);flex-shrink:0;display:flex;align-items:center;gap:4px}.quick-action a[data-v-29c5fa3e]{text-decoration:none;color:var(--zp-secondary)}.quick-action a[data-v-29c5fa3e]:hover{color:var(--zp-primary)}.content[data-v-29c5fa3e]{display:grid;grid-template-columns:repeat(auto-fit,minmax(384px,1fr));grid-gap:20px;margin-top:16px}.feature-item[data-v-29c5fa3e]{background-color:var(--zp-primary-background);border-radius:8px;box-shadow:0 1px 2px #0000001a;padding:20px}.feature-item ul[data-v-29c5fa3e]{list-style:none;padding:4px;max-height:70vh;overflow-y:auto}.feature-item.recent .title[data-v-29c5fa3e]{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px}.feature-item.recent .title h2[data-v-29c5fa3e]{margin:0}.feature-item .item[data-v-29c5fa3e]{margin-bottom:10px;padding:4px 8px;display:flex;align-items:center;position:relative}.feature-item .item.rem[data-v-29c5fa3e]{display:flex;align-items:center;justify-content:space-between}.feature-item .item[data-v-29c5fa3e]:hover{background:var(--zp-secondary-background);border-radius:4px;color:var(--primary-color);cursor:pointer}.feature-item .item .fixed[data-v-29c5fa3e]{background:var(--primary-color);color:#fff;font-size:.8em;padding:2px 4px;border-radius:8px;margin-right:4px}.feature-item .icon[data-v-29c5fa3e]{margin-right:8px}.feature-item h2[data-v-29c5fa3e]{margin-top:0;margin-bottom:20px;font-size:20px;font-weight:700;color:var(--zp-primary)}.text[data-v-29c5fa3e]{flex:1;font-size:16px;word-break:break-all}.ver-info[data-v-29c5fa3e]{display:flex;align-items:center;flex-direction:row;justify-content:center;color:var(--zp-secondary);gap:16px;padding:32px;flex-wrap:wrap;font-size:.9em} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/fullscreen-7f54b926.svg b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/fullscreen-7f54b926.svg new file mode 100644 index 0000000000000000000000000000000000000000..c8120a42ebc166048f6f57812ebd40e882dc5ae0 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/fullscreen-7f54b926.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/functionalCallableComp-685da399.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/functionalCallableComp-685da399.js new file mode 100644 index 0000000000000000000000000000000000000000..db9220369d4b09afbfaae6d87bd2f0a190e57ffa --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/functionalCallableComp-685da399.js @@ -0,0 +1 @@ +import{c as y,A as Ze,d3 as et,d4 as tt,h as m,d as X,r as _,bf as Se,d5 as Bt,al as z,a as p,bd as Tt,d6 as xe,b as Ee,E as re,b2 as Ft,m as ie,_ as he,cq as K,bz as Pt,j as at,u as nt,D as Et,au as $t,an as It,P as F,w as rt,aX as Ot,d7 as it,o as Dt,d8 as ve,i as Z,d9 as $e,bB as ot,cw as _t,da as Te,db as Ht,dc as At,dd as Lt,aL as Rt,cn as Ut,de as jt,df as Gt,S as zt,T as Kt,cV as Re,cz as se,L as qt,cf as Wt,$ as Xt,R as Ce,dg as Yt,dh as Jt,ai as Ue,c6 as Qt,ad as Zt,z as Y,di as ea,aA as ta,y as st,dj as aa,ah as lt,v as na,V as ra,dk as ia}from"./index-93a218ca.js";/* empty css */var oa={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"}}]},name:"up",theme:"outlined"};const sa=oa;function je(r){for(var e=1;eNumber.MAX_SAFE_INTEGER)return String(Fe()?BigInt(r).toString():Number.MAX_SAFE_INTEGER);if(rNumber.MAX_SAFE_INTEGER)return new r(Number.MAX_SAFE_INTEGER);if(n0&&arguments[0]!==void 0?arguments[0]:!0;return t?this.isInvalidate()?"":De(this.number):this.origin}}]),r}(),da=function(){function r(e){if(tt(this,r),m(this,"origin",""),ut(e)){this.empty=!0;return}if(this.origin=String(e),e==="-"||Number.isNaN(e)){this.nan=!0;return}var t=e;if(Oe(t)&&(t=Number(t)),t=typeof t=="string"?t:De(t),_e(t)){var a=fe(t);this.negative=a.negative;var n=a.trimStr.split(".");this.integer=BigInt(n[0]);var i=n[1]||"0";this.decimal=BigInt(i),this.decimalLen=i.length}else this.nan=!0}return et(r,[{key:"getMark",value:function(){return this.negative?"-":""}},{key:"getIntegerStr",value:function(){return this.integer.toString()}},{key:"getDecimalStr",value:function(){return this.decimal.toString().padStart(this.decimalLen,"0")}},{key:"alignDecimal",value:function(t){var a="".concat(this.getMark()).concat(this.getIntegerStr()).concat(this.getDecimalStr().padEnd(t,"0"));return BigInt(a)}},{key:"negate",value:function(){var t=new r(this.toString());return t.negative=!t.negative,t}},{key:"add",value:function(t){if(this.isInvalidate())return new r(t);var a=new r(t);if(a.isInvalidate())return this;var n=Math.max(this.getDecimalStr().length,a.getDecimalStr().length),i=this.alignDecimal(n),s=a.alignDecimal(n),o=(i+s).toString(),l=fe(o),c=l.negativeStr,d=l.trimStr,u="".concat(c).concat(d.padStart(n+1,"0"));return new r("".concat(u.slice(0,-n),".").concat(u.slice(-n)))}},{key:"isEmpty",value:function(){return this.empty}},{key:"isNaN",value:function(){return this.nan}},{key:"isInvalidate",value:function(){return this.isEmpty()||this.isNaN()}},{key:"equals",value:function(t){return this.toString()===(t==null?void 0:t.toString())}},{key:"lessEquals",value:function(t){return this.add(t.negate().toString()).toNumber()<=0}},{key:"toNumber",value:function(){return this.isNaN()?NaN:Number(this.toString())}},{key:"toString",value:function(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;return t?this.isInvalidate()?"":fe("".concat(this.getMark()).concat(this.getIntegerStr(),".").concat(this.getDecimalStr())).fullStr:this.origin}}]),r}();function W(r){return Fe()?new da(r):new ca(r)}function Pe(r,e,t){var a=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!1;if(r==="")return"";var n=fe(r),i=n.negativeStr,s=n.integerStr,o=n.decimalStr,l="".concat(e).concat(o),c="".concat(i).concat(s);if(t>=0){var d=Number(o[t]);if(d>=5&&!a){var u=W(r).add("".concat(i,"0.").concat("0".repeat(t)).concat(10-d));return Pe(u.toString(),e,t,a)}return t===0?c:"".concat(c).concat(e).concat(o.padEnd(t,"0").slice(0,t))}return l===".0"?c:"".concat(c).concat(l)}var va=200,fa=600;const ha=X({compatConfig:{MODE:3},name:"StepHandler",inheritAttrs:!1,props:{prefixCls:String,upDisabled:Boolean,downDisabled:Boolean,onStep:{type:Function}},slots:["upNode","downNode"],setup:function(e,t){var a=t.slots,n=t.emit,i=_(),s=function(c,d){c.preventDefault(),n("step",d);function u(){n("step",d),i.value=setTimeout(u,va)}i.value=setTimeout(u,fa)},o=function(){clearTimeout(i.value)};return Se(function(){o()}),function(){if(Bt())return null;var l=e.prefixCls,c=e.upDisabled,d=e.downDisabled,u="".concat(l,"-handler"),x=z(u,"".concat(u,"-up"),m({},"".concat(u,"-up-disabled"),c)),N=z(u,"".concat(u,"-down"),m({},"".concat(u,"-down-disabled"),d)),w={unselectable:"on",role:"button",onMouseup:o,onMouseleave:o},M=a.upNode,P=a.downNode;return y("div",{class:"".concat(u,"-wrap")},[y("span",p(p({},w),{},{onMousedown:function(b){s(b,!0)},"aria-label":"Increase Value","aria-disabled":c,class:x}),[(M==null?void 0:M())||y("span",{unselectable:"on",class:"".concat(l,"-handler-up-inner")},null)]),y("span",p(p({},w),{},{onMousedown:function(b){s(b,!1)},"aria-label":"Decrease Value","aria-disabled":d,class:N}),[(P==null?void 0:P())||y("span",{unselectable:"on",class:"".concat(l,"-handler-down-inner")},null)])])}}});function ma(r,e){var t=_(null);function a(){try{var i=r.value,s=i.selectionStart,o=i.selectionEnd,l=i.value,c=l.substring(0,s),d=l.substring(o);t.value={start:s,end:o,value:l,beforeTxt:c,afterTxt:d}}catch{}}function n(){if(r.value&&t.value&&e.value)try{var i=r.value.value,s=t.value,o=s.beforeTxt,l=s.afterTxt,c=s.start,d=i.length;if(i.endsWith(l))d=i.length-t.value.afterTxt.length;else if(i.startsWith(o))d=o.length;else{var u=o[c-1],x=i.indexOf(u,c-1);x!==-1&&(d=x+1)}r.value.setSelectionRange(d,d)}catch(N){Tt(!1,"Something warning of cursor restore. Please fire issue about this: ".concat(N.message))}}return[a,n]}const pa=function(){var r=_(0),e=function(){xe.cancel(r.value)};return Se(function(){e()}),function(t){e(),r.value=xe(function(){t()})}};var ga=["prefixCls","min","max","step","defaultValue","value","disabled","readonly","keyboard","controls","autofocus","stringMode","parser","formatter","precision","decimalSeparator","onChange","onInput","onPressEnter","onStep","lazy","class","style"],Ge=function(e,t){return e||t.isEmpty()?t.toString():t.toNumber()},ze=function(e){var t=W(e);return t.isInvalidate()?null:t},ct=function(){return{stringMode:{type:Boolean},defaultValue:{type:[String,Number]},value:{type:[String,Number]},prefixCls:{type:String},min:{type:[String,Number]},max:{type:[String,Number]},step:{type:[String,Number],default:1},tabindex:{type:Number},controls:{type:Boolean,default:!0},readonly:{type:Boolean},disabled:{type:Boolean},autofocus:{type:Boolean},keyboard:{type:Boolean,default:!0},parser:{type:Function},formatter:{type:Function},precision:{type:Number},decimalSeparator:{type:String},onInput:{type:Function},onChange:{type:Function},onPressEnter:{type:Function},onStep:{type:Function},onBlur:{type:Function},onFocus:{type:Function}}};const ba=X({compatConfig:{MODE:3},name:"InnerInputNumber",inheritAttrs:!1,props:p(p({},ct()),{},{lazy:Boolean}),slots:["upHandler","downHandler"],setup:function(e,t){var a=t.attrs,n=t.slots,i=t.emit,s=t.expose,o=_(),l=_(!1),c=_(!1),d=_(!1),u=_(W(e.value));function x(k){e.value===void 0&&(u.value=k)}var N=function(v,T){if(!T)return e.precision>=0?e.precision:Math.max(me(v),me(e.step))},w=function(v){var T=String(v);if(e.parser)return e.parser(T);var h=T;return e.decimalSeparator&&(h=h.replace(e.decimalSeparator,".")),h.replace(/[^\w.-]+/g,"")},M=_(""),P=function(v,T){if(e.formatter)return e.formatter(v,{userTyping:T,input:String(M.value)});var h=typeof v=="number"?De(v):v;if(!T){var j=N(h,T);if(_e(h)&&(e.decimalSeparator||j>=0)){var A=e.decimalSeparator||".";h=Pe(h,A,j)}}return h},V=function(){var k=e.value;return u.value.isInvalidate()&&["string","number"].includes(Ee(k))?Number.isNaN(k)?"":k:P(u.value.toString(),!1)}();M.value=V;function b(k,v){M.value=P(k.isInvalidate()?k.toString(!1):k.toString(!v),v)}var S=re(function(){return ze(e.max)}),C=re(function(){return ze(e.min)}),g=re(function(){return!S.value||!u.value||u.value.isInvalidate()?!1:S.value.lessEquals(u.value)}),I=re(function(){return!C.value||!u.value||u.value.isInvalidate()?!1:u.value.lessEquals(C.value)}),B=ma(o,l),f=Ft(B,2),E=f[0],D=f[1],$=function(v){return S.value&&!v.lessEquals(S.value)?S.value:C.value&&!C.value.lessEquals(v)?C.value:null},L=function(v){return!$(v)},H=function(v,T){var h=v,j=L(h)||h.isEmpty();if(!h.isEmpty()&&!T&&(h=$(h)||h,j=!0),!e.readonly&&!e.disabled&&j){var A=h.toString(),q=N(A,T);if(q>=0&&(h=W(Pe(A,".",q))),!h.equals(u.value)){var Q;x(h),(Q=e.onChange)===null||Q===void 0||Q.call(e,h.isEmpty()?null:Ge(e.stringMode,h)),e.value===void 0&&b(h,T)}return h}return u.value},G=pa(),R=function k(v){var T;if(E(),M.value=v,!d.value){var h=w(v),j=W(h);j.isNaN()||H(j,!0)}(T=e.onInput)===null||T===void 0||T.call(e,v),G(function(){var A=v;e.parser||(A=v.replace(/。/g,".")),A!==v&&k(A)})},U=function(){d.value=!0},J=function(){d.value=!1,R(o.value.value)},ee=function(v){R(v.target.value)},te=function(v){var T,h;if(!(v&&g.value||!v&&I.value)){c.value=!1;var j=W(e.step);v||(j=j.negate());var A=(u.value||W(0)).add(j.toString()),q=H(A,!1);(T=e.onStep)===null||T===void 0||T.call(e,Ge(e.stringMode,q),{offset:e.step,type:v?"up":"down"}),(h=o.value)===null||h===void 0||h.focus()}},le=function(v){var T=W(w(M.value)),h=T;T.isNaN()?h=u.value:h=H(T,v),e.value!==void 0?b(u.value,!1):h.isNaN()||b(h,!1)},ue=function(v){var T=v.which;if(c.value=!0,T===K.ENTER){var h;d.value||(c.value=!1),le(!1),(h=e.onPressEnter)===null||h===void 0||h.call(e,v)}e.keyboard!==!1&&!d.value&&[K.UP,K.DOWN].includes(T)&&(te(K.UP===T),v.preventDefault())},O=function(){c.value=!1},ne=function(v){le(!1),l.value=!1,c.value=!1,i("blur",v)};return ie(function(){return e.precision},function(){u.value.isInvalidate()||b(u.value,!1)},{flush:"post"}),ie(function(){return e.value},function(){var k=W(e.value);u.value=k;var v=W(w(M.value));(!k.equals(v)||!c.value||e.formatter)&&b(k,c.value)},{flush:"post"}),ie(M,function(){e.formatter&&D()},{flush:"post"}),ie(function(){return e.disabled},function(k){k&&(l.value=!1)}),s({focus:function(){var v;(v=o.value)===null||v===void 0||v.focus()},blur:function(){var v;(v=o.value)===null||v===void 0||v.blur()}}),function(){var k,v=p(p({},a),e),T=v.prefixCls,h=T===void 0?"rc-input-number":T,j=v.min,A=v.max,q=v.step,Q=q===void 0?1:q;v.defaultValue,v.value;var pe=v.disabled,ge=v.readonly;v.keyboard;var be=v.controls,ke=be===void 0?!0:be,ye=v.autofocus;v.stringMode,v.parser,v.formatter,v.precision,v.decimalSeparator,v.onChange,v.onInput,v.onPressEnter,v.onStep;var St=v.lazy,Ct=v.class,kt=v.style,Nt=he(v,ga),wt=n.upHandler,Vt=n.downHandler,Le="".concat(h,"-input"),Ne={};return St?Ne.onChange=ee:Ne.onInput=ee,y("div",{class:z(h,Ct,(k={},m(k,"".concat(h,"-focused"),l.value),m(k,"".concat(h,"-disabled"),pe),m(k,"".concat(h,"-readonly"),ge),m(k,"".concat(h,"-not-a-number"),u.value.isNaN()),m(k,"".concat(h,"-out-of-range"),!u.value.isInvalidate()&&!L(u.value)),k)),style:kt,onKeydown:ue,onKeyup:O},[ke&&y(ha,{prefixCls:h,upDisabled:g.value,downDisabled:I.value,onStep:te},{upNode:wt,downNode:Vt}),y("div",{class:"".concat(Le,"-wrap")},[y("input",p(p(p({autofocus:ye,autocomplete:"off",role:"spinbutton","aria-valuemin":j,"aria-valuemax":A,"aria-valuenow":u.value.isInvalidate()?null:u.value.toString(),step:Q},Nt),{},{ref:o,class:Le,value:M.value,disabled:pe,readonly:ge,onFocus:function(Mt){l.value=!0,i("focus",Mt)}},Ne),{},{onBlur:ne,onCompositionstart:U,onCompositionend:J}),null)])])}}});function we(r){return r!=null}var ya=["class","bordered","readonly","style","addonBefore","addonAfter","prefix","valueModifiers"],Ke=ct(),xa=function(){return p(p({},Ke),{},{size:{type:String},bordered:{type:Boolean,default:!0},placeholder:String,name:String,id:String,type:String,addonBefore:F.any,addonAfter:F.any,prefix:F.any,"onUpdate:value":Ke.onChange,valueModifiers:Object})},Ve=X({compatConfig:{MODE:3},name:"AInputNumber",inheritAttrs:!1,props:xa(),slots:["addonBefore","addonAfter","prefix"],setup:function(e,t){var a=t.emit,n=t.expose,i=t.attrs,s=t.slots,o=at(),l=nt("input-number",e),c=l.prefixCls,d=l.size,u=l.direction,x=_(e.value===void 0?e.defaultValue:e.value),N=_(!1);ie(function(){return e.value},function(){x.value=e.value});var w=_(null),M=function(){var g;(g=w.value)===null||g===void 0||g.focus()},P=function(){var g;(g=w.value)===null||g===void 0||g.blur()};n({focus:M,blur:P});var V=function(g){e.value===void 0&&(x.value=g),a("update:value",g),a("change",g),o.onFieldChange()},b=function(g){N.value=!1,a("blur",g),o.onFieldBlur()},S=function(g){N.value=!0,a("focus",g)};return function(){var C,g,I,B,f=p(p({},i),e),E=f.class,D=f.bordered,$=f.readonly,L=f.style,H=f.addonBefore,G=H===void 0?(C=s.addonBefore)===null||C===void 0?void 0:C.call(s):H,R=f.addonAfter,U=R===void 0?(g=s.addonAfter)===null||g===void 0?void 0:g.call(s):R,J=f.prefix,ee=J===void 0?(I=s.prefix)===null||I===void 0?void 0:I.call(s):J,te=f.valueModifiers,le=te===void 0?{}:te,ue=he(f,ya),O=c.value,ne=d.value,k=z((B={},m(B,"".concat(O,"-lg"),ne==="large"),m(B,"".concat(O,"-sm"),ne==="small"),m(B,"".concat(O,"-rtl"),u.value==="rtl"),m(B,"".concat(O,"-readonly"),$),m(B,"".concat(O,"-borderless"),!D),B),E),v=y(ba,p(p({},$t(ue,["size","defaultValue"])),{},{ref:w,lazy:!!le.lazy,value:x.value,class:k,prefixCls:O,readonly:$,onChange:V,onBlur:b,onFocus:S}),{upHandler:function(){return y(ua,{class:"".concat(O,"-handler-up-inner")},null)},downHandler:function(){return y(Et,{class:"".concat(O,"-handler-down-inner")},null)}}),T=we(G)||we(U);if(we(ee)){var h,j=z("".concat(O,"-affix-wrapper"),(h={},m(h,"".concat(O,"-affix-wrapper-focused"),N.value),m(h,"".concat(O,"-affix-wrapper-disabled"),e.disabled),m(h,"".concat(O,"-affix-wrapper-rtl"),u.value==="rtl"),m(h,"".concat(O,"-affix-wrapper-readonly"),$),m(h,"".concat(O,"-affix-wrapper-borderless"),!D),m(h,"".concat(E),!T&&E),h));v=y("div",{class:j,style:L,onMouseup:function(){return w.value.focus()}},[y("span",{class:"".concat(O,"-prefix")},[ee]),v])}if(T){var A,q="".concat(O,"-group"),Q="".concat(q,"-addon"),pe=G?y("div",{class:Q},[G]):null,ge=U?y("div",{class:Q},[U]):null,be=z("".concat(O,"-wrapper"),q,m({},"".concat(q,"-rtl"),u.value==="rtl")),ke=z("".concat(O,"-group-wrapper"),(A={},m(A,"".concat(O,"-group-wrapper-sm"),ne==="small"),m(A,"".concat(O,"-group-wrapper-lg"),ne==="large"),m(A,"".concat(O,"-group-wrapper-rtl"),u.value==="rtl"),A),E);v=y("div",{class:ke,style:L},[y("div",{class:be},[pe,v,ge])])}return It(v,{style:L})}}});const Sa=Pt(Ve,{install:function(e){return e.component(Ve.name,Ve),e}});var dt=function(e,t){var a,n,i=t.attrs,s=i.included,o=i.vertical,l=i.style,c=i.class,d=i.length,u=i.offset,x=i.reverse;d<0&&(x=!x,d=Math.abs(d),u=100-u);var N=o?(a={},m(a,x?"top":"bottom","".concat(u,"%")),m(a,x?"bottom":"top","auto"),m(a,"height","".concat(d,"%")),a):(n={},m(n,x?"right":"left","".concat(u,"%")),m(n,x?"left":"right","auto"),m(n,"width","".concat(d,"%")),n),w=p(p({},l),N);return s?y("div",{class:c,style:w},null):null};dt.inheritAttrs=!1;const vt=dt;var Ca=function(e,t,a,n,i,s){rt(a?n>0:!0,"Slider","`Slider[step]` should be a positive number in order to make Slider[dots] work.");var o=Object.keys(t).map(parseFloat).sort(function(c,d){return c-d});if(a&&n)for(var l=i;l<=s;l+=n)o.indexOf(l)===-1&&o.push(l);return o},ft=function(e,t){var a=t.attrs,n=a.prefixCls,i=a.vertical,s=a.reverse,o=a.marks,l=a.dots,c=a.step,d=a.included,u=a.lowerBound,x=a.upperBound,N=a.max,w=a.min,M=a.dotStyle,P=a.activeDotStyle,V=N-w,b=Ca(i,o,l,c,w,N).map(function(S){var C,g="".concat(Math.abs(S-w)/V*100,"%"),I=!d&&S===x||d&&S<=x&&S>=u,B=i?p(p({},M),{},m({},s?"top":"bottom",g)):p(p({},M),{},m({},s?"right":"left",g));I&&(B=p(p({},B),P));var f=z((C={},m(C,"".concat(n,"-dot"),!0),m(C,"".concat(n,"-dot-active"),I),m(C,"".concat(n,"-dot-reverse"),s),C));return y("span",{class:f,style:B,key:S},null)});return y("div",{class:"".concat(n,"-step")},[b])};ft.inheritAttrs=!1;const ka=ft;var ht=function(e,t){var a=t.attrs,n=t.slots,i=a.class,s=a.vertical,o=a.reverse,l=a.marks,c=a.included,d=a.upperBound,u=a.lowerBound,x=a.max,N=a.min,w=a.onClickLabel,M=Object.keys(l),P=n.mark,V=x-N,b=M.map(parseFloat).sort(function(S,C){return S-C}).map(function(S){var C,g=typeof l[S]=="function"?l[S]():l[S],I=Ee(g)==="object"&&!Ot(g),B=I?g.label:g;if(!B&&B!==0)return null;P&&(B=P({point:S,label:B}));var f=!c&&S===d||c&&S<=d&&S>=u,E=z((C={},m(C,"".concat(i,"-text"),!0),m(C,"".concat(i,"-text-active"),f),C)),D=m({marginBottom:"-50%"},o?"top":"bottom","".concat((S-N)/V*100,"%")),$=m({transform:"translateX(".concat(o?"50%":"-50%",")"),msTransform:"translateX(".concat(o?"50%":"-50%",")")},o?"right":"left","".concat((S-N)/V*100,"%")),L=s?D:$,H=I?p(p({},L),g.style):L,G=m({},it?"onTouchstartPassive":"onTouchstart",function(R){return w(R,S)});return y("span",p({class:E,style:H,key:S,onMousedown:function(U){return w(U,S)}},G),[B])});return y("div",{class:i},[b])};ht.inheritAttrs=!1;const Na=ht,mt=X({compatConfig:{MODE:3},name:"Handle",inheritAttrs:!1,props:{prefixCls:String,vertical:{type:Boolean,default:void 0},offset:Number,disabled:{type:Boolean,default:void 0},min:Number,max:Number,value:Number,tabindex:F.oneOfType([F.number,F.string]),reverse:{type:Boolean,default:void 0},ariaLabel:String,ariaLabelledBy:String,ariaValueTextFormatter:Function,onMouseenter:{type:Function},onMouseleave:{type:Function},onMousedown:{type:Function}},setup:function(e,t){var a=t.attrs,n=t.emit,i=t.expose,s=_(!1),o=_(),l=function(){document.activeElement===o.value&&(s.value=!0)},c=function(b){s.value=!1,n("blur",b)},d=function(){s.value=!1},u=function(){var b;(b=o.value)===null||b===void 0||b.focus()},x=function(){var b;(b=o.value)===null||b===void 0||b.blur()},N=function(){s.value=!0,u()},w=function(b){b.preventDefault(),u(),n("mousedown",b)};i({focus:u,blur:x,clickFocus:N,ref:o});var M=null;Dt(function(){M=ve(document,"mouseup",l)}),Se(function(){var V;(V=M)===null||V===void 0||V.remove()});var P=re(function(){var V,b,S=e.vertical,C=e.offset,g=e.reverse;return S?(V={},m(V,g?"top":"bottom","".concat(C,"%")),m(V,g?"bottom":"top","auto"),m(V,"transform",g?null:"translateY(+50%)"),V):(b={},m(b,g?"right":"left","".concat(C,"%")),m(b,g?"left":"right","auto"),m(b,"transform","translateX(".concat(g?"+":"-","50%)")),b)});return function(){var V=e.prefixCls,b=e.disabled,S=e.min,C=e.max,g=e.value,I=e.tabindex,B=e.ariaLabel,f=e.ariaLabelledBy,E=e.ariaValueTextFormatter,D=e.onMouseenter,$=e.onMouseleave,L=z(a.class,m({},"".concat(V,"-handle-click-focused"),s.value)),H={"aria-valuemin":S,"aria-valuemax":C,"aria-valuenow":g,"aria-disabled":!!b},G=[a.style,P.value],R=I||0;(b||I===null)&&(R=null);var U;E&&(U=E(g));var J=p(p(p({},a),{},{role:"slider",tabindex:R},H),{},{class:L,onBlur:c,onKeydown:d,onMousedown:w,onMouseenter:D,onMouseleave:$,ref:o,style:G});return y("div",p(p({},J),{},{"aria-label":B,"aria-labelledby":f,"aria-valuetext":U}),null)}}});function Me(r,e){try{return Object.keys(e).some(function(t){return r.target===e[t].ref})}catch{return!1}}function pt(r,e){var t=e.min,a=e.max;return ra}function qe(r){return r.touches.length>1||r.type.toLowerCase()==="touchend"&&r.touches.length>0}function We(r,e){var t=e.marks,a=e.step,n=e.min,i=e.max,s=Object.keys(t).map(parseFloat);if(a!==null){var o=Math.pow(10,gt(a)),l=Math.floor((i*o-n*o)/(a*o)),c=Math.min((r-n)/a,l),d=Math.round(c)*a+n;s.push(d)}var u=s.map(function(x){return Math.abs(r-x)});return s[u.indexOf(Math.min.apply(Math,Z(u)))]}function gt(r){var e=r.toString(),t=0;return e.indexOf(".")>=0&&(t=e.length-e.indexOf(".")-1),t}function Xe(r,e){var t=1;return window.visualViewport&&(t=+(window.visualViewport.width/document.body.getBoundingClientRect().width).toFixed(2)),(r?e.clientY:e.pageX)/t}function Ye(r,e){var t=1;return window.visualViewport&&(t=+(window.visualViewport.width/document.body.getBoundingClientRect().width).toFixed(2)),(r?e.touches[0].clientY:e.touches[0].pageX)/t}function Je(r,e){var t=e.getBoundingClientRect();return r?t.top+t.height*.5:window.pageXOffset+t.left+t.width*.5}function He(r,e){var t=e.max,a=e.min;return r<=a?a:r>=t?t:r}function bt(r,e){var t=e.step,a=isFinite(We(r,e))?We(r,e):0;return t===null?a:parseFloat(a.toFixed(gt(t)))}function oe(r){r.stopPropagation(),r.preventDefault()}function wa(r,e,t){var a={increase:function(o,l){return o+l},decrease:function(o,l){return o-l}},n=a[r](Object.keys(t.marks).indexOf(JSON.stringify(e)),1),i=Object.keys(t.marks)[n];return t.step?a[r](e,t.step):Object.keys(t.marks).length&&t.marks[i]?t.marks[i]:e}function yt(r,e,t){var a="increase",n="decrease",i=a;switch(r.keyCode){case K.UP:i=e&&t?n:a;break;case K.RIGHT:i=!e&&t?n:a;break;case K.DOWN:i=e&&t?a:n;break;case K.LEFT:i=!e&&t?a:n;break;case K.END:return function(s,o){return o.max};case K.HOME:return function(s,o){return o.min};case K.PAGE_UP:return function(s,o){return s+o.step*2};case K.PAGE_DOWN:return function(s,o){return s-o.step*2};default:return}return function(s,o){return wa(i,s,o)}}var Va=["index","directives","className","style"];function ae(){}function xt(r){var e={id:String,min:Number,max:Number,step:Number,marks:F.object,included:{type:Boolean,default:void 0},prefixCls:String,disabled:{type:Boolean,default:void 0},handle:Function,dots:{type:Boolean,default:void 0},vertical:{type:Boolean,default:void 0},reverse:{type:Boolean,default:void 0},minimumTrackStyle:F.object,maximumTrackStyle:F.object,handleStyle:F.oneOfType([F.object,F.arrayOf(F.object)]),trackStyle:F.oneOfType([F.object,F.arrayOf(F.object)]),railStyle:F.object,dotStyle:F.object,activeDotStyle:F.object,autofocus:{type:Boolean,default:void 0},draggableTrack:{type:Boolean,default:void 0}};return X({compatConfig:{MODE:3},name:"CreateSlider",mixins:[$e,r],inheritAttrs:!1,slots:["mark"],props:ot(e,{prefixCls:"rc-slider",min:0,max:100,step:1,marks:{},included:!0,disabled:!1,dots:!1,vertical:!1,reverse:!1,trackStyle:[{}],handleStyle:[{}],railStyle:{},dotStyle:{},activeDotStyle:{}}),emits:["change","blur","focus"],data:function(){var a=this.step,n=this.max,i=this.min,s=isFinite(n-i)?(n-i)%a===0:!0;return rt(a&&Math.floor(a)===a?s:!0,"Slider[max] - Slider[min] (".concat(n-i,") should be a multiple of Slider[step] (").concat(a,")")),this.handlesRefs={},{}},mounted:function(){var a=this;this.$nextTick(function(){a.document=a.sliderRef&&a.sliderRef.ownerDocument;var n=a.autofocus,i=a.disabled;n&&!i&&a.focus()})},beforeUnmount:function(){var a=this;this.$nextTick(function(){a.removeDocumentEvents()})},methods:{defaultHandle:function(a){var n=a.index;a.directives;var i=a.className,s=a.style,o=he(a,Va);if(delete o.dragging,o.value===null)return null;var l=p(p({},o),{},{class:i,style:s,key:n});return y(mt,l,null)},onDown:function(a,n){var i=n,s=this.$props,o=s.draggableTrack,l=s.vertical,c=this.$data.bounds,d=o&&this.positionGetValue?this.positionGetValue(i)||[]:[],u=Me(a,this.handlesRefs);if(this.dragTrack=o&&c.length>=2&&!u&&!d.map(function(N,w){var M=w?!0:N>=c[w];return w===d.length-1?N<=c[w]:M}).some(function(N){return!N}),this.dragTrack)this.dragOffset=i,this.startBounds=Z(c);else{if(!u)this.dragOffset=0;else{var x=Je(l,a.target);this.dragOffset=i-x,i=x}this.onStart(i)}},onMouseDown:function(a){if(a.button===0){this.removeDocumentEvents();var n=this.$props.vertical,i=Xe(n,a);this.onDown(a,i),this.addDocumentMouseEvents()}},onTouchStart:function(a){if(!qe(a)){var n=this.vertical,i=Ye(n,a);this.onDown(a,i),this.addDocumentTouchEvents(),oe(a)}},onFocus:function(a){var n=this.vertical;if(Me(a,this.handlesRefs)&&!this.dragTrack){var i=Je(n,a.target);this.dragOffset=0,this.onStart(i),oe(a),this.$emit("focus",a)}},onBlur:function(a){this.dragTrack||this.onEnd(),this.$emit("blur",a)},onMouseUp:function(){this.handlesRefs[this.prevMovedHandleIndex]&&this.handlesRefs[this.prevMovedHandleIndex].clickFocus()},onMouseMove:function(a){if(!this.sliderRef){this.onEnd();return}var n=Xe(this.vertical,a);this.onMove(a,n-this.dragOffset,this.dragTrack,this.startBounds)},onTouchMove:function(a){if(qe(a)||!this.sliderRef){this.onEnd();return}var n=Ye(this.vertical,a);this.onMove(a,n-this.dragOffset,this.dragTrack,this.startBounds)},onKeyDown:function(a){this.sliderRef&&Me(a,this.handlesRefs)&&this.onKeyboard(a)},onClickMarkLabel:function(a,n){var i=this;a.stopPropagation(),this.onChange({sValue:n}),this.setState({sValue:n},function(){return i.onEnd(!0)})},getSliderStart:function(){var a=this.sliderRef,n=this.vertical,i=this.reverse,s=a.getBoundingClientRect();return n?i?s.bottom:s.top:window.pageXOffset+(i?s.right:s.left)},getSliderLength:function(){var a=this.sliderRef;if(!a)return 0;var n=a.getBoundingClientRect();return this.vertical?n.height:n.width},addDocumentTouchEvents:function(){this.onTouchMoveListener=ve(this.document,"touchmove",this.onTouchMove),this.onTouchUpListener=ve(this.document,"touchend",this.onEnd)},addDocumentMouseEvents:function(){this.onMouseMoveListener=ve(this.document,"mousemove",this.onMouseMove),this.onMouseUpListener=ve(this.document,"mouseup",this.onEnd)},removeDocumentEvents:function(){this.onTouchMoveListener&&this.onTouchMoveListener.remove(),this.onTouchUpListener&&this.onTouchUpListener.remove(),this.onMouseMoveListener&&this.onMouseMoveListener.remove(),this.onMouseUpListener&&this.onMouseUpListener.remove()},focus:function(){var a;this.$props.disabled||(a=this.handlesRefs[0])===null||a===void 0||a.focus()},blur:function(){var a=this;this.$props.disabled||Object.keys(this.handlesRefs).forEach(function(n){var i,s;(i=a.handlesRefs[n])===null||i===void 0||(s=i.blur)===null||s===void 0||s.call(i)})},calcValue:function(a){var n=this.vertical,i=this.min,s=this.max,o=Math.abs(Math.max(a,0)/this.getSliderLength()),l=n?(1-o)*(s-i)+i:o*(s-i)+i;return l},calcValueByPos:function(a){var n=this.reverse?-1:1,i=n*(a-this.getSliderStart()),s=this.trimAlignValue(this.calcValue(i));return s},calcOffset:function(a){var n=this.min,i=this.max,s=(a-n)/(i-n);return Math.max(0,s*100)},saveSlider:function(a){this.sliderRef=a},saveHandle:function(a,n){this.handlesRefs[a]=n}},render:function(){var a,n=this.prefixCls,i=this.marks,s=this.dots,o=this.step,l=this.included,c=this.disabled,d=this.vertical,u=this.reverse,x=this.min,N=this.max,w=this.maximumTrackStyle,M=this.railStyle,P=this.dotStyle,V=this.activeDotStyle,b=this.id,S=this.$attrs,C=S.class,g=S.style,I=this.renderSlider(),B=I.tracks,f=I.handles,E=z(n,C,(a={},m(a,"".concat(n,"-with-marks"),Object.keys(i).length),m(a,"".concat(n,"-disabled"),c),m(a,"".concat(n,"-vertical"),d),a)),D={vertical:d,marks:i,included:l,lowerBound:this.getLowerBound(),upperBound:this.getUpperBound(),max:N,min:x,reverse:u,class:"".concat(n,"-mark"),onClickLabel:c?ae:this.onClickMarkLabel},$=m({},it?"onTouchstartPassive":"onTouchstart",c?ae:this.onTouchStart);return y("div",p(p({id:b,ref:this.saveSlider,tabindex:"-1",class:E},$),{},{onMousedown:c?ae:this.onMouseDown,onMouseup:c?ae:this.onMouseUp,onKeydown:c?ae:this.onKeyDown,onFocus:c?ae:this.onFocus,onBlur:c?ae:this.onBlur,style:g}),[y("div",{class:"".concat(n,"-rail"),style:p(p({},w),M)},null),B,y(ka,{prefixCls:n,vertical:d,reverse:u,marks:i,dots:s,step:o,included:l,lowerBound:this.getLowerBound(),upperBound:this.getUpperBound(),max:N,min:x,dotStyle:P,activeDotStyle:V},null),f,y(Na,D,{mark:this.$slots.mark}),_t(this)])}})}var Ma=X({compatConfig:{MODE:3},name:"Slider",mixins:[$e],inheritAttrs:!1,props:{defaultValue:Number,value:Number,disabled:{type:Boolean,default:void 0},autofocus:{type:Boolean,default:void 0},tabindex:F.oneOfType([F.number,F.string]),reverse:{type:Boolean,default:void 0},min:Number,max:Number,ariaLabelForHandle:String,ariaLabelledByForHandle:String,ariaValueTextFormatterForHandle:String,startPoint:Number},emits:["beforeChange","afterChange","change"],data:function(){var e=this.defaultValue!==void 0?this.defaultValue:this.min,t=this.value!==void 0?this.value:e;return{sValue:this.trimAlignValue(t),dragging:!1}},watch:{value:{handler:function(e){this.setChangeValue(e)},deep:!0},min:function(){var e=this.sValue;this.setChangeValue(e)},max:function(){var e=this.sValue;this.setChangeValue(e)}},methods:{setChangeValue:function(e){var t=e!==void 0?e:this.sValue,a=this.trimAlignValue(t,this.$props);a!==this.sValue&&(this.setState({sValue:a}),pt(t,this.$props)&&this.$emit("change",a))},onChange:function(e){var t=!Te(this,"value"),a=e.sValue>this.max?p(p({},e),{},{sValue:this.max}):e;t&&this.setState(a);var n=a.sValue;this.$emit("change",n)},onStart:function(e){this.setState({dragging:!0});var t=this.sValue;this.$emit("beforeChange",t);var a=this.calcValueByPos(e);this.startValue=a,this.startPosition=e,a!==t&&(this.prevMovedHandleIndex=0,this.onChange({sValue:a}))},onEnd:function(e){var t=this.dragging;this.removeDocumentEvents(),(t||e)&&this.$emit("afterChange",this.sValue),this.setState({dragging:!1})},onMove:function(e,t){oe(e);var a=this.sValue,n=this.calcValueByPos(t);n!==a&&this.onChange({sValue:n})},onKeyboard:function(e){var t=this.$props,a=t.reverse,n=t.vertical,i=yt(e,n,a);if(i){oe(e);var s=this.sValue,o=i(s,this.$props),l=this.trimAlignValue(o);if(l===s)return;this.onChange({sValue:l}),this.$emit("afterChange",l),this.onEnd()}},getLowerBound:function(){var e=this.$props.startPoint||this.$props.min;return this.$data.sValue>e?e:this.$data.sValue},getUpperBound:function(){return this.$data.sValue1&&arguments[1]!==void 0?arguments[1]:{};if(e===null)return null;var a=p(p({},this.$props),t),n=He(e,a);return bt(n,a)},getTrack:function(e){var t=e.prefixCls,a=e.reverse,n=e.vertical,i=e.included,s=e.minimumTrackStyle,o=e.mergedTrackStyle,l=e.length,c=e.offset;return y(vt,{class:"".concat(t,"-track"),vertical:n,included:i,offset:c,reverse:a,length:l,style:p(p({},s),o)},null)},renderSlider:function(){var e=this,t=this.prefixCls,a=this.vertical,n=this.included,i=this.disabled,s=this.minimumTrackStyle,o=this.trackStyle,l=this.handleStyle,c=this.tabindex,d=this.ariaLabelForHandle,u=this.ariaLabelledByForHandle,x=this.ariaValueTextFormatterForHandle,N=this.min,w=this.max,M=this.startPoint,P=this.reverse,V=this.handle,b=this.defaultHandle,S=V||b,C=this.sValue,g=this.dragging,I=this.calcOffset(C),B=S({class:"".concat(t,"-handle"),prefixCls:t,vertical:a,offset:I,value:C,dragging:g,disabled:i,min:N,max:w,reverse:P,index:0,tabindex:c,ariaLabel:d,ariaLabelledBy:u,ariaValueTextFormatter:x,style:l[0]||l,ref:function($){return e.saveHandle(0,$)},onFocus:this.onFocus,onBlur:this.onBlur}),f=M!==void 0?this.calcOffset(M):0,E=o[0]||o;return{tracks:this.getTrack({prefixCls:t,reverse:P,vertical:a,included:n,offset:f,minimumTrackStyle:s,mergedTrackStyle:E,length:I-f}),handles:B}}}});const Ba=xt(Ma);var ce=function(e){var t=e.value,a=e.handle,n=e.bounds,i=e.props,s=i.allowCross,o=i.pushable,l=Number(o),c=He(t,i),d=c;return!s&&a!=null&&n!==void 0&&(a>0&&c<=n[a-1]+l&&(d=n[a-1]+l),a=n[a+1]-l&&(d=n[a+1]-l)),bt(d,i)},Ta={defaultValue:F.arrayOf(F.number),value:F.arrayOf(F.number),count:Number,pushable:Ht(F.oneOfType([F.looseBool,F.number])),allowCross:{type:Boolean,default:void 0},disabled:{type:Boolean,default:void 0},reverse:{type:Boolean,default:void 0},tabindex:F.arrayOf(F.number),prefixCls:String,min:Number,max:Number,autofocus:{type:Boolean,default:void 0},ariaLabelGroupForHandles:Array,ariaLabelledByGroupForHandles:Array,ariaValueTextFormatterGroupForHandles:Array,draggableTrack:{type:Boolean,default:void 0}},Fa=X({compatConfig:{MODE:3},name:"Range",mixins:[$e],inheritAttrs:!1,props:ot(Ta,{count:1,allowCross:!0,pushable:!1,tabindex:[],draggableTrack:!1,ariaLabelGroupForHandles:[],ariaLabelledByGroupForHandles:[],ariaValueTextFormatterGroupForHandles:[]}),emits:["beforeChange","afterChange","change"],displayName:"Range",data:function(){var e=this,t=this.count,a=this.min,n=this.max,i=Array.apply(void 0,Z(Array(t+1))).map(function(){return a}),s=Te(this,"defaultValue")?this.defaultValue:i,o=this.value;o===void 0&&(o=s);var l=o.map(function(d,u){return ce({value:d,handle:u,props:e.$props})}),c=l[0]===n?0:l.length-1;return{sHandle:null,recent:c,bounds:l}},watch:{value:{handler:function(e){var t=this.bounds;this.setChangeValue(e||t)},deep:!0},min:function(){var e=this.value;this.setChangeValue(e||this.bounds)},max:function(){var e=this.value;this.setChangeValue(e||this.bounds)}},methods:{setChangeValue:function(e){var t=this,a=this.bounds,n=e.map(function(s,o){return ce({value:s,handle:o,bounds:a,props:t.$props})});if(a.length===n.length){if(n.every(function(s,o){return s===a[o]}))return null}else n=e.map(function(s,o){return ce({value:s,handle:o,props:t.$props})});if(this.setState({bounds:n}),e.some(function(s){return pt(s,t.$props)})){var i=e.map(function(s){return He(s,t.$props)});this.$emit("change",i)}},onChange:function(e){var t=!Te(this,"value");if(t)this.setState(e);else{var a={};["sHandle","recent"].forEach(function(s){e[s]!==void 0&&(a[s]=e[s])}),Object.keys(a).length&&this.setState(a)}var n=p(p({},this.$data),e),i=n.bounds;this.$emit("change",i)},positionGetValue:function(e){var t=this.getValue(),a=this.calcValueByPos(e),n=this.getClosestBound(a),i=this.getBoundNeedMoving(a,n),s=t[i];if(a===s)return null;var o=Z(t);return o[i]=a,o},onStart:function(e){var t=this.bounds;this.$emit("beforeChange",t);var a=this.calcValueByPos(e);this.startValue=a,this.startPosition=e;var n=this.getClosestBound(a);this.prevMovedHandleIndex=this.getBoundNeedMoving(a,n),this.setState({sHandle:this.prevMovedHandleIndex,recent:this.prevMovedHandleIndex});var i=t[this.prevMovedHandleIndex];if(a!==i){var s=Z(t);s[this.prevMovedHandleIndex]=a,this.onChange({bounds:s})}},onEnd:function(e){var t=this.sHandle;this.removeDocumentEvents(),t||(this.dragTrack=!1),(t!==null||e)&&this.$emit("afterChange",this.bounds),this.setState({sHandle:null})},onMove:function(e,t,a,n){oe(e);var i=this.$data,s=this.$props,o=s.max||100,l=s.min||0;if(a){var c=s.vertical?-t:t;c=s.reverse?-c:c;var d=o-Math.max.apply(Math,Z(n)),u=l-Math.min.apply(Math,Z(n)),x=Math.min(Math.max(c/(this.getSliderLength()/100),u),d),N=n.map(function(b){return Math.floor(Math.max(Math.min(b+x,o),l))});i.bounds.map(function(b,S){return b===N[S]}).some(function(b){return!b})&&this.onChange({bounds:N});return}var w=this.bounds,M=this.sHandle,P=this.calcValueByPos(t),V=w[M];P!==V&&this.moveTo(P)},onKeyboard:function(e){var t=this.$props,a=t.reverse,n=t.vertical,i=yt(e,n,a);if(i){oe(e);var s=this.bounds,o=this.sHandle,l=s[o===null?this.recent:o],c=i(l,this.$props),d=ce({value:c,handle:o,bounds:s,props:this.$props});if(d===l)return;var u=!0;this.moveTo(d,u)}},getClosestBound:function(e){for(var t=this.bounds,a=0,n=1;n=t[n]&&(a=n);return Math.abs(t[a+1]-e)=n.length||s<0)return!1;var o=t+a,l=n[s],c=this.pushable,d=Number(c),u=a*(e[o]-l);return this.pushHandle(e,o,a,d-u)?(e[t]=l,!0):!1},trimAlignValue:function(e){var t=this.sHandle,a=this.bounds;return ce({value:e,handle:t,bounds:a,props:this.$props})},ensureValueNotConflict:function(e,t,a){var n=a.allowCross,i=a.pushable,s=this.$data||{},o=s.bounds;if(e=e===void 0?s.sHandle:e,i=Number(i),!n&&e!=null&&o!==void 0){if(e>0&&t<=o[e-1]+i)return o[e-1]+i;if(e=o[e+1]-i)return o[e+1]-i}return t},getTrack:function(e){var t=e.bounds,a=e.prefixCls,n=e.reverse,i=e.vertical,s=e.included,o=e.offsets,l=e.trackStyle;return t.slice(0,-1).map(function(c,d){var u,x=d+1,N=z((u={},m(u,"".concat(a,"-track"),!0),m(u,"".concat(a,"-track-").concat(x),!0),u));return y(vt,{class:N,vertical:i,reverse:n,included:s,offset:o[x-1],length:o[x]-o[x-1],style:l[d],key:x},null)})},renderSlider:function(){var e=this,t=this.sHandle,a=this.bounds,n=this.prefixCls,i=this.vertical,s=this.included,o=this.disabled,l=this.min,c=this.max,d=this.reverse,u=this.handle,x=this.defaultHandle,N=this.trackStyle,w=this.handleStyle,M=this.tabindex,P=this.ariaLabelGroupForHandles,V=this.ariaLabelledByGroupForHandles,b=this.ariaValueTextFormatterGroupForHandles,S=u||x,C=a.map(function(B){return e.calcOffset(B)}),g="".concat(n,"-handle"),I=a.map(function(B,f){var E,D=M[f]||0;(o||M[f]===null)&&(D=null);var $=t===f;return S({class:z((E={},m(E,g,!0),m(E,"".concat(g,"-").concat(f+1),!0),m(E,"".concat(g,"-dragging"),$),E)),prefixCls:n,vertical:i,dragging:$,offset:C[f],value:B,index:f,tabindex:D,min:l,max:c,reverse:d,disabled:o,style:w[f],ref:function(H){return e.saveHandle(f,H)},onFocus:e.onFocus,onBlur:e.onBlur,ariaLabel:P[f],ariaLabelledBy:V[f],ariaValueTextFormatter:b[f]})});return{tracks:this.getTrack({bounds:a,prefixCls:n,reverse:d,vertical:i,included:s,offsets:C,trackStyle:N}),handles:I}}}});const Pa=xt(Fa),Ea=X({compatConfig:{MODE:3},name:"SliderTooltip",inheritAttrs:!1,props:At(),setup:function(e,t){var a=t.attrs,n=t.slots,i=_(null),s=_(null);function o(){xe.cancel(s.value),s.value=null}function l(){s.value=xe(function(){var d;(d=i.value)===null||d===void 0||d.forcePopupAlign(),s.value=null})}var c=function(){o(),e.visible&&l()};return ie([function(){return e.visible},function(){return e.title}],function(){c()},{flush:"post",immediate:!0}),Lt(function(){c()}),Se(function(){o()}),function(){return y(Rt,p(p({ref:i},e),a),n)}}});var $a=["value","dragging","index"],Ia=["tooltipPrefixCls","range","id"],Oa=function(e){return typeof e=="number"?e.toString():""},Da=function(){return{id:String,prefixCls:String,tooltipPrefixCls:String,range:{type:[Boolean,Object],default:void 0},reverse:{type:Boolean,default:void 0},min:Number,max:Number,step:{type:[Number,Object]},marks:{type:Object},dots:{type:Boolean,default:void 0},value:{type:[Number,Array]},defaultValue:{type:[Number,Array]},included:{type:Boolean,default:void 0},disabled:{type:Boolean,default:void 0},vertical:{type:Boolean,default:void 0},tipFormatter:{type:[Function,Object],default:function(){return Oa}},tooltipVisible:{type:Boolean,default:void 0},tooltipPlacement:{type:String},getTooltipPopupContainer:{type:Function},autofocus:{type:Boolean,default:void 0},handleStyle:{type:[Object,Array]},trackStyle:{type:[Object,Array]},onChange:{type:Function},onAfterChange:{type:Function},onFocus:{type:Function},onBlur:{type:Function},"onUpdate:value":{type:Function}}},_a=X({compatConfig:{MODE:3},name:"ASlider",inheritAttrs:!1,props:Da(),slots:["mark"],setup:function(e,t){var a=t.attrs,n=t.slots,i=t.emit,s=t.expose,o=nt("slider",e),l=o.prefixCls,c=o.rootPrefixCls,d=o.direction,u=o.getPopupContainer,x=o.configProvider,N=at(),w=_(),M=_({}),P=function(f,E){M.value[f]=E},V=re(function(){return e.tooltipPlacement?e.tooltipPlacement:e.vertical?d.value==="rtl"?"left":"right":"top"}),b=function(){var f;(f=w.value)===null||f===void 0||f.focus()},S=function(){var f;(f=w.value)===null||f===void 0||f.blur()},C=function(f){i("update:value",f),i("change",f),N.onFieldChange()},g=function(f){i("blur",f)};s({focus:b,blur:S});var I=function(f){var E=f.tooltipPrefixCls,D=f.info,$=D.value,L=D.dragging,H=D.index,G=he(D,$a),R=e.tipFormatter,U=e.tooltipVisible,J=e.getTooltipPopupContainer,ee=R?M.value[H]||L:!1,te=U||U===void 0&ⅇreturn y(Ea,{prefixCls:E,title:R?R($):"",visible:te,placement:V.value,transitionName:"".concat(c.value,"-zoom-down"),key:H,overlayClassName:"".concat(l.value,"-tooltip"),getPopupContainer:J||u.value},{default:function(){return[y(mt,p(p({},G),{},{value:$,onMouseenter:function(){return P(H,!0)},onMouseleave:function(){return P(H,!1)}}),null)]}})};return function(){var B=e.tooltipPrefixCls,f=e.range,E=e.id,D=E===void 0?N.id.value:E,$=he(e,Ia),L=x.getPrefixCls("tooltip",B),H=z(a.class,m({},"".concat(l.value,"-rtl"),d.value==="rtl"));d.value==="rtl"&&!$.vertical&&($.reverse=!$.reverse);var G;return Ee(f)==="object"&&(G=f.draggableTrack),f?y(Pa,p(p({},$),{},{step:$.step,draggableTrack:G,class:H,ref:w,handle:function(U){return I({tooltipPrefixCls:L,prefixCls:l.value,info:U})},prefixCls:l.value,onChange:C,onBlur:g}),{mark:n.mark}):y(Ba,p(p({},$),{},{id:D,step:$.step,class:H,ref:w,handle:function(U){return I({tooltipPrefixCls:L,prefixCls:l.value,info:U})},prefixCls:l.value,onChange:C,onBlur:g}),{mark:n.mark})}}});const Ha=Ut(_a);var Aa={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"}}]},name:"download",theme:"outlined"};const La=Aa;function Qe(r){for(var e=1;e{const i=Sa,s=Ha;return zt(),Kt("div",ja,[y(i,Re({value:t.value,"onUpdate:value":n[0]||(n[0]=o=>t.value=o)},e),null,16,["value"]),y(s,Re({value:t.value,"onUpdate:value":n[1]||(n[1]=o=>t.value=o)},e,{class:"slide"}),null,16,["value"])])}}});const Ja=async r=>(await se.value.get("/files",{params:{folder_path:r}})).data,Qa=async r=>(await se.value.post("/delete_files",{file_paths:r})).data,Za=async(r,e,t)=>(await se.value.post("/move_files",{file_paths:r,dest:e,create_dest_folder:t})).data,en=async(r,e,t)=>(await se.value.post("/copy_files",{file_paths:r,dest:e,create_dest_folder:t})).data,Ga=async r=>{await se.value.post("/mkdirs",{dest_folder:r})},tn=async r=>(await se.value.post("/batch_get_files_info",{paths:r})).data;let de,Be;const za=async(r,e,t="image/webp")=>{const n=await(await fetch(r)).arrayBuffer();return new File([n],e,{type:t})},Ka=(r,e="image/webp")=>{if(!Be){de=document.createElement("canvas");const n=de.getContext("2d");qt(n),Be=n}const{videoHeight:t,videoWidth:a}=r;return de.width=a,de.height=t,Be.drawImage(r,0,0,a,t),de.toDataURL(e)},an=r=>{const e=_("");return new Promise(t=>{Ce.confirm({title:Y("inputFolderName"),content:()=>y(lt,{value:e.value,"onUpdate:value":a=>e.value=a},null),async onOk(){if(!e.value)return;const a=na(r,e.value);await Ga(a),t()}})})},nn=()=>y("p",{style:{background:"var(--zp-secondary-background)",padding:"8px",borderLeft:"4px solid var(--primary-color)"}},[ra("Tips: "),Y("multiSelectTips")]),rn=(r,e)=>{const t=Wt(),a=Xt(),n=o=>{var l;return!!((l=t.tagMap.get(r.fullpath))!=null&&l.some(c=>c.id===o))},i=_(null),s=async()=>{if(!i.value)return;const o=i.value;o.pause();const l=Ka(o);await aa({path:r.fullpath,base64_img:l,updated_time:r.date}),r.cover_url=URL.createObjectURL(await za(l,"cover")),st.success(Y("success")+"! "+Y("clearCacheIfNotTakeEffect"))};Ce.confirm({width:"80vw",title:r.name,icon:null,content:()=>y("div",{style:{display:"flex",alignItems:"center",justifyContent:"center",flexDirection:"column"}},[y("video",{ref:i,style:{maxHeight:Yt?"80vh":"60vh",maxWidth:"100%",minWidth:"70%"},src:Jt(r),controls:!0,autoplay:!0},null),y("div",{style:{marginTop:"4px"}},[a.conf.all_custom_tags.map(o=>y("div",{key:o.id,onClick:()=>e==null?void 0:e(o.id),style:{background:n(o.id)?t.getColor(o.name):"var(--zp-primary-background)",color:n(o.id)?"white":t.getColor(o.name),margin:"2px",padding:"2px 16px","border-radius":"4px",display:"inline-block",cursor:"pointer","font-weight":"bold",transition:".5s all ease",border:`2px solid ${t.getColor(o.name)}`,"user-select":"none"}},[o.name]))]),y("div",{class:"actions",style:{marginTop:"16px"}},[y(Ue,{onClick:()=>Qt([Zt(r,!0)])},{icon:y(Ua,null,null),default:Y("download")}),y(Ue,{onClick:s},{default:Y("setCurrFrameAsVideoPoster")})])]),maskClosable:!0,wrapClassName:"hidden-antd-btns-modal"})},on=()=>{Ce.confirm({title:Y("confirmRebuildImageIndex"),onOk:async()=>{await ea(),ta.emit("searchIndexExpired"),st.success(Y("rebuildComplete"))}})},sn=r=>{const e=_(r.split(/[\\/]/).pop()??"");return new Promise(t=>{Ce.confirm({title:Y("rename"),content:()=>y(lt,{value:e.value,"onUpdate:value":a=>e.value=a},null),async onOk(){if(!e.value)return;const a=await ia({path:r,name:e.value});t(a.new_path)}})})};export{nn as M,Ya as _,on as a,sn as b,en as c,Qa as d,Sa as e,tn as f,Ja as g,rn as h,Za as m,an as o}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/functionalCallableComp-efd6daa3.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/functionalCallableComp-efd6daa3.css new file mode 100644 index 0000000000000000000000000000000000000000..c2563b1a12f703ce7f31c3ff302af4c30e2316aa --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/functionalCallableComp-efd6daa3.css @@ -0,0 +1 @@ +.ant-slider{box-sizing:border-box;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;height:12px;margin:10px 6px;padding:4px 0;cursor:pointer;touch-action:none}.ant-slider-vertical{width:12px;height:100%;margin:6px 10px;padding:0 4px}.ant-slider-vertical .ant-slider-rail{width:4px;height:100%}.ant-slider-vertical .ant-slider-track{width:4px}.ant-slider-vertical .ant-slider-handle{margin-top:-6px;margin-left:-5px}.ant-slider-vertical .ant-slider-mark{top:0;left:12px;width:18px;height:100%}.ant-slider-vertical .ant-slider-mark-text{left:4px;white-space:nowrap}.ant-slider-vertical .ant-slider-step{width:4px;height:100%}.ant-slider-vertical .ant-slider-dot{top:auto;left:2px;margin-bottom:-4px}.ant-slider-tooltip .ant-tooltip-inner{min-width:unset}.ant-slider-rtl.ant-slider-vertical .ant-slider-handle{margin-right:-5px;margin-left:0}.ant-slider-rtl.ant-slider-vertical .ant-slider-mark{right:12px;left:auto}.ant-slider-rtl.ant-slider-vertical .ant-slider-mark-text{right:4px;left:auto}.ant-slider-rtl.ant-slider-vertical .ant-slider-dot{right:2px;left:auto}.ant-slider-with-marks{margin-bottom:28px}.ant-slider-rail{position:absolute;width:100%;height:4px;background-color:#f5f5f5;border-radius:2px;transition:background-color .3s}.ant-slider-track{position:absolute;height:4px;background-color:#f7ae83;border-radius:2px;transition:background-color .3s}.ant-slider-handle{position:absolute;width:14px;height:14px;margin-top:-5px;background-color:#fff;border:solid 2px #f7ae83;border-radius:50%;box-shadow:0;cursor:pointer;transition:border-color .3s,box-shadow .6s,transform .3s cubic-bezier(.18,.89,.32,1.28)}.ant-slider-handle-dragging.ant-slider-handle-dragging.ant-slider-handle-dragging{border-color:#d9653b;box-shadow:0 0 0 5px #d03f0a1f}.ant-slider-handle:focus{border-color:#d9653b;outline:none;box-shadow:0 0 0 5px #d03f0a1f}.ant-slider-handle.ant-tooltip-open{border-color:#d03f0a}.ant-slider:hover .ant-slider-rail{background-color:#e1e1e1}.ant-slider:hover .ant-slider-track{background-color:#eb8857}.ant-slider:hover .ant-slider-handle:not(.ant-tooltip-open){border-color:#eb8857}.ant-slider-mark{position:absolute;top:14px;left:0;width:100%;font-size:14px}.ant-slider-mark-text{position:absolute;display:inline-block;color:#00000073;text-align:center;word-break:keep-all;cursor:pointer;user-select:none}.ant-slider-mark-text-active{color:#000000d9}.ant-slider-step{position:absolute;width:100%;height:4px;background:transparent}.ant-slider-dot{position:absolute;top:-2px;width:8px;height:8px;margin-left:-4px;background-color:#fff;border:2px solid #f0f0f0;border-radius:50%;cursor:pointer}.ant-slider-dot:first-child{margin-left:-4px}.ant-slider-dot:last-child{margin-left:-4px}.ant-slider-dot-active{border-color:#e89f85}.ant-slider-disabled{cursor:not-allowed}.ant-slider-disabled .ant-slider-rail{background-color:#f5f5f5!important}.ant-slider-disabled .ant-slider-track{background-color:#00000040!important}.ant-slider-disabled .ant-slider-handle,.ant-slider-disabled .ant-slider-dot{background-color:#fff;border-color:#00000040!important;box-shadow:none;cursor:not-allowed}.ant-slider-disabled .ant-slider-mark-text,.ant-slider-disabled .ant-slider-dot{cursor:not-allowed!important}.ant-slider-rtl{direction:rtl}.ant-slider-rtl .ant-slider-mark{right:0;left:auto}.ant-slider-rtl .ant-slider-dot,.ant-slider-rtl .ant-slider-dot:first-child{margin-right:-4px;margin-left:0}.ant-slider-rtl .ant-slider-dot:last-child{margin-right:-4px;margin-left:0}.ant-input-number-affix-wrapper{position:relative;display:inline-block;width:100%;min-width:0;color:#000000d9;font-size:14px;line-height:1.5715;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:2px;transition:all .3s;position:static;display:inline-flex;width:90px;padding:0;padding-inline-start:11px}.ant-input-number-affix-wrapper::-moz-placeholder{opacity:1}.ant-input-number-affix-wrapper::placeholder{color:#bfbfbf;user-select:none}.ant-input-number-affix-wrapper:placeholder-shown{text-overflow:ellipsis}.ant-input-number-affix-wrapper:hover{border-color:#de632f;border-right-width:1px!important}.ant-input-number-affix-wrapper:focus,.ant-input-number-affix-wrapper-focused{border-color:#de632f;box-shadow:0 0 0 2px #d03f0a33;border-right-width:1px!important;outline:0}.ant-input-number-affix-wrapper-disabled{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-affix-wrapper-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-input-number-affix-wrapper[disabled]{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-affix-wrapper[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-input-number-affix-wrapper-borderless,.ant-input-number-affix-wrapper-borderless:hover,.ant-input-number-affix-wrapper-borderless:focus,.ant-input-number-affix-wrapper-borderless-focused,.ant-input-number-affix-wrapper-borderless-disabled,.ant-input-number-affix-wrapper-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input-number-affix-wrapper{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-number-affix-wrapper-lg{padding:6.5px 11px;font-size:16px}.ant-input-number-affix-wrapper-sm{padding:0 7px}.ant-input-number-affix-wrapper:not(.ant-input-number-affix-wrapper-disabled):hover{border-color:#de632f;border-right-width:1px!important;z-index:1}.ant-input-number-affix-wrapper-focused,.ant-input-number-affix-wrapper:focus{z-index:1}.ant-input-number-affix-wrapper-disabled .ant-input-number[disabled]{background:transparent}.ant-input-number-affix-wrapper>div.ant-input-number{width:100%;border:none;outline:none}.ant-input-number-affix-wrapper>div.ant-input-number.ant-input-number-focused{box-shadow:none!important}.ant-input-number-affix-wrapper input.ant-input-number-input{padding:0}.ant-input-number-affix-wrapper:before{width:0;visibility:hidden;content:" "}.ant-input-number-prefix{display:flex;flex:none;align-items:center;margin-inline-end:4px}.ant-input-number-group-wrapper .ant-input-number-affix-wrapper{width:100%}.ant-input-number{box-sizing:border-box;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";position:relative;width:100%;min-width:0;color:#000000d9;font-size:14px;line-height:1.5715;background-color:#fff;background-image:none;transition:all .3s;display:inline-block;width:90px;margin:0;padding:0;border:1px solid #d9d9d9;border-radius:2px}.ant-input-number::-moz-placeholder{opacity:1}.ant-input-number::placeholder{color:#bfbfbf;user-select:none}.ant-input-number:placeholder-shown{text-overflow:ellipsis}.ant-input-number:focus,.ant-input-number-focused{border-color:#de632f;box-shadow:0 0 0 2px #d03f0a33;border-right-width:1px!important;outline:0}.ant-input-number[disabled]{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-input-number-borderless,.ant-input-number-borderless:hover,.ant-input-number-borderless:focus,.ant-input-number-borderless-focused,.ant-input-number-borderless-disabled,.ant-input-number-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height:1.5715;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-number-lg{padding:6.5px 11px;font-size:16px}.ant-input-number-sm{padding:0 7px}.ant-input-number-group{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:table;width:100%;border-collapse:separate;border-spacing:0}.ant-input-number-group[class*=col-]{float:none;padding-right:0;padding-left:0}.ant-input-number-group>[class*=col-]{padding-right:8px}.ant-input-number-group>[class*=col-]:last-child{padding-right:0}.ant-input-number-group-addon,.ant-input-number-group-wrap,.ant-input-number-group>.ant-input-number{display:table-cell}.ant-input-number-group-addon:not(:first-child):not(:last-child),.ant-input-number-group-wrap:not(:first-child):not(:last-child),.ant-input-number-group>.ant-input-number:not(:first-child):not(:last-child){border-radius:0}.ant-input-number-group-addon,.ant-input-number-group-wrap{width:1px;white-space:nowrap;vertical-align:middle}.ant-input-number-group-wrap>*{display:block!important}.ant-input-number-group .ant-input-number{float:left;width:100%;margin-bottom:0;text-align:inherit}.ant-input-number-group .ant-input-number:focus{z-index:1;border-right-width:1px}.ant-input-number-group .ant-input-number:hover{z-index:1;border-right-width:1px}.ant-input-search-with-button .ant-input-number-group .ant-input-number:hover{z-index:0}.ant-input-number-group-addon{position:relative;padding:0 11px;color:#000000d9;font-weight:400;font-size:14px;text-align:center;background-color:#fafafa;border:1px solid #d9d9d9;border-radius:2px;transition:all .3s}.ant-input-number-group-addon .ant-select{margin:-5px -11px}.ant-input-number-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector{background-color:inherit;border:1px solid transparent;box-shadow:none}.ant-input-number-group-addon .ant-select-open .ant-select-selector,.ant-input-number-group-addon .ant-select-focused .ant-select-selector{color:#d03f0a}.ant-input-number-group-addon .ant-cascader-picker{margin:-9px -12px;background-color:transparent}.ant-input-number-group-addon .ant-cascader-picker .ant-cascader-input{text-align:left;border:0;box-shadow:none}.ant-input-number-group>.ant-input-number:first-child,.ant-input-number-group-addon:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group>.ant-input-number:first-child .ant-select .ant-select-selector,.ant-input-number-group-addon:first-child .ant-select .ant-select-selector{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group>.ant-input-number-affix-wrapper:not(:first-child) .ant-input-number{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group>.ant-input-number-affix-wrapper:not(:last-child) .ant-input-number{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group-addon:first-child{border-right:0}.ant-input-number-group-addon:last-child{border-left:0}.ant-input-number-group>.ant-input-number:last-child,.ant-input-number-group-addon:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group>.ant-input-number:last-child .ant-select .ant-select-selector,.ant-input-number-group-addon:last-child .ant-select .ant-select-selector{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group-lg .ant-input-number,.ant-input-number-group-lg>.ant-input-number-group-addon{padding:6.5px 11px;font-size:16px}.ant-input-number-group-sm .ant-input-number,.ant-input-number-group-sm>.ant-input-number-group-addon{padding:0 7px}.ant-input-number-group-lg .ant-select-single .ant-select-selector{height:40px}.ant-input-number-group-sm .ant-select-single .ant-select-selector{height:24px}.ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child){border-top-left-radius:2px;border-bottom-left-radius:2px}.ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child),.ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group.ant-input-number-group-compact{display:block}.ant-input-number-group.ant-input-number-group-compact:before{display:table;content:""}.ant-input-number-group.ant-input-number-group-compact:after{display:table;clear:both;content:""}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child),.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child),.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child){border-right-width:1px}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):hover,.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):hover,.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child):hover{z-index:1}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):focus,.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):focus,.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child):focus{z-index:1}.ant-input-number-group.ant-input-number-group-compact>*{display:inline-block;float:none;vertical-align:top;border-radius:0}.ant-input-number-group.ant-input-number-group-compact>.ant-input-number-affix-wrapper{display:inline-flex}.ant-input-number-group.ant-input-number-group-compact>.ant-picker-range{display:inline-flex}.ant-input-number-group.ant-input-number-group-compact>*:not(:last-child){margin-right:-1px;border-right-width:1px}.ant-input-number-group.ant-input-number-group-compact .ant-input-number{float:none}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input{border-right-width:1px;border-radius:0}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input:hover{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input:focus{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-select-focused{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-arrow{z-index:1}.ant-input-number-group.ant-input-number-group-compact>*:first-child,.ant-input-number-group.ant-input-number-group-compact>.ant-select:first-child>.ant-select-selector,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete:first-child .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker:first-child .ant-input{border-top-left-radius:2px;border-bottom-left-radius:2px}.ant-input-number-group.ant-input-number-group-compact>*:last-child,.ant-input-number-group.ant-input-number-group-compact>.ant-select:last-child>.ant-select-selector,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker:last-child .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker-focused:last-child .ant-input{border-right-width:1px;border-top-right-radius:2px;border-bottom-right-radius:2px}.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input{vertical-align:top}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper{margin-left:-1px}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper .ant-input-affix-wrapper{border-radius:0}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input-group-addon>.ant-input-search-button{border-radius:0}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input{border-radius:2px 0 0 2px}.ant-input-number-group-wrapper{display:inline-block;text-align:start;vertical-align:top}.ant-input-number-handler{position:relative;display:block;width:100%;height:50%;overflow:hidden;color:#00000073;font-weight:700;line-height:0;text-align:center;border-left:1px solid #d9d9d9;transition:all .1s linear}.ant-input-number-handler:active{background:#f4f4f4}.ant-input-number-handler:hover .ant-input-number-handler-up-inner,.ant-input-number-handler:hover .ant-input-number-handler-down-inner{color:#de632f}.ant-input-number-handler-up-inner,.ant-input-number-handler-down-inner{display:inline-block;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;right:4px;width:12px;height:12px;color:#00000073;line-height:12px;transition:all .1s linear;user-select:none}.ant-input-number-handler-up-inner>*,.ant-input-number-handler-down-inner>*{line-height:1}.ant-input-number-handler-up-inner svg,.ant-input-number-handler-down-inner svg{display:inline-block}.ant-input-number-handler-up-inner:before,.ant-input-number-handler-down-inner:before{display:none}.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon{display:block}.ant-input-number:hover{border-color:#de632f;border-right-width:1px!important}.ant-input-number:hover+.ant-form-item-children-icon{opacity:0;transition:opacity .24s linear .24s}.ant-input-number-focused{border-color:#de632f;box-shadow:0 0 0 2px #d03f0a33;border-right-width:1px!important;outline:0}.ant-input-number-disabled{color:#00000040;background-color:#f5f5f5;border-color:#d9d9d9;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-input-number-disabled .ant-input-number-input{cursor:not-allowed}.ant-input-number-disabled .ant-input-number-handler-wrap,.ant-input-number-readonly .ant-input-number-handler-wrap{display:none}.ant-input-number-input{width:100%;height:30px;padding:0 11px;text-align:left;background-color:transparent;border:0;border-radius:2px;outline:0;transition:all .3s linear;appearance:textfield!important}.ant-input-number-input::-moz-placeholder{opacity:1}.ant-input-number-input::placeholder{color:#bfbfbf;user-select:none}.ant-input-number-input:placeholder-shown{text-overflow:ellipsis}.ant-input-number-input[type=number]::-webkit-inner-spin-button,.ant-input-number-input[type=number]::-webkit-outer-spin-button{margin:0;-webkit-appearance:none;appearance:none}.ant-input-number-lg{padding:0;font-size:16px}.ant-input-number-lg input{height:38px}.ant-input-number-sm{padding:0}.ant-input-number-sm input{height:22px;padding:0 7px}.ant-input-number-handler-wrap{position:absolute;top:0;right:0;width:22px;height:100%;background:#fff;border-radius:0 2px 2px 0;opacity:0;transition:opacity .24s linear .1s}.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-up-inner,.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-down-inner{display:flex;align-items:center;justify-content:center;min-width:auto;margin-right:0;font-size:7px}.ant-input-number-borderless .ant-input-number-handler-wrap{border-left-width:0}.ant-input-number-handler-wrap:hover .ant-input-number-handler{height:40%}.ant-input-number:hover .ant-input-number-handler-wrap,.ant-input-number-focused .ant-input-number-handler-wrap{opacity:1}.ant-input-number-handler-up{border-top-right-radius:2px;cursor:pointer}.ant-input-number-handler-up-inner{top:50%;margin-top:-5px;text-align:center}.ant-input-number-handler-up:hover{height:60%!important}.ant-input-number-handler-down{top:0;border-top:1px solid #d9d9d9;border-bottom-right-radius:2px;cursor:pointer}.ant-input-number-handler-down-inner{top:50%;text-align:center;transform:translateY(-50%)}.ant-input-number-handler-down:hover{height:60%!important}.ant-input-number-borderless .ant-input-number-handler-down{border-top-width:0}.ant-input-number-handler-up-disabled,.ant-input-number-handler-down-disabled{cursor:not-allowed}.ant-input-number-handler-up-disabled:hover .ant-input-number-handler-up-inner,.ant-input-number-handler-down-disabled:hover .ant-input-number-handler-down-inner{color:#00000040}.ant-input-number-borderless{box-shadow:none}.ant-input-number-out-of-range input{color:#ff4d4f}.ant-input-number-rtl{direction:rtl}.ant-input-number-rtl .ant-input-number-handler{border-right:1px solid #d9d9d9;border-left:0}.ant-input-number-rtl .ant-input-number-handler-wrap{right:auto;left:0}.ant-input-number-rtl.ant-input-number-borderless .ant-input-number-handler-wrap{border-right-width:0}.ant-input-number-rtl .ant-input-number-handler-up{border-top-right-radius:0}.ant-input-number-rtl .ant-input-number-handler-down{border-bottom-right-radius:0}.ant-input-number-rtl .ant-input-number-input{direction:ltr;text-align:right}.num-input[data-v-55978858]{display:flex}.num-input .slide[data-v-55978858]{flex:1;min-width:128px;max-width:256px;margin-left:8px} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/globalSetting-00ba278d.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/globalSetting-00ba278d.css new file mode 100644 index 0000000000000000000000000000000000000000..fb4a2ec37335bab87acb94d543011cad44370bc2 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/globalSetting-00ba278d.css @@ -0,0 +1 @@ +@keyframes antCheckboxEffect{0%{transform:scale(1);opacity:.5}to{transform:scale(1.6);opacity:0}}.ant-checkbox{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;top:.2em;line-height:1;white-space:nowrap;outline:none;cursor:pointer}.ant-checkbox-wrapper:hover .ant-checkbox-inner,.ant-checkbox:hover .ant-checkbox-inner,.ant-checkbox-input:focus+.ant-checkbox-inner{border-color:#d03f0a}.ant-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #d03f0a;border-radius:2px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-checkbox:hover:after,.ant-checkbox-wrapper:hover .ant-checkbox:after{visibility:visible}.ant-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;direction:ltr;background-color:#fff;border:1px solid #d9d9d9;border-radius:2px;border-collapse:separate;transition:all .3s}.ant-checkbox-inner:after{position:absolute;top:50%;left:21.5%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-checkbox-checked .ant-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-checkbox-checked .ant-checkbox-inner{background-color:#d03f0a;border-color:#d03f0a}.ant-checkbox-disabled{cursor:not-allowed}.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner:after{border-color:#00000040;animation-name:none}.ant-checkbox-disabled .ant-checkbox-input{cursor:not-allowed;pointer-events:none}.ant-checkbox-disabled .ant-checkbox-inner{background-color:#f5f5f5;border-color:#d9d9d9!important}.ant-checkbox-disabled .ant-checkbox-inner:after{border-color:#f5f5f5;border-collapse:separate;animation-name:none}.ant-checkbox-disabled+span{color:#00000040;cursor:not-allowed}.ant-checkbox-disabled:hover:after,.ant-checkbox-wrapper:hover .ant-checkbox-disabled:after{visibility:hidden}.ant-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-flex;align-items:baseline;line-height:unset;cursor:pointer}.ant-checkbox-wrapper:after{display:inline-block;width:0;overflow:hidden;content:" "}.ant-checkbox-wrapper.ant-checkbox-wrapper-disabled{cursor:not-allowed}.ant-checkbox-wrapper+.ant-checkbox-wrapper{margin-left:8px}.ant-checkbox+span{padding-right:8px;padding-left:8px}.ant-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-checkbox-group-item{margin-right:8px}.ant-checkbox-group-item:last-child{margin-right:0}.ant-checkbox-group-item+.ant-checkbox-group-item{margin-left:0}.ant-checkbox-indeterminate .ant-checkbox-inner{background-color:#fff;border-color:#d9d9d9}.ant-checkbox-indeterminate .ant-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#d03f0a;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-checkbox-indeterminate.ant-checkbox-disabled .ant-checkbox-inner:after{background-color:#00000040;border-color:#00000040}.ant-checkbox-rtl{direction:rtl}.ant-checkbox-group-rtl .ant-checkbox-group-item{margin-right:0;margin-left:8px}.ant-checkbox-group-rtl .ant-checkbox-group-item:last-child{margin-left:0!important}.ant-checkbox-group-rtl .ant-checkbox-group-item+.ant-checkbox-group-item{margin-left:8px}.panel[data-v-8a6b0808]{padding:8px;margin:16px;border-radius:8px;background:var(--zp-primary-background);overflow:auto;height:calc(100% - 32px)}.panel[data-v-8a6b0808]>:not(:first-child){margin-left:16px}.lang-select-wrap[data-v-8a6b0808]{width:128px;display:inline-block;padding-right:16px}h2[data-v-8a6b0808]{margin:64px 0 16px;font-weight:700}.row[data-v-8a6b0808]{margin-top:16px;padding:0 16px}.col[data-v-8a6b0808]{display:flex}.clear-btn[data-v-8a6b0808]{margin-left:16px} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/globalSetting-07137205.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/globalSetting-07137205.js new file mode 100644 index 0000000000000000000000000000000000000000..68c0feceedef3cb087eeab1812deb3d09af709e3 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/globalSetting-07137205.js @@ -0,0 +1 @@ +import{d as B,$ as K,r as D,m as X,n as Y,S as f,T as y,c as t,a1 as o,a2 as e,z as i,a0 as k,Y as v,U as p,X as M,aI as Z,E as j,W as h,V as _,aa as P,aP as q,ab as J,a6 as x,a3 as T,a7 as ee,aQ as te,aR as le,aS as ae,aT as ne,aM as oe,ai as F,ah as se,Z as de}from"./index-93a218ca.js";import{_ as R,a as N,F as ue}from"./numInput-27ba7dc9.js";import"./index-67a7e5e1.js";/* empty css *//* empty css */import{_ as O}from"./index-4596f42d.js";/* empty css */import{g as ie,C as re}from"./shortcut-ff09b8ec.js";import{a as ce}from"./functionalCallableComp-685da399.js";const A="/infinite_image_browsing/fe-static/assets/sample-55dcafc6.webp",me=["width","height","src"],pe=B({__name:"ImageSetting",setup(G){function a($,r){return new Promise(m=>{const g=new Image;g.onload=()=>{const u=document.createElement("canvas");u.width=g.width*r,u.height=g.height*r,u.getContext("2d").drawImage(g,0,0,u.width,u.height),m(u.toDataURL())},g.src=$})}const d=K(),w=D("");return X(()=>[d.enableThumbnail,d.gridThumbnailResolution],Y(async()=>{d.enableThumbnail&&(w.value=await a(A,d.gridThumbnailResolution/1024))},300),{immediate:!0,deep:!0}),($,r)=>{const m=N,g=O;return f(),y(M,null,[t(m,{label:e(i)("defaultGridCellWidth")},{default:o(()=>[t(R,{min:64,max:1024,step:16,modelValue:e(d).defaultGridCellWidth,"onUpdate:modelValue":r[0]||(r[0]=u=>e(d).defaultGridCellWidth=u)},null,8,["modelValue"])]),_:1},8,["label"]),t(m,{label:e(i)("useThumbnailPreview")},{default:o(()=>[t(g,{checked:e(d).enableThumbnail,"onUpdate:checked":r[1]||(r[1]=u=>e(d).enableThumbnail=u)},null,8,["checked"])]),_:1},8,["label"]),e(d).enableThumbnail?(f(),k(m,{key:0,label:e(i)("thumbnailResolution")},{default:o(()=>[t(R,{modelValue:e(d).gridThumbnailResolution,"onUpdate:modelValue":r[2]||(r[2]=u=>e(d).gridThumbnailResolution=u),min:256,max:1024,step:64},null,8,["modelValue"])]),_:1},8,["label"])):v("",!0),t(m,{label:e(i)("livePreview")},{default:o(()=>[p("div",null,[p("img",{width:e(d).defaultGridCellWidth,height:e(d).defaultGridCellWidth,src:e(d).enableThumbnail?w.value:e(A)},null,8,me)])]),_:1},8,["label"]),t(m,{label:e(i)("defaultShowChangeIndicators")},{default:o(()=>[t(g,{checked:e(d).defaultChangeIndchecked,"onUpdate:checked":r[3]||(r[3]=u=>e(d).defaultChangeIndchecked=u)},null,8,["checked"])]),_:1},8,["label"]),e(d).defaultChangeIndchecked?(f(),k(m,{key:1,label:e(i)("defaultSeedAsChange")},{default:o(()=>[t(g,{checked:e(d).defaultSeedChangeChecked,"onUpdate:checked":r[4]||(r[4]=u=>e(d).defaultSeedChangeChecked=u)},null,8,["checked"])]),_:1},8,["label"])):v("",!0),t(m,{label:e(i)("previewMaskBackgroundOpacity")},{default:o(()=>[t(R,{min:0,max:1,step:.05,modelValue:e(d).previewBgOpacity,"onUpdate:modelValue":r[5]||(r[5]=u=>e(d).previewBgOpacity=u)},null,8,["modelValue"])]),_:1},8,["label"])],64)}}}),he={class:"panel"},ge={style:{"margin-top":"0"}},fe={class:"lang-select-wrap"},be={class:"col"},_e={class:"col"},ke={class:"col"},ve={class:"col"},we=B({__name:"globalSetting",setup(G){const a=K(),d=Z(),w=D(!1),$=async()=>{window.location.reload()},r=[{value:"en",text:"English"},{value:"zhHans",text:"简体中文"},{value:"zhHant",text:"繁體中文"},{value:"de",text:"Deutsch"}],m=(s,l)=>{const I=ie(s);I&&(a.shortcut[l]=I)},g=async()=>{await te("shutdown_api_server_command"),await le.removeFile(ae),await ne()},u=j(()=>[{value:"empty",text:i("emptyStartPage")},{value:"last-workspace-state",text:i("restoreLastWorkspaceState")},...d.snapshots.map(l=>({value:`workspace_snapshot_${l.id}`,text:i("restoreWorkspaceSnapshot",[l.name])}))]);return(s,l)=>{var V;const I=oe,L=F,c=N,C=O,E=R,S=F,z=re,U=se,H=ue;return f(),y("div",he,[(V=e(a).conf)!=null&&V.is_readonly?(f(),k(I,{key:0,message:s.$t("readonlyModeSettingPageDesc"),type:"warning"},null,8,["message"])):v("",!0),v("",!0),t(H,null,{default:o(()=>{var W;return[p("h2",ge,h(e(i)("ImageBrowsingSettings")),1),t(pe),p("h2",null,h(e(i)("imgSearch")),1),t(c,{label:s.$t("rebuildImageIndex")},{default:o(()=>[t(L,{onClick:e(ce)},{default:o(()=>[_(h(s.$t("start")),1)]),_:1},8,["onClick"])]),_:1},8,["label"]),p("h2",null,h(e(i)("autoRefresh")),1),t(c,{label:s.$t("autoRefreshWalkMode")},{default:o(()=>[t(C,{checked:e(a).autoRefreshWalkMode,"onUpdate:checked":l[0]||(l[0]=n=>e(a).autoRefreshWalkMode=n)},null,8,["checked"])]),_:1},8,["label"]),t(c,{label:s.$t("autoRefreshNormalFixedMode")},{default:o(()=>[t(C,{checked:e(a).autoRefreshNormalFixedMode,"onUpdate:checked":l[1]||(l[1]=n=>e(a).autoRefreshNormalFixedMode=n)},null,8,["checked"])]),_:1},8,["label"]),t(c,{label:e(i)("autoRefreshWalkModePosLimit")},{default:o(()=>[t(E,{min:0,max:1024,step:16,modelValue:e(a).autoRefreshWalkModePosLimit,"onUpdate:modelValue":l[2]||(l[2]=n=>e(a).autoRefreshWalkModePosLimit=n)},null,8,["modelValue"])]),_:1},8,["label"]),p("h2",null,h(e(i)("other")),1),t(c,{label:s.$t("lang")},{default:o(()=>[p("div",fe,[t(e(P),{options:r,value:e(a).lang,"onUpdate:value":l[3]||(l[3]=n=>e(a).lang=n),onChange:l[4]||(l[4]=n=>w.value=!0)},null,8,["value"])]),w.value?(f(),k(S,{key:0,type:"primary",onClick:$,ghost:""},{default:o(()=>[_(h(e(i)("langChangeReload")),1)]),_:1})):v("",!0)]),_:1},8,["label"]),t(c,{label:s.$t("onlyFoldersAndImages")},{default:o(()=>[t(C,{checked:e(a).onlyFoldersAndImages,"onUpdate:checked":l[5]||(l[5]=n=>e(a).onlyFoldersAndImages=n)},null,8,["checked"])]),_:1},8,["label"]),t(c,{label:s.$t("showCommaInGenInfoPanel")},{default:o(()=>[t(C,{checked:e(a).showCommaInInfoPanel,"onUpdate:checked":l[6]||(l[6]=n=>e(a).showCommaInInfoPanel=n)},null,8,["checked"])]),_:1},8,["label"]),t(c,{label:s.$t("defaultSortingMethod")},{default:o(()=>[t(e(P),{value:e(a).defaultSortingMethod,"onUpdate:value":l[7]||(l[7]=n=>e(a).defaultSortingMethod=n),conv:e(q),options:e(J)},null,8,["value","conv","options"])]),_:1},8,["label"]),t(c,{label:s.$t("longPressOpenContextMenu")},{default:o(()=>[t(C,{checked:e(a).longPressOpenContextMenu,"onUpdate:checked":l[8]||(l[8]=n=>e(a).longPressOpenContextMenu=n)},null,8,["checked"])]),_:1},8,["label"]),t(c,{label:s.$t("openOnAppStart")},{default:o(()=>[t(e(P),{value:e(a).defaultInitinalPage,"onUpdate:value":l[9]||(l[9]=n=>e(a).defaultInitinalPage=n),options:u.value},null,8,["value","options"])]),_:1},8,["label"]),(f(!0),y(M,null,x(e(a).ignoredConfirmActions,(n,b)=>(f(),k(c,{label:s.$t(b+"SkipConfirm"),key:b},{default:o(()=>[t(z,{checked:e(a).ignoredConfirmActions[b],"onUpdate:checked":Q=>e(a).ignoredConfirmActions[b]=Q},null,8,["checked","onUpdate:checked"])]),_:2},1032,["label"]))),128)),p("h2",null,h(e(i)("shortcutKey")),1),t(c,{label:s.$t("download")},{default:o(()=>[p("div",be,[t(U,{value:e(a).shortcut.download,onKeydown:l[10]||(l[10]=T(n=>m(n,"download"),["stop","prevent"])),placeholder:s.$t("shortcutKeyDescription")},null,8,["value","placeholder"]),t(S,{onClick:l[11]||(l[11]=n=>e(a).shortcut.download=""),class:"clear-btn"},{default:o(()=>[_(h(s.$t("clear")),1)]),_:1})])]),_:1},8,["label"]),t(c,{label:s.$t("deleteSelected")},{default:o(()=>[p("div",_e,[t(U,{value:e(a).shortcut.delete,onKeydown:l[12]||(l[12]=T(n=>m(n,"delete"),["stop","prevent"])),placeholder:s.$t("shortcutKeyDescription")},null,8,["value","placeholder"]),t(S,{onClick:l[13]||(l[13]=n=>e(a).shortcut.delete=""),class:"clear-btn"},{default:o(()=>[_(h(s.$t("clear")),1)]),_:1})])]),_:1},8,["label"]),(f(!0),y(M,null,x(((W=e(a).conf)==null?void 0:W.all_custom_tags)??[],n=>(f(),k(c,{label:s.$t("toggleTagSelection",{tag:n.name}),key:n.id},{default:o(()=>[p("div",ke,[t(U,{value:e(a).shortcut[`toggle_tag_${n.name}`],onKeydown:T(b=>m(b,`toggle_tag_${n.name}`),["stop","prevent"]),placeholder:s.$t("shortcutKeyDescription")},null,8,["value","onKeydown","placeholder"]),t(S,{onClick:b=>e(a).shortcut[`toggle_tag_${n.name}`]="",class:"clear-btn"},{default:o(()=>[_(h(s.$t("clear")),1)]),_:2},1032,["onClick"])])]),_:2},1032,["label"]))),128)),e(ee)?(f(),y(M,{key:0},[p("h2",null,h(e(i)("clientSpecificSettings")),1),t(c,null,{default:o(()=>[p("div",ve,[t(S,{onClick:g,class:"clear-btn"},{default:o(()=>[_(h(s.$t("initiateSoftwareStartupConfig")),1)]),_:1})])]),_:1})],64)):v("",!0)]}),_:1})])}}});const Te=de(we,[["__scopeId","data-v-8a6b0808"]]);export{Te as default}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/gridView-b870c73c.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/gridView-b870c73c.js new file mode 100644 index 0000000000000000000000000000000000000000..9f0ae80b51e7d022baa249283b0d440028040b81 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/gridView-b870c73c.js @@ -0,0 +1 @@ +import{u as w,a as y,F as k,d as x}from"./FileItem-9a94f7dd.js";import{d as b,$ as F,cf as h,r as D,bc as I,bg as C,S as E,T as S,c,a1 as V,a2 as e,ad as z,b$ as B,cA as $,Z as A}from"./index-93a218ca.js";import"./functionalCallableComp-685da399.js";/* empty css */import"./index-f60e0de3.js";const R=b({__name:"gridView",props:{tabIdx:{},paneIdx:{},id:{},removable:{type:Boolean},allowDragAndDrop:{type:Boolean},files:{},paneKey:{}},setup(p){const o=p,d=F(),{stackViewEl:m}=w().toRefs(),{itemSize:i,gridItems:u,cellWidth:f}=y(),g=h(),a=D(o.files??[]),_=async s=>{const l=B(s);o.allowDragAndDrop&&l&&(a.value=$([...a.value,...l.nodes]))},v=s=>{a.value.splice(s,1)};return I(()=>{d.pageFuncExportMap.set(o.paneKey,{getFiles:()=>C(a.value),setFiles:s=>a.value=s})}),(s,l)=>(E(),S("div",{class:"container",ref_key:"stackViewEl",ref:m,onDrop:_},[c(e(x),{ref:"scroller",class:"file-list",items:a.value.slice(),"item-size":e(i).first,"key-field":"fullpath","item-secondary-size":e(i).second,gridItems:e(u)},{default:V(({item:t,index:r})=>{var n;return[c(k,{idx:r,file:t,"cell-width":e(f),"enable-close-icon":o.removable,onCloseIconClick:T=>v(r),"full-screen-preview-image-url":e(z)(t),"extra-tags":(n=t==null?void 0:t.tags)==null?void 0:n.map(e(g).tagConvert),"enable-right-click-menu":!1},null,8,["idx","file","cell-width","enable-close-icon","onCloseIconClick","full-screen-preview-image-url","extra-tags"])]}),_:1},8,["items","item-size","item-secondary-size","gridItems"])],544))}});const N=A(R,[["__scopeId","data-v-f35f4802"]]);export{N as default}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/gridView-eef9ac55.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/gridView-eef9ac55.css new file mode 100644 index 0000000000000000000000000000000000000000..01ed9af0c16bea2c202f2c82067b76871597ba24 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/gridView-eef9ac55.css @@ -0,0 +1 @@ +.container[data-v-f35f4802]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column}.container .actions-panel[data-v-f35f4802]{padding:8px;background-color:var(--zp-primary-background)}.container .file-list[data-v-f35f4802]{flex:1;list-style:none;padding:8px;height:var(--pane-max-height);width:100%}.container .file-list .hint[data-v-f35f4802]{text-align:center;font-size:2em;padding:30vh 128px 0} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/hook-983826b5.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/hook-983826b5.js new file mode 100644 index 0000000000000000000000000000000000000000..82a1fadabbe9148c678cc0ca58065268028ae4eb --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/hook-983826b5.js @@ -0,0 +1 @@ +import{ak as k,r as g,l as R,k as b,L as A,E as q,bN as G,bp as L,bX as z}from"./index-93a218ca.js";import{u as O,a as Q,b as j,e as H}from"./FileItem-9a94f7dd.js";import{a as T,b as U,c as W,d as X}from"./MultiSelectKeep-e20b5f46.js";let B=0;const K=()=>++B,V=(n,i,{dataUpdateStrategy:l="replace"}={})=>{const a=k([""]),c=g(!1),t=g(),o=g(!1);let f=g(-1);const v=new Set,w=e=>{var s;l==="replace"?t.value=e:l==="merge"&&(A((Array.isArray(t.value)||typeof t.value>"u")&&Array.isArray(e),"数据更新策略为合并时仅可用于值为数组的情况"),t.value=[...(s=t==null?void 0:t.value)!==null&&s!==void 0?s:[],...e])},d=e=>b(void 0,void 0,void 0,function*(){if(o.value||c.value&&typeof e>"u")return!1;o.value=!0;const s=K();f.value=s;try{let r;if(typeof e=="number"){if(r=a[e],typeof r!="string")return!1}else r=a[a.length-1];const h=yield n(r);if(v.has(s))return v.delete(s),!1;w(i(h));const u=h.cursor;if((e===a.length-1||typeof e!="number")&&(c.value=!u.has_next,u.has_next)){const y=u.next_cursor||u.next;A(typeof y=="string"),a.push(y)}}finally{f.value===s&&(o.value=!1)}return!0}),m=()=>{v.add(f.value),o.value=!1},x=(e=!1)=>b(void 0,void 0,void 0,function*(){const{refetch:s,force:r}=typeof e=="object"?e:{refetch:e};r&&m(),A(!o.value),a.splice(0,a.length,""),o.value=!1,t.value=void 0,c.value=!1,s&&(yield d())}),I=()=>({next:()=>b(void 0,void 0,void 0,function*(){if(o.value)throw new Error("不允许同时迭代");return{done:!(yield d()),value:t.value}})});return R({abort:m,load:c,next:d,res:t,loading:o,cursorStack:a,reset:x,[Symbol.asyncIterator]:I,iter:{[Symbol.asyncIterator]:I}})},te=n=>k(V(n,i=>i.files,{dataUpdateStrategy:"merge"})),se=n=>{const i=k(new Set),l=q(()=>(n.res??[]).filter(p=>!i.has(p.fullpath))),a=G(),{stackViewEl:c,multiSelectedIdxs:t,stack:o,scroller:f,props:v}=O({images:l}).toRefs(),{itemSize:w,gridItems:d,cellWidth:m,onScroll:x}=Q({fetchNext:()=>n.next()}),{showMenuIdx:I}=j(),{onFileDragStart:e,onFileDragEnd:s}=T(),{showGenInfo:r,imageGenInfo:h,q:u,onContextMenuClick:y,onFileItemClick:C}=U({openNext:L}),{previewIdx:E,previewing:_,onPreviewVisibleChange:M,previewImgMove:N,canPreview:D}=W({loadNext:()=>n.next()}),J=async(p,S,P)=>{o.value=[{curr:"",files:l.value}],await y(p,S,P)};H("removeFiles",async({paths:p})=>{p.forEach(S=>i.add(S))});const F=()=>{z(l.value)};return{images:l,scroller:f,queue:a,iter:n,onContextMenuClickU:J,stackViewEl:c,previewIdx:E,previewing:_,onPreviewVisibleChange:M,previewImgMove:N,canPreview:D,itemSize:w,gridItems:d,showGenInfo:r,imageGenInfo:h,q:u,onContextMenuClick:y,onFileItemClick:C,showMenuIdx:I,multiSelectedIdxs:t,onFileDragStart:e,onFileDragEnd:s,cellWidth:m,onScroll:x,saveLoadedFileAsJson:F,saveAllFileAsJson:async()=>{for(;!n.load;)await n.next();F()},props:v,...X()}};export{te as c,se as u}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-1cc34316.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-1cc34316.css new file mode 100644 index 0000000000000000000000000000000000000000..a18fe4500bb471578a4224b2b1450a29e858ce3f --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-1cc34316.css @@ -0,0 +1 @@ +.ant-switch{margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;box-sizing:border-box;min-width:44px;height:22px;line-height:22px;vertical-align:middle;background-color:#00000040;border:0;border-radius:100px;cursor:pointer;transition:all .2s;user-select:none}.ant-switch:focus{outline:0;box-shadow:0 0 0 2px #0000001a}.ant-switch-checked:focus{box-shadow:0 0 0 2px #fff1e6}.ant-switch:focus:hover{box-shadow:none}.ant-switch-checked{background-color:#d03f0a}.ant-switch-loading,.ant-switch-disabled{cursor:not-allowed;opacity:.4}.ant-switch-loading *,.ant-switch-disabled *{box-shadow:none;cursor:not-allowed}.ant-switch-inner{display:block;margin:0 7px 0 25px;color:#fff;font-size:12px;transition:margin .2s}.ant-switch-checked .ant-switch-inner{margin:0 25px 0 7px}.ant-switch-handle{position:absolute;top:2px;left:2px;width:18px;height:18px;transition:all .2s ease-in-out}.ant-switch-handle:before{position:absolute;top:0;right:0;bottom:0;left:0;background-color:#fff;border-radius:9px;box-shadow:0 2px 4px #00230b33;transition:all .2s ease-in-out;content:""}.ant-switch-checked .ant-switch-handle{left:calc(100% - 20px)}.ant-switch:not(.ant-switch-disabled):active .ant-switch-handle:before{right:-30%;left:0}.ant-switch:not(.ant-switch-disabled):active.ant-switch-checked .ant-switch-handle:before{right:0;left:-30%}.ant-switch-loading-icon.anticon{position:relative;top:2px;color:#000000a6;vertical-align:top}.ant-switch-checked .ant-switch-loading-icon{color:#d03f0a}.ant-switch-small{min-width:28px;height:16px;line-height:16px}.ant-switch-small .ant-switch-inner{margin:0 5px 0 18px;font-size:12px}.ant-switch-small .ant-switch-handle{width:12px;height:12px}.ant-switch-small .ant-switch-loading-icon{top:1.5px;font-size:9px}.ant-switch-small.ant-switch-checked .ant-switch-inner{margin:0 18px 0 5px}.ant-switch-small.ant-switch-checked .ant-switch-handle{left:calc(100% - 14px)}.ant-switch-rtl{direction:rtl}.ant-switch-rtl .ant-switch-inner{margin:0 25px 0 7px}.ant-switch-rtl .ant-switch-handle{right:2px;left:auto}.ant-switch-rtl:not(.ant-switch-rtl-disabled):active .ant-switch-handle:before{right:0;left:-30%}.ant-switch-rtl:not(.ant-switch-rtl-disabled):active.ant-switch-checked .ant-switch-handle:before{right:-30%;left:0}.ant-switch-rtl.ant-switch-checked .ant-switch-inner{margin:0 7px 0 25px}.ant-switch-rtl.ant-switch-checked .ant-switch-handle{right:calc(100% - 20px)}.ant-switch-rtl.ant-switch-small.ant-switch-checked .ant-switch-handle{right:calc(100% - 14px)} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-41e4fe63.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-41e4fe63.css new file mode 100644 index 0000000000000000000000000000000000000000..56d6883118b2520a1eb80e58ac5598c9aee5b16d --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-41e4fe63.css @@ -0,0 +1 @@ +.ant-spin{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;display:none;color:#d03f0a;text-align:center;vertical-align:middle;opacity:0;transition:transform .3s cubic-bezier(.78,.14,.15,.86)}.ant-spin-spinning{position:static;display:inline-block;opacity:1}.ant-spin-nested-loading{position:relative}.ant-spin-nested-loading>div>.ant-spin{position:absolute;top:0;left:0;z-index:4;display:block;width:100%;height:100%;max-height:400px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-dot{position:absolute;top:50%;left:50%;margin:-10px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-text{position:absolute;top:50%;width:100%;padding-top:5px;text-shadow:0 1px 2px #fff}.ant-spin-nested-loading>div>.ant-spin.ant-spin-show-text .ant-spin-dot{margin-top:-20px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-dot{margin:-7px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-text{padding-top:2px}.ant-spin-nested-loading>div>.ant-spin-sm.ant-spin-show-text .ant-spin-dot{margin-top:-17px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-dot{margin:-16px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-text{padding-top:11px}.ant-spin-nested-loading>div>.ant-spin-lg.ant-spin-show-text .ant-spin-dot{margin-top:-26px}.ant-spin-container{position:relative;transition:opacity .3s}.ant-spin-container:after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;display:none \ ;width:100%;height:100%;background:#fff;opacity:0;transition:all .3s;content:"";pointer-events:none}.ant-spin-blur{clear:both;opacity:.5;user-select:none;pointer-events:none}.ant-spin-blur:after{opacity:.4;pointer-events:auto}.ant-spin-tip{color:#00000073}.ant-spin-dot{position:relative;display:inline-block;font-size:20px;width:1em;height:1em}.ant-spin-dot-item{position:absolute;display:block;width:9px;height:9px;background-color:#d03f0a;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.ant-spin-dot-item:nth-child(1){top:0;left:0}.ant-spin-dot-item:nth-child(2){top:0;right:0;animation-delay:.4s}.ant-spin-dot-item:nth-child(3){right:0;bottom:0;animation-delay:.8s}.ant-spin-dot-item:nth-child(4){bottom:0;left:0;animation-delay:1.2s}.ant-spin-dot-spin{transform:rotate(45deg);animation:antRotate 1.2s infinite linear}.ant-spin-sm .ant-spin-dot{font-size:14px}.ant-spin-sm .ant-spin-dot i{width:6px;height:6px}.ant-spin-lg .ant-spin-dot{font-size:32px}.ant-spin-lg .ant-spin-dot i{width:14px;height:14px}.ant-spin.ant-spin-show-text .ant-spin-text{display:block}@media all and (-ms-high-contrast: none),(-ms-high-contrast: active){.ant-spin-blur{background:#fff;opacity:.5}}@keyframes antSpinMove{to{opacity:1}}@keyframes antRotate{to{transform:rotate(405deg)}}.ant-spin-rtl{direction:rtl}.ant-spin-rtl .ant-spin-dot-spin{transform:rotate(-45deg);animation-name:antRotateRtl}@keyframes antRotateRtl{to{transform:rotate(-405deg)}} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-4596f42d.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-4596f42d.js new file mode 100644 index 0000000000000000000000000000000000000000..df6f7eb5125a1f74d7626b4ed9370d8df465e3c4 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-4596f42d.js @@ -0,0 +1 @@ +import{d as q,bB as H,r as w,m as A,_ as L,a as i,al as G,h as f,c as h,P as c,cn as J,aw as Q,j as X,co as Y,w as j,E as D,u as Z,o as ee,ax as ne,au as ae,ba as te,g as I,cp as ce,cq as M}from"./index-93a218ca.js";var ue=["prefixCls","name","id","type","disabled","readonly","tabindex","autofocus","value","required"],le={prefixCls:String,name:String,id:String,type:String,defaultChecked:{type:[Boolean,Number],default:void 0},checked:{type:[Boolean,Number],default:void 0},disabled:Boolean,tabindex:{type:[Number,String]},readonly:Boolean,autofocus:Boolean,value:c.any,required:Boolean};const se=q({compatConfig:{MODE:3},name:"Checkbox",inheritAttrs:!1,props:H(le,{prefixCls:"rc-checkbox",type:"checkbox",defaultChecked:!1}),emits:["click","change"],setup:function(e,s){var l=s.attrs,C=s.emit,p=s.expose,o=w(e.checked===void 0?e.defaultChecked:e.checked),v=w();A(function(){return e.checked},function(){o.value=e.checked}),p({focus:function(){var a;(a=v.value)===null||a===void 0||a.focus()},blur:function(){var a;(a=v.value)===null||a===void 0||a.blur()}});var k=w(),b=function(a){if(!e.disabled){e.checked===void 0&&(o.value=a.target.checked),a.shiftKey=k.value;var g={target:i(i({},e),{},{checked:a.target.checked}),stopPropagation:function(){a.stopPropagation()},preventDefault:function(){a.preventDefault()},nativeEvent:a};e.checked!==void 0&&(v.value.checked=!!e.checked),C("change",g),k.value=!1}},y=function(a){C("click",a),k.value=a.shiftKey};return function(){var u,a=e.prefixCls,g=e.name,r=e.id,S=e.type,B=e.disabled,m=e.readonly,_=e.tabindex,K=e.autofocus,F=e.value,N=e.required,P=L(e,ue),t=l.class,n=l.onFocus,d=l.onBlur,E=l.onKeydown,$=l.onKeypress,z=l.onKeyup,O=i(i({},P),l),R=Object.keys(O).reduce(function(T,x){return(x.substr(0,5)==="aria-"||x.substr(0,5)==="data-"||x==="role")&&(T[x]=O[x]),T},{}),U=G(a,t,(u={},f(u,"".concat(a,"-checked"),o.value),f(u,"".concat(a,"-disabled"),B),u)),W=i(i({name:g,id:r,type:S,readonly:m,disabled:B,tabindex:_,class:"".concat(a,"-input"),checked:!!o.value,autofocus:K,value:F},R),{},{onChange:b,onClick:y,onFocus:n,onBlur:d,onKeydown:E,onKeypress:$,onKeyup:z,required:N});return h("span",{class:U},[h("input",i({ref:v},W),null),h("span",{class:"".concat(a,"-inner")},null)])}}});var oe=Q("small","default"),de=function(){return{id:String,prefixCls:String,size:c.oneOf(oe),disabled:{type:Boolean,default:void 0},checkedChildren:c.any,unCheckedChildren:c.any,tabindex:c.oneOfType([c.string,c.number]),autofocus:{type:Boolean,default:void 0},loading:{type:Boolean,default:void 0},checked:c.oneOfType([c.string,c.number,c.looseBool]),checkedValue:c.oneOfType([c.string,c.number,c.looseBool]).def(!0),unCheckedValue:c.oneOfType([c.string,c.number,c.looseBool]).def(!1),onChange:{type:Function},onClick:{type:Function},onKeydown:{type:Function},onMouseup:{type:Function},"onUpdate:checked":{type:Function},onBlur:Function,onFocus:Function}},ie=q({compatConfig:{MODE:3},name:"ASwitch",__ANT_SWITCH:!0,inheritAttrs:!1,props:de(),slots:["checkedChildren","unCheckedChildren"],setup:function(e,s){var l=s.attrs,C=s.slots,p=s.expose,o=s.emit,v=X();Y(function(){j(!("defaultChecked"in l),"Switch","'defaultChecked' is deprecated, please use 'v-model:checked'"),j(!("value"in l),"Switch","`value` is not validate prop, do you mean `checked`?")});var k=w(e.checked!==void 0?e.checked:l.defaultChecked),b=D(function(){return k.value===e.checkedValue});A(function(){return e.checked},function(){k.value=e.checked});var y=Z("switch",e),u=y.prefixCls,a=y.direction,g=y.size,r=w(),S=function(){var n;(n=r.value)===null||n===void 0||n.focus()},B=function(){var n;(n=r.value)===null||n===void 0||n.blur()};p({focus:S,blur:B}),ee(function(){ne(function(){e.autofocus&&!e.disabled&&r.value.focus()})});var m=function(n,d){e.disabled||(o("update:checked",n),o("change",n,d),v.onFieldChange())},_=function(n){o("blur",n)},K=function(n){S();var d=b.value?e.unCheckedValue:e.checkedValue;m(d,n),o("click",d,n)},F=function(n){n.keyCode===M.LEFT?m(e.unCheckedValue,n):n.keyCode===M.RIGHT&&m(e.checkedValue,n),o("keydown",n)},N=function(n){var d;(d=r.value)===null||d===void 0||d.blur(),o("mouseup",n)},P=D(function(){var t;return t={},f(t,"".concat(u.value,"-small"),g.value==="small"),f(t,"".concat(u.value,"-loading"),e.loading),f(t,"".concat(u.value,"-checked"),b.value),f(t,"".concat(u.value,"-disabled"),e.disabled),f(t,u.value,!0),f(t,"".concat(u.value,"-rtl"),a.value==="rtl"),t});return function(){var t;return h(ce,{insertExtraNode:!0},{default:function(){return[h("button",i(i(i({},ae(e,["prefixCls","checkedChildren","unCheckedChildren","checked","autofocus","checkedValue","unCheckedValue","id","onChange","onUpdate:checked"])),l),{},{id:(t=e.id)!==null&&t!==void 0?t:v.id.value,onKeydown:F,onClick:K,onBlur:_,onMouseup:N,type:"button",role:"switch","aria-checked":k.value,disabled:e.disabled||e.loading,class:[l.class,P.value],ref:r}),[h("div",{class:"".concat(u.value,"-handle")},[e.loading?h(te,{class:"".concat(u.value,"-loading-icon")},null):null]),h("span",{class:"".concat(u.value,"-inner")},[b.value?I(C,e,"checkedChildren"):I(C,e,"unCheckedChildren")])])]}})}}});const fe=J(ie);export{se as V,fe as _}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-67a7e5e1.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-67a7e5e1.js new file mode 100644 index 0000000000000000000000000000000000000000..1d5611932715cbdf21a9d7e2661cce01515988c5 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-67a7e5e1.js @@ -0,0 +1 @@ +import{r as F,o as P,cr as K,at as L,E as i,av as T,aw as I,d as E,u as $,cs as _,b as y,bf as V,ct as A,al as B,h as c,c as M,a as G}from"./index-93a218ca.js";const W=function(){var o=F(!1);return P(function(){o.value=K()}),o};var D=Symbol("rowContextKey"),U=function(r){T(D,r)},k=function(){return L(D,{gutter:i(function(){}),wrap:i(function(){}),supportFlexGap:i(function(){})})};I("top","middle","bottom","stretch");I("start","end","center","space-around","space-between");var q=function(){return{align:String,justify:String,prefixCls:String,gutter:{type:[Number,Array,Object],default:0},wrap:{type:Boolean,default:void 0}}},H=E({compatConfig:{MODE:3},name:"ARow",props:q(),setup:function(r,N){var m=N.slots,v=$("row",r),d=v.prefixCls,h=v.direction,j,x=F({xs:!0,sm:!0,md:!0,lg:!0,xl:!0,xxl:!0,xxxl:!0}),w=W();P(function(){j=_.subscribe(function(e){var t=r.gutter||0;(!Array.isArray(t)&&y(t)==="object"||Array.isArray(t)&&(y(t[0])==="object"||y(t[1])==="object"))&&(x.value=e)})}),V(function(){_.unsubscribe(j)});var S=i(function(){var e=[0,0],t=r.gutter,n=t===void 0?0:t,s=Array.isArray(n)?n:[n,0];return s.forEach(function(l,b){if(y(l)==="object")for(var a=0;a0?"".concat(e[0]/-2,"px"):void 0,s=e[1]>0?"".concat(e[1]/-2,"px"):void 0;return n&&(t.marginLeft=n,t.marginRight=n),w.value?t.rowGap="".concat(e[1],"px"):s&&(t.marginTop=s,t.marginBottom=s),t});return function(){var e;return M("div",{class:R.value,style:O.value},[(e=m.default)===null||e===void 0?void 0:e.call(m)])}}});const Y=H;function J(o){return typeof o=="number"?"".concat(o," ").concat(o," auto"):/^\d+(\.\d+)?(px|em|rem|%)$/.test(o)?"0 0 ".concat(o):o}var Q=function(){return{span:[String,Number],order:[String,Number],offset:[String,Number],push:[String,Number],pull:[String,Number],xs:{type:[String,Number,Object],default:void 0},sm:{type:[String,Number,Object],default:void 0},md:{type:[String,Number,Object],default:void 0},lg:{type:[String,Number,Object],default:void 0},xl:{type:[String,Number,Object],default:void 0},xxl:{type:[String,Number,Object],default:void 0},xxxl:{type:[String,Number,Object],default:void 0},prefixCls:String,flex:[String,Number]}};const Z=E({compatConfig:{MODE:3},name:"ACol",props:Q(),setup:function(r,N){var m=N.slots,v=k(),d=v.gutter,h=v.supportFlexGap,j=v.wrap,x=$("col",r),w=x.prefixCls,S=x.direction,R=i(function(){var e,t=r.span,n=r.order,s=r.offset,l=r.push,b=r.pull,a=w.value,p={};return["xs","sm","md","lg","xl","xxl","xxxl"].forEach(function(g){var f,u={},C=r[g];typeof C=="number"?u.span=C:y(C)==="object"&&(u=C||{}),p=G(G({},p),{},(f={},c(f,"".concat(a,"-").concat(g,"-").concat(u.span),u.span!==void 0),c(f,"".concat(a,"-").concat(g,"-order-").concat(u.order),u.order||u.order===0),c(f,"".concat(a,"-").concat(g,"-offset-").concat(u.offset),u.offset||u.offset===0),c(f,"".concat(a,"-").concat(g,"-push-").concat(u.push),u.push||u.push===0),c(f,"".concat(a,"-").concat(g,"-pull-").concat(u.pull),u.pull||u.pull===0),c(f,"".concat(a,"-rtl"),S.value==="rtl"),f))}),B(a,(e={},c(e,"".concat(a,"-").concat(t),t!==void 0),c(e,"".concat(a,"-order-").concat(n),n),c(e,"".concat(a,"-offset-").concat(s),s),c(e,"".concat(a,"-push-").concat(l),l),c(e,"".concat(a,"-pull-").concat(b),b),e),p)}),O=i(function(){var e=r.flex,t=d.value,n={};if(t&&t[0]>0){var s="".concat(t[0]/2,"px");n.paddingLeft=s,n.paddingRight=s}if(t&&t[1]>0&&!h.value){var l="".concat(t[1]/2,"px");n.paddingTop=l,n.paddingBottom=l}return e&&(n.flex=J(e),j.value===!1&&!n.minWidth&&(n.minWidth=0)),n});return function(){var e;return M("div",{class:R.value,style:O.value},[(e=m.default)===null||e===void 0?void 0:e.call(m)])}}});export{Z as C,Y as R}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-80432a0c.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-80432a0c.css new file mode 100644 index 0000000000000000000000000000000000000000..5d7b2667dbb799d218af77ca7ac5d8579a884a48 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-80432a0c.css @@ -0,0 +1 @@ +.ant-dropdown-menu-item.ant-dropdown-menu-item-danger{color:#ff4d4f}.ant-dropdown-menu-item.ant-dropdown-menu-item-danger:hover{color:#fff;background-color:#ff4d4f}.ant-dropdown{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;display:block}.ant-dropdown:before{position:absolute;top:-4px;right:0;bottom:-4px;left:-7px;z-index:-9999;opacity:.0001;content:" "}.ant-dropdown-wrap{position:relative}.ant-dropdown-wrap .ant-btn>.anticon-down{font-size:10px}.ant-dropdown-wrap .anticon-down:before{transition:transform .2s}.ant-dropdown-wrap-open .anticon-down:before{transform:rotate(180deg)}.ant-dropdown-hidden,.ant-dropdown-menu-hidden,.ant-dropdown-menu-submenu-hidden{display:none}.ant-dropdown-show-arrow.ant-dropdown-placement-topCenter,.ant-dropdown-show-arrow.ant-dropdown-placement-topLeft,.ant-dropdown-show-arrow.ant-dropdown-placement-topRight{padding-bottom:10px}.ant-dropdown-show-arrow.ant-dropdown-placement-bottomCenter,.ant-dropdown-show-arrow.ant-dropdown-placement-bottomLeft,.ant-dropdown-show-arrow.ant-dropdown-placement-bottomRight{padding-top:10px}.ant-dropdown-arrow{position:absolute;z-index:1;display:block;width:8.48528137px;height:8.48528137px;background:transparent;border-style:solid;border-width:4.24264069px;transform:rotate(45deg)}.ant-dropdown-placement-topCenter>.ant-dropdown-arrow,.ant-dropdown-placement-topLeft>.ant-dropdown-arrow,.ant-dropdown-placement-topRight>.ant-dropdown-arrow{bottom:6.2px;border-color:transparent #fff #fff transparent;box-shadow:3px 3px 7px #00000012}.ant-dropdown-placement-topCenter>.ant-dropdown-arrow{left:50%;transform:translate(-50%) rotate(45deg)}.ant-dropdown-placement-topLeft>.ant-dropdown-arrow{left:16px}.ant-dropdown-placement-topRight>.ant-dropdown-arrow{right:16px}.ant-dropdown-placement-bottomCenter>.ant-dropdown-arrow,.ant-dropdown-placement-bottomLeft>.ant-dropdown-arrow,.ant-dropdown-placement-bottomRight>.ant-dropdown-arrow{top:6px;border-color:#fff transparent transparent #fff;box-shadow:-2px -2px 5px #0000000f}.ant-dropdown-placement-bottomCenter>.ant-dropdown-arrow{left:50%;transform:translate(-50%) rotate(45deg)}.ant-dropdown-placement-bottomLeft>.ant-dropdown-arrow{left:16px}.ant-dropdown-placement-bottomRight>.ant-dropdown-arrow{right:16px}.ant-dropdown-menu{position:relative;margin:0;padding:4px 0;text-align:left;list-style-type:none;background-color:#fff;background-clip:padding-box;border-radius:2px;outline:none;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d}.ant-dropdown-menu-item-group-title{padding:5px 12px;color:#00000073;transition:all .3s}.ant-dropdown-menu-submenu-popup{position:absolute;z-index:1050;background:transparent;box-shadow:none;transform-origin:0 0}.ant-dropdown-menu-submenu-popup ul,.ant-dropdown-menu-submenu-popup li{list-style:none}.ant-dropdown-menu-submenu-popup ul{margin-right:.3em;margin-left:.3em}.ant-dropdown-menu-item{position:relative;display:flex;align-items:center}.ant-dropdown-menu-item-icon{min-width:12px;margin-right:8px;font-size:12px}.ant-dropdown-menu-title-content{flex:auto;white-space:nowrap}.ant-dropdown-menu-title-content>a{color:inherit;transition:all .3s}.ant-dropdown-menu-title-content>a:hover{color:inherit}.ant-dropdown-menu-title-content>a:after{position:absolute;top:0;right:0;bottom:0;left:0;content:""}.ant-dropdown-menu-item,.ant-dropdown-menu-submenu-title{clear:both;margin:0;padding:5px 12px;color:#000000d9;font-weight:400;font-size:14px;line-height:22px;cursor:pointer;transition:all .3s}.ant-dropdown-menu-item-selected,.ant-dropdown-menu-submenu-title-selected{color:#d03f0a;background-color:#fff1e6}.ant-dropdown-menu-item:hover,.ant-dropdown-menu-submenu-title:hover{background-color:#f5f5f5}.ant-dropdown-menu-item-disabled,.ant-dropdown-menu-submenu-title-disabled{color:#00000040;cursor:not-allowed}.ant-dropdown-menu-item-disabled:hover,.ant-dropdown-menu-submenu-title-disabled:hover{color:#00000040;background-color:#fff;cursor:not-allowed}.ant-dropdown-menu-item-disabled a,.ant-dropdown-menu-submenu-title-disabled a{pointer-events:none}.ant-dropdown-menu-item-divider,.ant-dropdown-menu-submenu-title-divider{height:1px;margin:4px 0;overflow:hidden;line-height:0;background-color:#f0f0f0}.ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon,.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon{position:absolute;right:8px}.ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon,.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon{margin-right:0!important;color:#00000073;font-size:10px;font-style:normal}.ant-dropdown-menu-item-group-list{margin:0 8px;padding:0;list-style:none}.ant-dropdown-menu-submenu-title{padding-right:24px}.ant-dropdown-menu-submenu-vertical{position:relative}.ant-dropdown-menu-submenu-vertical>.ant-dropdown-menu{position:absolute;top:0;left:100%;min-width:100%;margin-left:4px;transform-origin:0 0}.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title,.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow-icon{color:#00000040;background-color:#fff;cursor:not-allowed}.ant-dropdown-menu-submenu-selected .ant-dropdown-menu-submenu-title{color:#d03f0a}.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomRight,.ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomRight{animation-name:antSlideUpIn}.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topLeft,.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topLeft,.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topCenter,.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topCenter,.ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topRight,.ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topRight{animation-name:antSlideDownIn}.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomRight{animation-name:antSlideUpOut}.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topLeft,.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topCenter,.ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topRight{animation-name:antSlideDownOut}.ant-dropdown-trigger>.anticon.anticon-down,.ant-dropdown-link>.anticon.anticon-down,.ant-dropdown-button>.anticon.anticon-down{font-size:10px;vertical-align:baseline}.ant-dropdown-button{white-space:nowrap}.ant-dropdown-button.ant-btn-group>.ant-btn-loading,.ant-dropdown-button.ant-btn-group>.ant-btn-loading+.ant-btn{cursor:default;pointer-events:none}.ant-dropdown-button.ant-btn-group>.ant-btn-loading+.ant-btn:before{display:block}.ant-dropdown-button.ant-btn-group>.ant-btn:last-child:not(:first-child):not(.ant-btn-icon-only){padding-right:8px;padding-left:8px}.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu{background:#001529}.ant-dropdown-menu-dark .ant-dropdown-menu-item,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a,.ant-dropdown-menu-dark .ant-dropdown-menu-item>.anticon+span>a{color:#ffffffa6}.ant-dropdown-menu-dark .ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-item>.anticon+span>a .ant-dropdown-menu-submenu-arrow:after{color:#ffffffa6}.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item>.anticon+span>a:hover{color:#fff;background:transparent}.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected,.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected>a{color:#fff;background:#d03f0a}.ant-dropdown-rtl{direction:rtl}.ant-dropdown-rtl.ant-dropdown:before{right:-7px;left:0}.ant-dropdown-menu.ant-dropdown-menu-rtl,.ant-dropdown-rtl .ant-dropdown-menu-item-group-title,.ant-dropdown-menu-submenu-rtl .ant-dropdown-menu-item-group-title{direction:rtl;text-align:right}.ant-dropdown-menu-submenu-popup.ant-dropdown-menu-submenu-rtl{transform-origin:100% 0}.ant-dropdown-rtl .ant-dropdown-menu-submenu-popup ul,.ant-dropdown-rtl .ant-dropdown-menu-submenu-popup li,.ant-dropdown-rtl .ant-dropdown-menu-item,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title{text-align:right}.ant-dropdown-rtl .ant-dropdown-menu-item>.anticon:first-child,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title>.anticon:first-child,.ant-dropdown-rtl .ant-dropdown-menu-item>span>.anticon:first-child,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title>span>.anticon:first-child{margin-right:0;margin-left:8px}.ant-dropdown-rtl .ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon{right:auto;left:8px}.ant-dropdown-rtl .ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon,.ant-dropdown-rtl .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon{margin-left:0!important;transform:scaleX(-1)}.ant-dropdown-rtl .ant-dropdown-menu-submenu-title{padding-right:12px;padding-left:24px}.ant-dropdown-rtl .ant-dropdown-menu-submenu-vertical>.ant-dropdown-menu{right:100%;left:0;margin-right:4px;margin-left:0}.ant-menu-item-danger.ant-menu-item,.ant-menu-item-danger.ant-menu-item:hover,.ant-menu-item-danger.ant-menu-item-active{color:#ff4d4f}.ant-menu-item-danger.ant-menu-item:active{background:#fff1f0}.ant-menu-item-danger.ant-menu-item-selected{color:#ff4d4f}.ant-menu-item-danger.ant-menu-item-selected>a,.ant-menu-item-danger.ant-menu-item-selected>a:hover{color:#ff4d4f}.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-danger.ant-menu-item-selected{background-color:#fff1f0}.ant-menu-inline .ant-menu-item-danger.ant-menu-item:after{border-right-color:#ff4d4f}.ant-menu-dark .ant-menu-item-danger.ant-menu-item,.ant-menu-dark .ant-menu-item-danger.ant-menu-item:hover,.ant-menu-dark .ant-menu-item-danger.ant-menu-item>a{color:#ff4d4f}.ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-danger.ant-menu-item-selected{color:#fff;background-color:#ff4d4f}.ant-menu{box-sizing:border-box;margin:0;font-variant:tabular-nums;line-height:1.5715;font-feature-settings:"tnum";padding:0;color:#000000d9;font-size:14px;line-height:0;text-align:left;list-style:none;background:#fff;outline:none;box-shadow:0 3px 6px -4px #0000001f,0 6px 16px #00000014,0 9px 28px 8px #0000000d;transition:background .3s,width .3s cubic-bezier(.2,0,0,1) 0s}.ant-menu:before{display:table;content:""}.ant-menu:after{display:table;clear:both;content:""}.ant-menu.ant-menu-root:focus-visible{box-shadow:0 0 0 2px #ffd0b0}.ant-menu ul,.ant-menu ol{margin:0;padding:0;list-style:none}.ant-menu-overflow{display:flex}.ant-menu-overflow-item{flex:none}.ant-menu-hidden,.ant-menu-submenu-hidden{display:none}.ant-menu-item-group-title{height:1.5715;padding:8px 16px;color:#00000073;font-size:14px;line-height:1.5715;transition:all .3s}.ant-menu-horizontal .ant-menu-submenu{transition:border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu,.ant-menu-submenu-inline{transition:border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1),padding .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-selected{color:#d03f0a}.ant-menu-item:active,.ant-menu-submenu-title:active{background:#fff1e6}.ant-menu-submenu .ant-menu-sub{cursor:initial;transition:background .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-title-content{transition:color .3s}.ant-menu-item a{color:#000000d9}.ant-menu-item a:hover{color:#d03f0a}.ant-menu-item a:before{position:absolute;top:0;right:0;bottom:0;left:0;background-color:transparent;content:""}.ant-menu-item>.ant-badge a{color:#000000d9}.ant-menu-item>.ant-badge a:hover{color:#d03f0a}.ant-menu-item-divider{overflow:hidden;line-height:0;border-color:#f0f0f0;border-style:solid;border-width:1px 0 0}.ant-menu-item-divider-dashed{border-style:dashed}.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu{margin-top:-1px}.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-submenu .ant-menu-submenu-title:hover{background-color:transparent}.ant-menu-item-selected,.ant-menu-item-selected a,.ant-menu-item-selected a:hover{color:#d03f0a}.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{background-color:#fff1e6}.ant-menu-inline,.ant-menu-vertical,.ant-menu-vertical-left{border-right:1px solid #f0f0f0}.ant-menu-vertical-right{border-left:1px solid #f0f0f0}.ant-menu-vertical.ant-menu-sub,.ant-menu-vertical-left.ant-menu-sub,.ant-menu-vertical-right.ant-menu-sub{min-width:160px;max-height:calc(100vh - 100px);padding:0;overflow:hidden;border-right:0}.ant-menu-vertical.ant-menu-sub:not([class*="-active"]),.ant-menu-vertical-left.ant-menu-sub:not([class*="-active"]),.ant-menu-vertical-right.ant-menu-sub:not([class*="-active"]){overflow-x:hidden;overflow-y:auto}.ant-menu-vertical.ant-menu-sub .ant-menu-item,.ant-menu-vertical-left.ant-menu-sub .ant-menu-item,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item{left:0;margin-left:0;border-right:0}.ant-menu-vertical.ant-menu-sub .ant-menu-item:after,.ant-menu-vertical-left.ant-menu-sub .ant-menu-item:after,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item:after{border-right:0}.ant-menu-vertical.ant-menu-sub>.ant-menu-item,.ant-menu-vertical-left.ant-menu-sub>.ant-menu-item,.ant-menu-vertical-right.ant-menu-sub>.ant-menu-item,.ant-menu-vertical.ant-menu-sub>.ant-menu-submenu,.ant-menu-vertical-left.ant-menu-sub>.ant-menu-submenu,.ant-menu-vertical-right.ant-menu-sub>.ant-menu-submenu{transform-origin:0 0}.ant-menu-horizontal.ant-menu-sub{min-width:114px}.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu-title{transition:border-color .3s,background .3s}.ant-menu-item,.ant-menu-submenu-title{position:relative;display:block;margin:0;padding:0 20px;white-space:nowrap;cursor:pointer;transition:border-color .3s,background .3s,padding .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-item .ant-menu-item-icon,.ant-menu-submenu-title .ant-menu-item-icon,.ant-menu-item .anticon,.ant-menu-submenu-title .anticon{min-width:14px;font-size:14px;transition:font-size .15s cubic-bezier(.215,.61,.355,1),margin .3s cubic-bezier(.645,.045,.355,1),color .3s}.ant-menu-item .ant-menu-item-icon+span,.ant-menu-submenu-title .ant-menu-item-icon+span,.ant-menu-item .anticon+span,.ant-menu-submenu-title .anticon+span{margin-left:10px;opacity:1;transition:opacity .3s cubic-bezier(.645,.045,.355,1),margin .3s,color .3s}.ant-menu-item .ant-menu-item-icon.svg,.ant-menu-submenu-title .ant-menu-item-icon.svg{vertical-align:-.125em}.ant-menu-item.ant-menu-item-only-child>.anticon,.ant-menu-submenu-title.ant-menu-item-only-child>.anticon,.ant-menu-item.ant-menu-item-only-child>.ant-menu-item-icon,.ant-menu-submenu-title.ant-menu-item-only-child>.ant-menu-item-icon{margin-right:0}.ant-menu-item:focus-visible,.ant-menu-submenu-title:focus-visible{box-shadow:0 0 0 2px #ffd0b0}.ant-menu>.ant-menu-item-divider{margin:1px 0;padding:0}.ant-menu-submenu-popup{position:absolute;z-index:1050;background:transparent;border-radius:2px;box-shadow:none;transform-origin:0 0}.ant-menu-submenu-popup:before{position:absolute;top:-7px;right:0;bottom:0;left:0;z-index:-1;width:100%;height:100%;opacity:.0001;content:" "}.ant-menu-submenu-placement-rightTop:before{top:0;left:-7px}.ant-menu-submenu>.ant-menu{background-color:#fff;border-radius:2px}.ant-menu-submenu>.ant-menu-submenu-title:after{transition:transform .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-popup>.ant-menu{background-color:#fff}.ant-menu-submenu-expand-icon,.ant-menu-submenu-arrow{position:absolute;top:50%;right:16px;width:10px;color:#000000d9;transform:translateY(-50%);transition:transform .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-arrow:before,.ant-menu-submenu-arrow:after{position:absolute;width:6px;height:1.5px;background-color:currentcolor;border-radius:2px;transition:background .3s cubic-bezier(.645,.045,.355,1),transform .3s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1),color .3s cubic-bezier(.645,.045,.355,1);content:""}.ant-menu-submenu-arrow:before{transform:rotate(45deg) translateY(-2.5px)}.ant-menu-submenu-arrow:after{transform:rotate(-45deg) translateY(2.5px)}.ant-menu-submenu:hover>.ant-menu-submenu-title>.ant-menu-submenu-expand-icon,.ant-menu-submenu:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow{color:#d03f0a}.ant-menu-inline-collapsed .ant-menu-submenu-arrow:before,.ant-menu-submenu-inline .ant-menu-submenu-arrow:before{transform:rotate(-45deg) translate(2.5px)}.ant-menu-inline-collapsed .ant-menu-submenu-arrow:after,.ant-menu-submenu-inline .ant-menu-submenu-arrow:after{transform:rotate(45deg) translate(-2.5px)}.ant-menu-submenu-horizontal .ant-menu-submenu-arrow{display:none}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title>.ant-menu-submenu-arrow{transform:translateY(-2px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after{transform:rotate(-45deg) translate(-2.5px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{transform:rotate(45deg) translate(2.5px)}.ant-menu-vertical .ant-menu-submenu-selected,.ant-menu-vertical-left .ant-menu-submenu-selected,.ant-menu-vertical-right .ant-menu-submenu-selected{color:#d03f0a}.ant-menu-horizontal{line-height:46px;border:0;border-bottom:1px solid #f0f0f0;box-shadow:none}.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu{margin-top:-1px;margin-bottom:0;padding:0 20px}.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item:hover,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu:hover,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-active,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-active,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-open,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-selected{color:#d03f0a}.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item:hover:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu:hover:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-active:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-active:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-open:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-open:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-item-selected:after,.ant-menu-horizontal:not(.ant-menu-dark)>.ant-menu-submenu-selected:after{border-bottom:2px solid #d03f0a}.ant-menu-horizontal>.ant-menu-item,.ant-menu-horizontal>.ant-menu-submenu{position:relative;top:1px;display:inline-block;vertical-align:bottom}.ant-menu-horizontal>.ant-menu-item:after,.ant-menu-horizontal>.ant-menu-submenu:after{position:absolute;right:20px;bottom:0;left:20px;border-bottom:2px solid transparent;transition:border-color .3s cubic-bezier(.645,.045,.355,1);content:""}.ant-menu-horizontal>.ant-menu-submenu>.ant-menu-submenu-title{padding:0}.ant-menu-horizontal>.ant-menu-item a{color:#000000d9}.ant-menu-horizontal>.ant-menu-item a:hover{color:#d03f0a}.ant-menu-horizontal>.ant-menu-item a:before{bottom:-2px}.ant-menu-horizontal>.ant-menu-item-selected a{color:#d03f0a}.ant-menu-horizontal:after{display:block;clear:both;height:0;content:" "}.ant-menu-vertical .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item,.ant-menu-inline .ant-menu-item{position:relative}.ant-menu-vertical .ant-menu-item:after,.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-vertical-right .ant-menu-item:after,.ant-menu-inline .ant-menu-item:after{position:absolute;top:0;right:0;bottom:0;border-right:3px solid #d03f0a;transform:scaleY(.0001);opacity:0;transition:transform .15s cubic-bezier(.215,.61,.355,1),opacity .15s cubic-bezier(.215,.61,.355,1);content:""}.ant-menu-vertical .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item,.ant-menu-inline .ant-menu-item,.ant-menu-vertical .ant-menu-submenu-title,.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-vertical-right .ant-menu-submenu-title,.ant-menu-inline .ant-menu-submenu-title{height:40px;margin-top:4px;margin-bottom:4px;padding:0 16px;overflow:hidden;line-height:40px;text-overflow:ellipsis}.ant-menu-vertical .ant-menu-submenu,.ant-menu-vertical-left .ant-menu-submenu,.ant-menu-vertical-right .ant-menu-submenu,.ant-menu-inline .ant-menu-submenu{padding-bottom:.02px}.ant-menu-vertical .ant-menu-item:not(:last-child),.ant-menu-vertical-left .ant-menu-item:not(:last-child),.ant-menu-vertical-right .ant-menu-item:not(:last-child),.ant-menu-inline .ant-menu-item:not(:last-child){margin-bottom:8px}.ant-menu-vertical>.ant-menu-item,.ant-menu-vertical-left>.ant-menu-item,.ant-menu-vertical-right>.ant-menu-item,.ant-menu-inline>.ant-menu-item,.ant-menu-vertical>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical-left>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical-right>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-inline>.ant-menu-submenu>.ant-menu-submenu-title{height:40px;line-height:40px}.ant-menu-vertical .ant-menu-item-group-list .ant-menu-submenu-title,.ant-menu-vertical .ant-menu-submenu-title{padding-right:34px}.ant-menu-inline{width:100%}.ant-menu-inline .ant-menu-selected:after,.ant-menu-inline .ant-menu-item-selected:after{transform:scaleY(1);opacity:1;transition:transform .15s cubic-bezier(.645,.045,.355,1),opacity .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title{width:calc(100% + 1px)}.ant-menu-inline .ant-menu-item-group-list .ant-menu-submenu-title,.ant-menu-inline .ant-menu-submenu-title{padding-right:34px}.ant-menu-inline.ant-menu-root .ant-menu-item,.ant-menu-inline.ant-menu-root .ant-menu-submenu-title{display:flex;align-items:center;transition:border-color .3s,background .3s,padding .1s cubic-bezier(.215,.61,.355,1)}.ant-menu-inline.ant-menu-root .ant-menu-item>.ant-menu-title-content,.ant-menu-inline.ant-menu-root .ant-menu-submenu-title>.ant-menu-title-content{flex:auto;min-width:0;overflow:hidden;text-overflow:ellipsis}.ant-menu-inline.ant-menu-root .ant-menu-item>*,.ant-menu-inline.ant-menu-root .ant-menu-submenu-title>*{flex:none}.ant-menu.ant-menu-inline-collapsed{width:80px}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title{left:0;padding:0 calc(50% - 8px);text-overflow:clip}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .ant-menu-submenu-arrow,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .ant-menu-submenu-arrow,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-submenu-arrow{opacity:0}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .anticon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .anticon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .anticon,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .anticon{margin:0;font-size:16px;line-height:40px}.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-item-icon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item .anticon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .anticon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .anticon+span,.ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .anticon+span{display:inline-block;opacity:0}.ant-menu.ant-menu-inline-collapsed .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed .anticon{display:inline-block}.ant-menu.ant-menu-inline-collapsed-tooltip{pointer-events:none}.ant-menu.ant-menu-inline-collapsed-tooltip .ant-menu-item-icon,.ant-menu.ant-menu-inline-collapsed-tooltip .anticon{display:none}.ant-menu.ant-menu-inline-collapsed-tooltip a{color:#ffffffd9}.ant-menu.ant-menu-inline-collapsed .ant-menu-item-group-title{padding-right:4px;padding-left:4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-menu-item-group-list{margin:0;padding:0}.ant-menu-item-group-list .ant-menu-item,.ant-menu-item-group-list .ant-menu-submenu-title{padding:0 16px 0 28px}.ant-menu-root.ant-menu-vertical,.ant-menu-root.ant-menu-vertical-left,.ant-menu-root.ant-menu-vertical-right,.ant-menu-root.ant-menu-inline{box-shadow:none}.ant-menu-root.ant-menu-inline-collapsed .ant-menu-item>.ant-menu-inline-collapsed-noicon,.ant-menu-root.ant-menu-inline-collapsed .ant-menu-submenu .ant-menu-submenu-title>.ant-menu-inline-collapsed-noicon{font-size:16px;text-align:center}.ant-menu-sub.ant-menu-inline{padding:0;background:#fafafa;border:0;border-radius:0;box-shadow:none}.ant-menu-sub.ant-menu-inline>.ant-menu-item,.ant-menu-sub.ant-menu-inline>.ant-menu-submenu>.ant-menu-submenu-title{height:40px;line-height:40px;list-style-position:inside;list-style-type:disc}.ant-menu-sub.ant-menu-inline .ant-menu-item-group-title{padding-left:32px}.ant-menu-item-disabled,.ant-menu-submenu-disabled{color:#00000040!important;background:none;cursor:not-allowed}.ant-menu-item-disabled:after,.ant-menu-submenu-disabled:after{border-color:transparent!important}.ant-menu-item-disabled a,.ant-menu-submenu-disabled a{color:#00000040!important;pointer-events:none}.ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-submenu-disabled>.ant-menu-submenu-title{color:#00000040!important;cursor:not-allowed}.ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after{background:rgba(0,0,0,.25)!important}.ant-layout-header .ant-menu{line-height:inherit}.ant-menu-inline-collapsed-tooltip a,.ant-menu-inline-collapsed-tooltip a:hover{color:#fff}.ant-menu-light .ant-menu-item:hover,.ant-menu-light .ant-menu-item-active,.ant-menu-light .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open,.ant-menu-light .ant-menu-submenu-active,.ant-menu-light .ant-menu-submenu-title:hover{color:#d03f0a}.ant-menu.ant-menu-root:focus-visible{box-shadow:0 0 0 2px #ab2800}.ant-menu-dark .ant-menu-item:focus-visible,.ant-menu-dark .ant-menu-submenu-title:focus-visible{box-shadow:0 0 0 2px #ab2800}.ant-menu.ant-menu-dark,.ant-menu-dark .ant-menu-sub,.ant-menu.ant-menu-dark .ant-menu-sub{color:#ffffffa6;background:#001529}.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow{opacity:.45;transition:all .3s}.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:before{background:#fff}.ant-menu-dark.ant-menu-submenu-popup{background:transparent}.ant-menu-dark .ant-menu-inline.ant-menu-sub{background:#000c17}.ant-menu-dark.ant-menu-horizontal{border-bottom:0}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu{top:0;margin-top:0;padding:0 20px;border-color:#001529;border-bottom:0}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item:hover{background-color:#d03f0a}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item>a:before{bottom:0}.ant-menu-dark .ant-menu-item,.ant-menu-dark .ant-menu-item-group-title,.ant-menu-dark .ant-menu-item>a,.ant-menu-dark .ant-menu-item>span>a{color:#ffffffa6}.ant-menu-dark.ant-menu-inline,.ant-menu-dark.ant-menu-vertical,.ant-menu-dark.ant-menu-vertical-left,.ant-menu-dark.ant-menu-vertical-right{border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item,.ant-menu-dark.ant-menu-vertical .ant-menu-item,.ant-menu-dark.ant-menu-vertical-left .ant-menu-item,.ant-menu-dark.ant-menu-vertical-right .ant-menu-item{left:0;margin-left:0;border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical-right .ant-menu-item:after{border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item,.ant-menu-dark.ant-menu-inline .ant-menu-submenu-title{width:100%}.ant-menu-dark .ant-menu-item:hover,.ant-menu-dark .ant-menu-item-active,.ant-menu-dark .ant-menu-submenu-active,.ant-menu-dark .ant-menu-submenu-open,.ant-menu-dark .ant-menu-submenu-selected,.ant-menu-dark .ant-menu-submenu-title:hover{color:#fff;background-color:transparent}.ant-menu-dark .ant-menu-item:hover>a,.ant-menu-dark .ant-menu-item-active>a,.ant-menu-dark .ant-menu-submenu-active>a,.ant-menu-dark .ant-menu-submenu-open>a,.ant-menu-dark .ant-menu-submenu-selected>a,.ant-menu-dark .ant-menu-submenu-title:hover>a,.ant-menu-dark .ant-menu-item:hover>span>a,.ant-menu-dark .ant-menu-item-active>span>a,.ant-menu-dark .ant-menu-submenu-active>span>a,.ant-menu-dark .ant-menu-submenu-open>span>a,.ant-menu-dark .ant-menu-submenu-selected>span>a,.ant-menu-dark .ant-menu-submenu-title:hover>span>a{color:#fff}.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow{opacity:1}.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:#fff}.ant-menu-dark .ant-menu-item:hover{background-color:transparent}.ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected{background-color:#d03f0a}.ant-menu-dark .ant-menu-item-selected{color:#fff;border-right:0}.ant-menu-dark .ant-menu-item-selected:after{border-right:0}.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>span>a,.ant-menu-dark .ant-menu-item-selected>a:hover,.ant-menu-dark .ant-menu-item-selected>span>a:hover{color:#fff}.ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon,.ant-menu-dark .ant-menu-item-selected .anticon{color:#fff}.ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon+span,.ant-menu-dark .ant-menu-item-selected .anticon+span{color:#fff}.ant-menu.ant-menu-dark .ant-menu-item-selected,.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected{background-color:#d03f0a}.ant-menu-dark .ant-menu-item-disabled,.ant-menu-dark .ant-menu-submenu-disabled,.ant-menu-dark .ant-menu-item-disabled>a,.ant-menu-dark .ant-menu-submenu-disabled>a,.ant-menu-dark .ant-menu-item-disabled>span>a,.ant-menu-dark .ant-menu-submenu-disabled>span>a{color:#ffffff59!important;opacity:.8}.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title{color:#ffffff59!important}.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after{background:rgba(255,255,255,.35)!important}.ant-menu.ant-menu-rtl{direction:rtl;text-align:right}.ant-menu-rtl .ant-menu-item-group-title{text-align:right}.ant-menu-rtl.ant-menu-inline,.ant-menu-rtl.ant-menu-vertical{border-right:none;border-left:1px solid #f0f0f0}.ant-menu-rtl.ant-menu-dark.ant-menu-inline,.ant-menu-rtl.ant-menu-dark.ant-menu-vertical{border-left:none}.ant-menu-rtl.ant-menu-vertical.ant-menu-sub>.ant-menu-item,.ant-menu-rtl.ant-menu-vertical-left.ant-menu-sub>.ant-menu-item,.ant-menu-rtl.ant-menu-vertical-right.ant-menu-sub>.ant-menu-item,.ant-menu-rtl.ant-menu-vertical.ant-menu-sub>.ant-menu-submenu,.ant-menu-rtl.ant-menu-vertical-left.ant-menu-sub>.ant-menu-submenu,.ant-menu-rtl.ant-menu-vertical-right.ant-menu-sub>.ant-menu-submenu{transform-origin:top right}.ant-menu-rtl .ant-menu-item .ant-menu-item-icon,.ant-menu-rtl .ant-menu-submenu-title .ant-menu-item-icon,.ant-menu-rtl .ant-menu-item .anticon,.ant-menu-rtl .ant-menu-submenu-title .anticon{margin-right:auto;margin-left:10px}.ant-menu-rtl .ant-menu-item.ant-menu-item-only-child>.ant-menu-item-icon,.ant-menu-rtl .ant-menu-submenu-title.ant-menu-item-only-child>.ant-menu-item-icon,.ant-menu-rtl .ant-menu-item.ant-menu-item-only-child>.anticon,.ant-menu-rtl .ant-menu-submenu-title.ant-menu-item-only-child>.anticon{margin-left:0}.ant-menu-submenu-rtl.ant-menu-submenu-popup{transform-origin:100% 0}.ant-menu-rtl .ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-rtl .ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-rtl .ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-rtl .ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow{right:auto;left:16px}.ant-menu-rtl .ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-rtl .ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-rtl .ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:before{transform:rotate(-45deg) translateY(-2px)}.ant-menu-rtl .ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-rtl .ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-rtl .ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:after{transform:rotate(45deg) translateY(2px)}.ant-menu-rtl.ant-menu-vertical .ant-menu-item:after,.ant-menu-rtl.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-rtl.ant-menu-vertical-right .ant-menu-item:after,.ant-menu-rtl.ant-menu-inline .ant-menu-item:after{right:auto;left:0}.ant-menu-rtl.ant-menu-vertical .ant-menu-item,.ant-menu-rtl.ant-menu-vertical-left .ant-menu-item,.ant-menu-rtl.ant-menu-vertical-right .ant-menu-item,.ant-menu-rtl.ant-menu-inline .ant-menu-item,.ant-menu-rtl.ant-menu-vertical .ant-menu-submenu-title,.ant-menu-rtl.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-rtl.ant-menu-vertical-right .ant-menu-submenu-title,.ant-menu-rtl.ant-menu-inline .ant-menu-submenu-title{text-align:right}.ant-menu-rtl.ant-menu-inline .ant-menu-submenu-title{padding-right:0;padding-left:34px}.ant-menu-rtl.ant-menu-vertical .ant-menu-submenu-title{padding-right:16px;padding-left:34px}.ant-menu-rtl.ant-menu-inline-collapsed.ant-menu-vertical .ant-menu-submenu-title{padding:0 calc(50% - 8px)}.ant-menu-rtl .ant-menu-item-group-list .ant-menu-item,.ant-menu-rtl .ant-menu-item-group-list .ant-menu-submenu-title{padding:0 28px 0 16px}.ant-menu-sub.ant-menu-inline{border:0}.ant-menu-rtl.ant-menu-sub.ant-menu-inline .ant-menu-item-group-title{padding-right:32px;padding-left:0} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-806213af.css b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-806213af.css new file mode 100644 index 0000000000000000000000000000000000000000..b67cf275937f5d4f133c7dcf41dc05275dccce14 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-806213af.css @@ -0,0 +1 @@ +.ant-row{display:flex;flex-flow:row wrap}.ant-row:before,.ant-row:after{display:flex}.ant-row-no-wrap{flex-wrap:nowrap}.ant-row-start{justify-content:flex-start}.ant-row-center{justify-content:center}.ant-row-end{justify-content:flex-end}.ant-row-space-between{justify-content:space-between}.ant-row-space-around{justify-content:space-around}.ant-row-top{align-items:flex-start}.ant-row-middle{align-items:center}.ant-row-bottom{align-items:flex-end}.ant-col{position:relative;max-width:100%;min-height:1px}.ant-col-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-push-24{left:100%}.ant-col-pull-24{right:100%}.ant-col-offset-24{margin-left:100%}.ant-col-order-24{order:24}.ant-col-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-push-23{left:95.83333333%}.ant-col-pull-23{right:95.83333333%}.ant-col-offset-23{margin-left:95.83333333%}.ant-col-order-23{order:23}.ant-col-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-push-22{left:91.66666667%}.ant-col-pull-22{right:91.66666667%}.ant-col-offset-22{margin-left:91.66666667%}.ant-col-order-22{order:22}.ant-col-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-push-21{left:87.5%}.ant-col-pull-21{right:87.5%}.ant-col-offset-21{margin-left:87.5%}.ant-col-order-21{order:21}.ant-col-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-push-20{left:83.33333333%}.ant-col-pull-20{right:83.33333333%}.ant-col-offset-20{margin-left:83.33333333%}.ant-col-order-20{order:20}.ant-col-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-push-19{left:79.16666667%}.ant-col-pull-19{right:79.16666667%}.ant-col-offset-19{margin-left:79.16666667%}.ant-col-order-19{order:19}.ant-col-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-push-18{left:75%}.ant-col-pull-18{right:75%}.ant-col-offset-18{margin-left:75%}.ant-col-order-18{order:18}.ant-col-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-push-17{left:70.83333333%}.ant-col-pull-17{right:70.83333333%}.ant-col-offset-17{margin-left:70.83333333%}.ant-col-order-17{order:17}.ant-col-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-push-16{left:66.66666667%}.ant-col-pull-16{right:66.66666667%}.ant-col-offset-16{margin-left:66.66666667%}.ant-col-order-16{order:16}.ant-col-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-push-15{left:62.5%}.ant-col-pull-15{right:62.5%}.ant-col-offset-15{margin-left:62.5%}.ant-col-order-15{order:15}.ant-col-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-push-14{left:58.33333333%}.ant-col-pull-14{right:58.33333333%}.ant-col-offset-14{margin-left:58.33333333%}.ant-col-order-14{order:14}.ant-col-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-push-13{left:54.16666667%}.ant-col-pull-13{right:54.16666667%}.ant-col-offset-13{margin-left:54.16666667%}.ant-col-order-13{order:13}.ant-col-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-push-12{left:50%}.ant-col-pull-12{right:50%}.ant-col-offset-12{margin-left:50%}.ant-col-order-12{order:12}.ant-col-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-push-11{left:45.83333333%}.ant-col-pull-11{right:45.83333333%}.ant-col-offset-11{margin-left:45.83333333%}.ant-col-order-11{order:11}.ant-col-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-push-10{left:41.66666667%}.ant-col-pull-10{right:41.66666667%}.ant-col-offset-10{margin-left:41.66666667%}.ant-col-order-10{order:10}.ant-col-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-push-9{left:37.5%}.ant-col-pull-9{right:37.5%}.ant-col-offset-9{margin-left:37.5%}.ant-col-order-9{order:9}.ant-col-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-push-8{left:33.33333333%}.ant-col-pull-8{right:33.33333333%}.ant-col-offset-8{margin-left:33.33333333%}.ant-col-order-8{order:8}.ant-col-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-push-7{left:29.16666667%}.ant-col-pull-7{right:29.16666667%}.ant-col-offset-7{margin-left:29.16666667%}.ant-col-order-7{order:7}.ant-col-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-push-6{left:25%}.ant-col-pull-6{right:25%}.ant-col-offset-6{margin-left:25%}.ant-col-order-6{order:6}.ant-col-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-push-5{left:20.83333333%}.ant-col-pull-5{right:20.83333333%}.ant-col-offset-5{margin-left:20.83333333%}.ant-col-order-5{order:5}.ant-col-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-push-4{left:16.66666667%}.ant-col-pull-4{right:16.66666667%}.ant-col-offset-4{margin-left:16.66666667%}.ant-col-order-4{order:4}.ant-col-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-push-3{left:12.5%}.ant-col-pull-3{right:12.5%}.ant-col-offset-3{margin-left:12.5%}.ant-col-order-3{order:3}.ant-col-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-push-2{left:8.33333333%}.ant-col-pull-2{right:8.33333333%}.ant-col-offset-2{margin-left:8.33333333%}.ant-col-order-2{order:2}.ant-col-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-push-1{left:4.16666667%}.ant-col-pull-1{right:4.16666667%}.ant-col-offset-1{margin-left:4.16666667%}.ant-col-order-1{order:1}.ant-col-0{display:none}.ant-col-offset-0{margin-left:0}.ant-col-order-0{order:0}.ant-col-offset-0.ant-col-rtl{margin-right:0}.ant-col-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}.ant-col-xs-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-xs-push-24{left:100%}.ant-col-xs-pull-24{right:100%}.ant-col-xs-offset-24{margin-left:100%}.ant-col-xs-order-24{order:24}.ant-col-xs-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-xs-push-23{left:95.83333333%}.ant-col-xs-pull-23{right:95.83333333%}.ant-col-xs-offset-23{margin-left:95.83333333%}.ant-col-xs-order-23{order:23}.ant-col-xs-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-xs-push-22{left:91.66666667%}.ant-col-xs-pull-22{right:91.66666667%}.ant-col-xs-offset-22{margin-left:91.66666667%}.ant-col-xs-order-22{order:22}.ant-col-xs-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-xs-push-21{left:87.5%}.ant-col-xs-pull-21{right:87.5%}.ant-col-xs-offset-21{margin-left:87.5%}.ant-col-xs-order-21{order:21}.ant-col-xs-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-xs-push-20{left:83.33333333%}.ant-col-xs-pull-20{right:83.33333333%}.ant-col-xs-offset-20{margin-left:83.33333333%}.ant-col-xs-order-20{order:20}.ant-col-xs-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-xs-push-19{left:79.16666667%}.ant-col-xs-pull-19{right:79.16666667%}.ant-col-xs-offset-19{margin-left:79.16666667%}.ant-col-xs-order-19{order:19}.ant-col-xs-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-xs-push-18{left:75%}.ant-col-xs-pull-18{right:75%}.ant-col-xs-offset-18{margin-left:75%}.ant-col-xs-order-18{order:18}.ant-col-xs-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-xs-push-17{left:70.83333333%}.ant-col-xs-pull-17{right:70.83333333%}.ant-col-xs-offset-17{margin-left:70.83333333%}.ant-col-xs-order-17{order:17}.ant-col-xs-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-xs-push-16{left:66.66666667%}.ant-col-xs-pull-16{right:66.66666667%}.ant-col-xs-offset-16{margin-left:66.66666667%}.ant-col-xs-order-16{order:16}.ant-col-xs-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-xs-push-15{left:62.5%}.ant-col-xs-pull-15{right:62.5%}.ant-col-xs-offset-15{margin-left:62.5%}.ant-col-xs-order-15{order:15}.ant-col-xs-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-xs-push-14{left:58.33333333%}.ant-col-xs-pull-14{right:58.33333333%}.ant-col-xs-offset-14{margin-left:58.33333333%}.ant-col-xs-order-14{order:14}.ant-col-xs-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-xs-push-13{left:54.16666667%}.ant-col-xs-pull-13{right:54.16666667%}.ant-col-xs-offset-13{margin-left:54.16666667%}.ant-col-xs-order-13{order:13}.ant-col-xs-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-xs-push-12{left:50%}.ant-col-xs-pull-12{right:50%}.ant-col-xs-offset-12{margin-left:50%}.ant-col-xs-order-12{order:12}.ant-col-xs-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-xs-push-11{left:45.83333333%}.ant-col-xs-pull-11{right:45.83333333%}.ant-col-xs-offset-11{margin-left:45.83333333%}.ant-col-xs-order-11{order:11}.ant-col-xs-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-xs-push-10{left:41.66666667%}.ant-col-xs-pull-10{right:41.66666667%}.ant-col-xs-offset-10{margin-left:41.66666667%}.ant-col-xs-order-10{order:10}.ant-col-xs-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-xs-push-9{left:37.5%}.ant-col-xs-pull-9{right:37.5%}.ant-col-xs-offset-9{margin-left:37.5%}.ant-col-xs-order-9{order:9}.ant-col-xs-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-xs-push-8{left:33.33333333%}.ant-col-xs-pull-8{right:33.33333333%}.ant-col-xs-offset-8{margin-left:33.33333333%}.ant-col-xs-order-8{order:8}.ant-col-xs-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-xs-push-7{left:29.16666667%}.ant-col-xs-pull-7{right:29.16666667%}.ant-col-xs-offset-7{margin-left:29.16666667%}.ant-col-xs-order-7{order:7}.ant-col-xs-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-xs-push-6{left:25%}.ant-col-xs-pull-6{right:25%}.ant-col-xs-offset-6{margin-left:25%}.ant-col-xs-order-6{order:6}.ant-col-xs-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-xs-push-5{left:20.83333333%}.ant-col-xs-pull-5{right:20.83333333%}.ant-col-xs-offset-5{margin-left:20.83333333%}.ant-col-xs-order-5{order:5}.ant-col-xs-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-xs-push-4{left:16.66666667%}.ant-col-xs-pull-4{right:16.66666667%}.ant-col-xs-offset-4{margin-left:16.66666667%}.ant-col-xs-order-4{order:4}.ant-col-xs-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-xs-push-3{left:12.5%}.ant-col-xs-pull-3{right:12.5%}.ant-col-xs-offset-3{margin-left:12.5%}.ant-col-xs-order-3{order:3}.ant-col-xs-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-xs-push-2{left:8.33333333%}.ant-col-xs-pull-2{right:8.33333333%}.ant-col-xs-offset-2{margin-left:8.33333333%}.ant-col-xs-order-2{order:2}.ant-col-xs-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-xs-push-1{left:4.16666667%}.ant-col-xs-pull-1{right:4.16666667%}.ant-col-xs-offset-1{margin-left:4.16666667%}.ant-col-xs-order-1{order:1}.ant-col-xs-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xs-push-0{left:auto}.ant-col-xs-pull-0{right:auto}.ant-col-xs-offset-0{margin-left:0}.ant-col-xs-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-xs-push-0.ant-col-rtl{right:auto}.ant-col-xs-pull-0.ant-col-rtl{left:auto}.ant-col-xs-offset-0.ant-col-rtl{margin-right:0}.ant-col-xs-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-xs-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-xs-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-xs-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-xs-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-xs-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-xs-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-xs-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-xs-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-xs-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-xs-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-xs-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-xs-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-xs-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-xs-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-xs-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-xs-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-xs-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-xs-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-xs-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-xs-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-xs-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-xs-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-xs-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-xs-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-xs-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-xs-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-xs-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-xs-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-xs-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-xs-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-xs-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-xs-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-xs-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-xs-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-xs-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-xs-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-xs-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-xs-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-xs-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-xs-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-xs-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-xs-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-xs-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-xs-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-xs-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-xs-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-xs-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-xs-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-xs-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-xs-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-xs-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-xs-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-xs-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-xs-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-xs-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-xs-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-xs-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-xs-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-xs-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-xs-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-xs-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-xs-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-xs-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-xs-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-xs-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-xs-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-xs-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-xs-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-xs-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-xs-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-xs-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}@media (min-width: 576px){.ant-col-sm-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-sm-push-24{left:100%}.ant-col-sm-pull-24{right:100%}.ant-col-sm-offset-24{margin-left:100%}.ant-col-sm-order-24{order:24}.ant-col-sm-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-sm-push-23{left:95.83333333%}.ant-col-sm-pull-23{right:95.83333333%}.ant-col-sm-offset-23{margin-left:95.83333333%}.ant-col-sm-order-23{order:23}.ant-col-sm-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-sm-push-22{left:91.66666667%}.ant-col-sm-pull-22{right:91.66666667%}.ant-col-sm-offset-22{margin-left:91.66666667%}.ant-col-sm-order-22{order:22}.ant-col-sm-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-sm-push-21{left:87.5%}.ant-col-sm-pull-21{right:87.5%}.ant-col-sm-offset-21{margin-left:87.5%}.ant-col-sm-order-21{order:21}.ant-col-sm-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-sm-push-20{left:83.33333333%}.ant-col-sm-pull-20{right:83.33333333%}.ant-col-sm-offset-20{margin-left:83.33333333%}.ant-col-sm-order-20{order:20}.ant-col-sm-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-sm-push-19{left:79.16666667%}.ant-col-sm-pull-19{right:79.16666667%}.ant-col-sm-offset-19{margin-left:79.16666667%}.ant-col-sm-order-19{order:19}.ant-col-sm-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-sm-push-18{left:75%}.ant-col-sm-pull-18{right:75%}.ant-col-sm-offset-18{margin-left:75%}.ant-col-sm-order-18{order:18}.ant-col-sm-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-sm-push-17{left:70.83333333%}.ant-col-sm-pull-17{right:70.83333333%}.ant-col-sm-offset-17{margin-left:70.83333333%}.ant-col-sm-order-17{order:17}.ant-col-sm-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-sm-push-16{left:66.66666667%}.ant-col-sm-pull-16{right:66.66666667%}.ant-col-sm-offset-16{margin-left:66.66666667%}.ant-col-sm-order-16{order:16}.ant-col-sm-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-sm-push-15{left:62.5%}.ant-col-sm-pull-15{right:62.5%}.ant-col-sm-offset-15{margin-left:62.5%}.ant-col-sm-order-15{order:15}.ant-col-sm-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-sm-push-14{left:58.33333333%}.ant-col-sm-pull-14{right:58.33333333%}.ant-col-sm-offset-14{margin-left:58.33333333%}.ant-col-sm-order-14{order:14}.ant-col-sm-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-sm-push-13{left:54.16666667%}.ant-col-sm-pull-13{right:54.16666667%}.ant-col-sm-offset-13{margin-left:54.16666667%}.ant-col-sm-order-13{order:13}.ant-col-sm-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-sm-push-12{left:50%}.ant-col-sm-pull-12{right:50%}.ant-col-sm-offset-12{margin-left:50%}.ant-col-sm-order-12{order:12}.ant-col-sm-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-sm-push-11{left:45.83333333%}.ant-col-sm-pull-11{right:45.83333333%}.ant-col-sm-offset-11{margin-left:45.83333333%}.ant-col-sm-order-11{order:11}.ant-col-sm-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-sm-push-10{left:41.66666667%}.ant-col-sm-pull-10{right:41.66666667%}.ant-col-sm-offset-10{margin-left:41.66666667%}.ant-col-sm-order-10{order:10}.ant-col-sm-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-sm-push-9{left:37.5%}.ant-col-sm-pull-9{right:37.5%}.ant-col-sm-offset-9{margin-left:37.5%}.ant-col-sm-order-9{order:9}.ant-col-sm-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-sm-push-8{left:33.33333333%}.ant-col-sm-pull-8{right:33.33333333%}.ant-col-sm-offset-8{margin-left:33.33333333%}.ant-col-sm-order-8{order:8}.ant-col-sm-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-sm-push-7{left:29.16666667%}.ant-col-sm-pull-7{right:29.16666667%}.ant-col-sm-offset-7{margin-left:29.16666667%}.ant-col-sm-order-7{order:7}.ant-col-sm-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-sm-push-6{left:25%}.ant-col-sm-pull-6{right:25%}.ant-col-sm-offset-6{margin-left:25%}.ant-col-sm-order-6{order:6}.ant-col-sm-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-sm-push-5{left:20.83333333%}.ant-col-sm-pull-5{right:20.83333333%}.ant-col-sm-offset-5{margin-left:20.83333333%}.ant-col-sm-order-5{order:5}.ant-col-sm-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-sm-push-4{left:16.66666667%}.ant-col-sm-pull-4{right:16.66666667%}.ant-col-sm-offset-4{margin-left:16.66666667%}.ant-col-sm-order-4{order:4}.ant-col-sm-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-sm-push-3{left:12.5%}.ant-col-sm-pull-3{right:12.5%}.ant-col-sm-offset-3{margin-left:12.5%}.ant-col-sm-order-3{order:3}.ant-col-sm-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-sm-push-2{left:8.33333333%}.ant-col-sm-pull-2{right:8.33333333%}.ant-col-sm-offset-2{margin-left:8.33333333%}.ant-col-sm-order-2{order:2}.ant-col-sm-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-sm-push-1{left:4.16666667%}.ant-col-sm-pull-1{right:4.16666667%}.ant-col-sm-offset-1{margin-left:4.16666667%}.ant-col-sm-order-1{order:1}.ant-col-sm-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-sm-push-0{left:auto}.ant-col-sm-pull-0{right:auto}.ant-col-sm-offset-0{margin-left:0}.ant-col-sm-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-sm-push-0.ant-col-rtl{right:auto}.ant-col-sm-pull-0.ant-col-rtl{left:auto}.ant-col-sm-offset-0.ant-col-rtl{margin-right:0}.ant-col-sm-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-sm-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-sm-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-sm-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-sm-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-sm-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-sm-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-sm-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-sm-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-sm-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-sm-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-sm-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-sm-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-sm-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-sm-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-sm-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-sm-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-sm-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-sm-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-sm-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-sm-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-sm-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-sm-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-sm-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-sm-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-sm-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-sm-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-sm-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-sm-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-sm-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-sm-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-sm-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-sm-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-sm-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-sm-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-sm-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-sm-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-sm-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-sm-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-sm-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-sm-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-sm-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-sm-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-sm-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-sm-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-sm-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-sm-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-sm-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-sm-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-sm-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-sm-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-sm-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-sm-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-sm-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-sm-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-sm-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-sm-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-sm-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-sm-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-sm-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-sm-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-sm-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-sm-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-sm-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-sm-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-sm-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-sm-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-sm-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-sm-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-sm-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-sm-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-sm-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 768px){.ant-col-md-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-md-push-24{left:100%}.ant-col-md-pull-24{right:100%}.ant-col-md-offset-24{margin-left:100%}.ant-col-md-order-24{order:24}.ant-col-md-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-md-push-23{left:95.83333333%}.ant-col-md-pull-23{right:95.83333333%}.ant-col-md-offset-23{margin-left:95.83333333%}.ant-col-md-order-23{order:23}.ant-col-md-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-md-push-22{left:91.66666667%}.ant-col-md-pull-22{right:91.66666667%}.ant-col-md-offset-22{margin-left:91.66666667%}.ant-col-md-order-22{order:22}.ant-col-md-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-md-push-21{left:87.5%}.ant-col-md-pull-21{right:87.5%}.ant-col-md-offset-21{margin-left:87.5%}.ant-col-md-order-21{order:21}.ant-col-md-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-md-push-20{left:83.33333333%}.ant-col-md-pull-20{right:83.33333333%}.ant-col-md-offset-20{margin-left:83.33333333%}.ant-col-md-order-20{order:20}.ant-col-md-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-md-push-19{left:79.16666667%}.ant-col-md-pull-19{right:79.16666667%}.ant-col-md-offset-19{margin-left:79.16666667%}.ant-col-md-order-19{order:19}.ant-col-md-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-md-push-18{left:75%}.ant-col-md-pull-18{right:75%}.ant-col-md-offset-18{margin-left:75%}.ant-col-md-order-18{order:18}.ant-col-md-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-md-push-17{left:70.83333333%}.ant-col-md-pull-17{right:70.83333333%}.ant-col-md-offset-17{margin-left:70.83333333%}.ant-col-md-order-17{order:17}.ant-col-md-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-md-push-16{left:66.66666667%}.ant-col-md-pull-16{right:66.66666667%}.ant-col-md-offset-16{margin-left:66.66666667%}.ant-col-md-order-16{order:16}.ant-col-md-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-md-push-15{left:62.5%}.ant-col-md-pull-15{right:62.5%}.ant-col-md-offset-15{margin-left:62.5%}.ant-col-md-order-15{order:15}.ant-col-md-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-md-push-14{left:58.33333333%}.ant-col-md-pull-14{right:58.33333333%}.ant-col-md-offset-14{margin-left:58.33333333%}.ant-col-md-order-14{order:14}.ant-col-md-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-md-push-13{left:54.16666667%}.ant-col-md-pull-13{right:54.16666667%}.ant-col-md-offset-13{margin-left:54.16666667%}.ant-col-md-order-13{order:13}.ant-col-md-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-md-push-12{left:50%}.ant-col-md-pull-12{right:50%}.ant-col-md-offset-12{margin-left:50%}.ant-col-md-order-12{order:12}.ant-col-md-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-md-push-11{left:45.83333333%}.ant-col-md-pull-11{right:45.83333333%}.ant-col-md-offset-11{margin-left:45.83333333%}.ant-col-md-order-11{order:11}.ant-col-md-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-md-push-10{left:41.66666667%}.ant-col-md-pull-10{right:41.66666667%}.ant-col-md-offset-10{margin-left:41.66666667%}.ant-col-md-order-10{order:10}.ant-col-md-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-md-push-9{left:37.5%}.ant-col-md-pull-9{right:37.5%}.ant-col-md-offset-9{margin-left:37.5%}.ant-col-md-order-9{order:9}.ant-col-md-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-md-push-8{left:33.33333333%}.ant-col-md-pull-8{right:33.33333333%}.ant-col-md-offset-8{margin-left:33.33333333%}.ant-col-md-order-8{order:8}.ant-col-md-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-md-push-7{left:29.16666667%}.ant-col-md-pull-7{right:29.16666667%}.ant-col-md-offset-7{margin-left:29.16666667%}.ant-col-md-order-7{order:7}.ant-col-md-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-md-push-6{left:25%}.ant-col-md-pull-6{right:25%}.ant-col-md-offset-6{margin-left:25%}.ant-col-md-order-6{order:6}.ant-col-md-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-md-push-5{left:20.83333333%}.ant-col-md-pull-5{right:20.83333333%}.ant-col-md-offset-5{margin-left:20.83333333%}.ant-col-md-order-5{order:5}.ant-col-md-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-md-push-4{left:16.66666667%}.ant-col-md-pull-4{right:16.66666667%}.ant-col-md-offset-4{margin-left:16.66666667%}.ant-col-md-order-4{order:4}.ant-col-md-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-md-push-3{left:12.5%}.ant-col-md-pull-3{right:12.5%}.ant-col-md-offset-3{margin-left:12.5%}.ant-col-md-order-3{order:3}.ant-col-md-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-md-push-2{left:8.33333333%}.ant-col-md-pull-2{right:8.33333333%}.ant-col-md-offset-2{margin-left:8.33333333%}.ant-col-md-order-2{order:2}.ant-col-md-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-md-push-1{left:4.16666667%}.ant-col-md-pull-1{right:4.16666667%}.ant-col-md-offset-1{margin-left:4.16666667%}.ant-col-md-order-1{order:1}.ant-col-md-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-md-push-0{left:auto}.ant-col-md-pull-0{right:auto}.ant-col-md-offset-0{margin-left:0}.ant-col-md-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-md-push-0.ant-col-rtl{right:auto}.ant-col-md-pull-0.ant-col-rtl{left:auto}.ant-col-md-offset-0.ant-col-rtl{margin-right:0}.ant-col-md-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-md-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-md-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-md-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-md-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-md-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-md-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-md-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-md-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-md-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-md-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-md-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-md-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-md-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-md-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-md-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-md-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-md-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-md-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-md-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-md-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-md-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-md-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-md-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-md-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-md-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-md-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-md-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-md-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-md-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-md-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-md-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-md-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-md-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-md-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-md-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-md-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-md-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-md-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-md-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-md-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-md-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-md-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-md-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-md-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-md-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-md-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-md-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-md-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-md-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-md-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-md-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-md-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-md-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-md-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-md-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-md-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-md-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-md-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-md-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-md-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-md-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-md-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-md-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-md-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-md-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-md-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-md-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-md-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-md-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-md-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-md-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 992px){.ant-col-lg-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-lg-push-24{left:100%}.ant-col-lg-pull-24{right:100%}.ant-col-lg-offset-24{margin-left:100%}.ant-col-lg-order-24{order:24}.ant-col-lg-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-lg-push-23{left:95.83333333%}.ant-col-lg-pull-23{right:95.83333333%}.ant-col-lg-offset-23{margin-left:95.83333333%}.ant-col-lg-order-23{order:23}.ant-col-lg-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-lg-push-22{left:91.66666667%}.ant-col-lg-pull-22{right:91.66666667%}.ant-col-lg-offset-22{margin-left:91.66666667%}.ant-col-lg-order-22{order:22}.ant-col-lg-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-lg-push-21{left:87.5%}.ant-col-lg-pull-21{right:87.5%}.ant-col-lg-offset-21{margin-left:87.5%}.ant-col-lg-order-21{order:21}.ant-col-lg-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-lg-push-20{left:83.33333333%}.ant-col-lg-pull-20{right:83.33333333%}.ant-col-lg-offset-20{margin-left:83.33333333%}.ant-col-lg-order-20{order:20}.ant-col-lg-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-lg-push-19{left:79.16666667%}.ant-col-lg-pull-19{right:79.16666667%}.ant-col-lg-offset-19{margin-left:79.16666667%}.ant-col-lg-order-19{order:19}.ant-col-lg-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-lg-push-18{left:75%}.ant-col-lg-pull-18{right:75%}.ant-col-lg-offset-18{margin-left:75%}.ant-col-lg-order-18{order:18}.ant-col-lg-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-lg-push-17{left:70.83333333%}.ant-col-lg-pull-17{right:70.83333333%}.ant-col-lg-offset-17{margin-left:70.83333333%}.ant-col-lg-order-17{order:17}.ant-col-lg-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-lg-push-16{left:66.66666667%}.ant-col-lg-pull-16{right:66.66666667%}.ant-col-lg-offset-16{margin-left:66.66666667%}.ant-col-lg-order-16{order:16}.ant-col-lg-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-lg-push-15{left:62.5%}.ant-col-lg-pull-15{right:62.5%}.ant-col-lg-offset-15{margin-left:62.5%}.ant-col-lg-order-15{order:15}.ant-col-lg-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-lg-push-14{left:58.33333333%}.ant-col-lg-pull-14{right:58.33333333%}.ant-col-lg-offset-14{margin-left:58.33333333%}.ant-col-lg-order-14{order:14}.ant-col-lg-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-lg-push-13{left:54.16666667%}.ant-col-lg-pull-13{right:54.16666667%}.ant-col-lg-offset-13{margin-left:54.16666667%}.ant-col-lg-order-13{order:13}.ant-col-lg-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-lg-push-12{left:50%}.ant-col-lg-pull-12{right:50%}.ant-col-lg-offset-12{margin-left:50%}.ant-col-lg-order-12{order:12}.ant-col-lg-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-lg-push-11{left:45.83333333%}.ant-col-lg-pull-11{right:45.83333333%}.ant-col-lg-offset-11{margin-left:45.83333333%}.ant-col-lg-order-11{order:11}.ant-col-lg-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-lg-push-10{left:41.66666667%}.ant-col-lg-pull-10{right:41.66666667%}.ant-col-lg-offset-10{margin-left:41.66666667%}.ant-col-lg-order-10{order:10}.ant-col-lg-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-lg-push-9{left:37.5%}.ant-col-lg-pull-9{right:37.5%}.ant-col-lg-offset-9{margin-left:37.5%}.ant-col-lg-order-9{order:9}.ant-col-lg-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-lg-push-8{left:33.33333333%}.ant-col-lg-pull-8{right:33.33333333%}.ant-col-lg-offset-8{margin-left:33.33333333%}.ant-col-lg-order-8{order:8}.ant-col-lg-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-lg-push-7{left:29.16666667%}.ant-col-lg-pull-7{right:29.16666667%}.ant-col-lg-offset-7{margin-left:29.16666667%}.ant-col-lg-order-7{order:7}.ant-col-lg-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-lg-push-6{left:25%}.ant-col-lg-pull-6{right:25%}.ant-col-lg-offset-6{margin-left:25%}.ant-col-lg-order-6{order:6}.ant-col-lg-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-lg-push-5{left:20.83333333%}.ant-col-lg-pull-5{right:20.83333333%}.ant-col-lg-offset-5{margin-left:20.83333333%}.ant-col-lg-order-5{order:5}.ant-col-lg-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-lg-push-4{left:16.66666667%}.ant-col-lg-pull-4{right:16.66666667%}.ant-col-lg-offset-4{margin-left:16.66666667%}.ant-col-lg-order-4{order:4}.ant-col-lg-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-lg-push-3{left:12.5%}.ant-col-lg-pull-3{right:12.5%}.ant-col-lg-offset-3{margin-left:12.5%}.ant-col-lg-order-3{order:3}.ant-col-lg-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-lg-push-2{left:8.33333333%}.ant-col-lg-pull-2{right:8.33333333%}.ant-col-lg-offset-2{margin-left:8.33333333%}.ant-col-lg-order-2{order:2}.ant-col-lg-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-lg-push-1{left:4.16666667%}.ant-col-lg-pull-1{right:4.16666667%}.ant-col-lg-offset-1{margin-left:4.16666667%}.ant-col-lg-order-1{order:1}.ant-col-lg-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-lg-push-0{left:auto}.ant-col-lg-pull-0{right:auto}.ant-col-lg-offset-0{margin-left:0}.ant-col-lg-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-lg-push-0.ant-col-rtl{right:auto}.ant-col-lg-pull-0.ant-col-rtl{left:auto}.ant-col-lg-offset-0.ant-col-rtl{margin-right:0}.ant-col-lg-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-lg-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-lg-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-lg-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-lg-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-lg-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-lg-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-lg-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-lg-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-lg-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-lg-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-lg-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-lg-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-lg-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-lg-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-lg-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-lg-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-lg-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-lg-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-lg-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-lg-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-lg-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-lg-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-lg-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-lg-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-lg-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-lg-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-lg-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-lg-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-lg-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-lg-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-lg-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-lg-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-lg-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-lg-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-lg-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-lg-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-lg-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-lg-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-lg-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-lg-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-lg-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-lg-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-lg-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-lg-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-lg-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-lg-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-lg-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-lg-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-lg-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-lg-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-lg-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-lg-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-lg-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-lg-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-lg-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-lg-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-lg-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-lg-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-lg-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-lg-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-lg-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-lg-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-lg-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-lg-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-lg-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-lg-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-lg-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-lg-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-lg-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-lg-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-lg-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 1200px){.ant-col-xl-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-xl-push-24{left:100%}.ant-col-xl-pull-24{right:100%}.ant-col-xl-offset-24{margin-left:100%}.ant-col-xl-order-24{order:24}.ant-col-xl-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-xl-push-23{left:95.83333333%}.ant-col-xl-pull-23{right:95.83333333%}.ant-col-xl-offset-23{margin-left:95.83333333%}.ant-col-xl-order-23{order:23}.ant-col-xl-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-xl-push-22{left:91.66666667%}.ant-col-xl-pull-22{right:91.66666667%}.ant-col-xl-offset-22{margin-left:91.66666667%}.ant-col-xl-order-22{order:22}.ant-col-xl-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-xl-push-21{left:87.5%}.ant-col-xl-pull-21{right:87.5%}.ant-col-xl-offset-21{margin-left:87.5%}.ant-col-xl-order-21{order:21}.ant-col-xl-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-xl-push-20{left:83.33333333%}.ant-col-xl-pull-20{right:83.33333333%}.ant-col-xl-offset-20{margin-left:83.33333333%}.ant-col-xl-order-20{order:20}.ant-col-xl-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-xl-push-19{left:79.16666667%}.ant-col-xl-pull-19{right:79.16666667%}.ant-col-xl-offset-19{margin-left:79.16666667%}.ant-col-xl-order-19{order:19}.ant-col-xl-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-xl-push-18{left:75%}.ant-col-xl-pull-18{right:75%}.ant-col-xl-offset-18{margin-left:75%}.ant-col-xl-order-18{order:18}.ant-col-xl-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-xl-push-17{left:70.83333333%}.ant-col-xl-pull-17{right:70.83333333%}.ant-col-xl-offset-17{margin-left:70.83333333%}.ant-col-xl-order-17{order:17}.ant-col-xl-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-xl-push-16{left:66.66666667%}.ant-col-xl-pull-16{right:66.66666667%}.ant-col-xl-offset-16{margin-left:66.66666667%}.ant-col-xl-order-16{order:16}.ant-col-xl-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-xl-push-15{left:62.5%}.ant-col-xl-pull-15{right:62.5%}.ant-col-xl-offset-15{margin-left:62.5%}.ant-col-xl-order-15{order:15}.ant-col-xl-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-xl-push-14{left:58.33333333%}.ant-col-xl-pull-14{right:58.33333333%}.ant-col-xl-offset-14{margin-left:58.33333333%}.ant-col-xl-order-14{order:14}.ant-col-xl-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-xl-push-13{left:54.16666667%}.ant-col-xl-pull-13{right:54.16666667%}.ant-col-xl-offset-13{margin-left:54.16666667%}.ant-col-xl-order-13{order:13}.ant-col-xl-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-xl-push-12{left:50%}.ant-col-xl-pull-12{right:50%}.ant-col-xl-offset-12{margin-left:50%}.ant-col-xl-order-12{order:12}.ant-col-xl-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-xl-push-11{left:45.83333333%}.ant-col-xl-pull-11{right:45.83333333%}.ant-col-xl-offset-11{margin-left:45.83333333%}.ant-col-xl-order-11{order:11}.ant-col-xl-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-xl-push-10{left:41.66666667%}.ant-col-xl-pull-10{right:41.66666667%}.ant-col-xl-offset-10{margin-left:41.66666667%}.ant-col-xl-order-10{order:10}.ant-col-xl-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-xl-push-9{left:37.5%}.ant-col-xl-pull-9{right:37.5%}.ant-col-xl-offset-9{margin-left:37.5%}.ant-col-xl-order-9{order:9}.ant-col-xl-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-xl-push-8{left:33.33333333%}.ant-col-xl-pull-8{right:33.33333333%}.ant-col-xl-offset-8{margin-left:33.33333333%}.ant-col-xl-order-8{order:8}.ant-col-xl-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-xl-push-7{left:29.16666667%}.ant-col-xl-pull-7{right:29.16666667%}.ant-col-xl-offset-7{margin-left:29.16666667%}.ant-col-xl-order-7{order:7}.ant-col-xl-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-xl-push-6{left:25%}.ant-col-xl-pull-6{right:25%}.ant-col-xl-offset-6{margin-left:25%}.ant-col-xl-order-6{order:6}.ant-col-xl-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-xl-push-5{left:20.83333333%}.ant-col-xl-pull-5{right:20.83333333%}.ant-col-xl-offset-5{margin-left:20.83333333%}.ant-col-xl-order-5{order:5}.ant-col-xl-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-xl-push-4{left:16.66666667%}.ant-col-xl-pull-4{right:16.66666667%}.ant-col-xl-offset-4{margin-left:16.66666667%}.ant-col-xl-order-4{order:4}.ant-col-xl-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-xl-push-3{left:12.5%}.ant-col-xl-pull-3{right:12.5%}.ant-col-xl-offset-3{margin-left:12.5%}.ant-col-xl-order-3{order:3}.ant-col-xl-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-xl-push-2{left:8.33333333%}.ant-col-xl-pull-2{right:8.33333333%}.ant-col-xl-offset-2{margin-left:8.33333333%}.ant-col-xl-order-2{order:2}.ant-col-xl-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-xl-push-1{left:4.16666667%}.ant-col-xl-pull-1{right:4.16666667%}.ant-col-xl-offset-1{margin-left:4.16666667%}.ant-col-xl-order-1{order:1}.ant-col-xl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xl-push-0{left:auto}.ant-col-xl-pull-0{right:auto}.ant-col-xl-offset-0{margin-left:0}.ant-col-xl-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-xl-push-0.ant-col-rtl{right:auto}.ant-col-xl-pull-0.ant-col-rtl{left:auto}.ant-col-xl-offset-0.ant-col-rtl{margin-right:0}.ant-col-xl-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-xl-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-xl-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-xl-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-xl-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-xl-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-xl-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-xl-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-xl-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-xl-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-xl-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-xl-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-xl-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-xl-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-xl-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-xl-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-xl-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-xl-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-xl-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-xl-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-xl-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-xl-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-xl-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-xl-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-xl-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-xl-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-xl-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-xl-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-xl-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-xl-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-xl-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-xl-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-xl-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-xl-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-xl-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-xl-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-xl-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-xl-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-xl-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-xl-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-xl-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-xl-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-xl-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-xl-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-xl-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-xl-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-xl-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-xl-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-xl-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-xl-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-xl-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-xl-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-xl-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-xl-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-xl-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-xl-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-xl-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-xl-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-xl-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-xl-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-xl-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-xl-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-xl-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-xl-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-xl-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-xl-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-xl-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-xl-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-xl-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-xl-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-xl-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-xl-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 1600px){.ant-col-xxl-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-xxl-push-24{left:100%}.ant-col-xxl-pull-24{right:100%}.ant-col-xxl-offset-24{margin-left:100%}.ant-col-xxl-order-24{order:24}.ant-col-xxl-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-xxl-push-23{left:95.83333333%}.ant-col-xxl-pull-23{right:95.83333333%}.ant-col-xxl-offset-23{margin-left:95.83333333%}.ant-col-xxl-order-23{order:23}.ant-col-xxl-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-xxl-push-22{left:91.66666667%}.ant-col-xxl-pull-22{right:91.66666667%}.ant-col-xxl-offset-22{margin-left:91.66666667%}.ant-col-xxl-order-22{order:22}.ant-col-xxl-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-xxl-push-21{left:87.5%}.ant-col-xxl-pull-21{right:87.5%}.ant-col-xxl-offset-21{margin-left:87.5%}.ant-col-xxl-order-21{order:21}.ant-col-xxl-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-xxl-push-20{left:83.33333333%}.ant-col-xxl-pull-20{right:83.33333333%}.ant-col-xxl-offset-20{margin-left:83.33333333%}.ant-col-xxl-order-20{order:20}.ant-col-xxl-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-xxl-push-19{left:79.16666667%}.ant-col-xxl-pull-19{right:79.16666667%}.ant-col-xxl-offset-19{margin-left:79.16666667%}.ant-col-xxl-order-19{order:19}.ant-col-xxl-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-xxl-push-18{left:75%}.ant-col-xxl-pull-18{right:75%}.ant-col-xxl-offset-18{margin-left:75%}.ant-col-xxl-order-18{order:18}.ant-col-xxl-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-xxl-push-17{left:70.83333333%}.ant-col-xxl-pull-17{right:70.83333333%}.ant-col-xxl-offset-17{margin-left:70.83333333%}.ant-col-xxl-order-17{order:17}.ant-col-xxl-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-xxl-push-16{left:66.66666667%}.ant-col-xxl-pull-16{right:66.66666667%}.ant-col-xxl-offset-16{margin-left:66.66666667%}.ant-col-xxl-order-16{order:16}.ant-col-xxl-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-xxl-push-15{left:62.5%}.ant-col-xxl-pull-15{right:62.5%}.ant-col-xxl-offset-15{margin-left:62.5%}.ant-col-xxl-order-15{order:15}.ant-col-xxl-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-xxl-push-14{left:58.33333333%}.ant-col-xxl-pull-14{right:58.33333333%}.ant-col-xxl-offset-14{margin-left:58.33333333%}.ant-col-xxl-order-14{order:14}.ant-col-xxl-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-xxl-push-13{left:54.16666667%}.ant-col-xxl-pull-13{right:54.16666667%}.ant-col-xxl-offset-13{margin-left:54.16666667%}.ant-col-xxl-order-13{order:13}.ant-col-xxl-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-xxl-push-12{left:50%}.ant-col-xxl-pull-12{right:50%}.ant-col-xxl-offset-12{margin-left:50%}.ant-col-xxl-order-12{order:12}.ant-col-xxl-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-xxl-push-11{left:45.83333333%}.ant-col-xxl-pull-11{right:45.83333333%}.ant-col-xxl-offset-11{margin-left:45.83333333%}.ant-col-xxl-order-11{order:11}.ant-col-xxl-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-xxl-push-10{left:41.66666667%}.ant-col-xxl-pull-10{right:41.66666667%}.ant-col-xxl-offset-10{margin-left:41.66666667%}.ant-col-xxl-order-10{order:10}.ant-col-xxl-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-xxl-push-9{left:37.5%}.ant-col-xxl-pull-9{right:37.5%}.ant-col-xxl-offset-9{margin-left:37.5%}.ant-col-xxl-order-9{order:9}.ant-col-xxl-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-xxl-push-8{left:33.33333333%}.ant-col-xxl-pull-8{right:33.33333333%}.ant-col-xxl-offset-8{margin-left:33.33333333%}.ant-col-xxl-order-8{order:8}.ant-col-xxl-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-xxl-push-7{left:29.16666667%}.ant-col-xxl-pull-7{right:29.16666667%}.ant-col-xxl-offset-7{margin-left:29.16666667%}.ant-col-xxl-order-7{order:7}.ant-col-xxl-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-xxl-push-6{left:25%}.ant-col-xxl-pull-6{right:25%}.ant-col-xxl-offset-6{margin-left:25%}.ant-col-xxl-order-6{order:6}.ant-col-xxl-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-xxl-push-5{left:20.83333333%}.ant-col-xxl-pull-5{right:20.83333333%}.ant-col-xxl-offset-5{margin-left:20.83333333%}.ant-col-xxl-order-5{order:5}.ant-col-xxl-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-xxl-push-4{left:16.66666667%}.ant-col-xxl-pull-4{right:16.66666667%}.ant-col-xxl-offset-4{margin-left:16.66666667%}.ant-col-xxl-order-4{order:4}.ant-col-xxl-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-xxl-push-3{left:12.5%}.ant-col-xxl-pull-3{right:12.5%}.ant-col-xxl-offset-3{margin-left:12.5%}.ant-col-xxl-order-3{order:3}.ant-col-xxl-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-xxl-push-2{left:8.33333333%}.ant-col-xxl-pull-2{right:8.33333333%}.ant-col-xxl-offset-2{margin-left:8.33333333%}.ant-col-xxl-order-2{order:2}.ant-col-xxl-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-xxl-push-1{left:4.16666667%}.ant-col-xxl-pull-1{right:4.16666667%}.ant-col-xxl-offset-1{margin-left:4.16666667%}.ant-col-xxl-order-1{order:1}.ant-col-xxl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xxl-push-0{left:auto}.ant-col-xxl-pull-0{right:auto}.ant-col-xxl-offset-0{margin-left:0}.ant-col-xxl-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-xxl-push-0.ant-col-rtl{right:auto}.ant-col-xxl-pull-0.ant-col-rtl{left:auto}.ant-col-xxl-offset-0.ant-col-rtl{margin-right:0}.ant-col-xxl-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-xxl-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-xxl-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-xxl-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-xxl-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-xxl-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-xxl-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-xxl-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-xxl-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-xxl-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-xxl-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-xxl-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-xxl-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-xxl-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-xxl-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-xxl-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-xxl-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-xxl-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-xxl-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-xxl-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-xxl-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-xxl-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-xxl-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-xxl-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-xxl-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-xxl-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-xxl-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-xxl-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-xxl-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-xxl-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-xxl-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-xxl-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-xxl-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-xxl-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-xxl-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-xxl-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-xxl-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-xxl-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-xxl-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-xxl-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-xxl-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-xxl-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-xxl-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-xxl-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-xxl-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-xxl-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-xxl-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-xxl-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-xxl-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-xxl-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-xxl-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-xxl-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-xxl-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-xxl-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-xxl-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-xxl-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-xxl-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-xxl-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-xxl-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-xxl-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-xxl-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-xxl-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-xxl-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-xxl-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-xxl-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-xxl-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-xxl-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-xxl-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-xxl-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-xxl-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-xxl-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-xxl-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}@media (min-width: 2000px){.ant-col-xxxl-24{display:block;flex:0 0 100%;max-width:100%}.ant-col-xxxl-push-24{left:100%}.ant-col-xxxl-pull-24{right:100%}.ant-col-xxxl-offset-24{margin-left:100%}.ant-col-xxxl-order-24{order:24}.ant-col-xxxl-23{display:block;flex:0 0 95.83333333%;max-width:95.83333333%}.ant-col-xxxl-push-23{left:95.83333333%}.ant-col-xxxl-pull-23{right:95.83333333%}.ant-col-xxxl-offset-23{margin-left:95.83333333%}.ant-col-xxxl-order-23{order:23}.ant-col-xxxl-22{display:block;flex:0 0 91.66666667%;max-width:91.66666667%}.ant-col-xxxl-push-22{left:91.66666667%}.ant-col-xxxl-pull-22{right:91.66666667%}.ant-col-xxxl-offset-22{margin-left:91.66666667%}.ant-col-xxxl-order-22{order:22}.ant-col-xxxl-21{display:block;flex:0 0 87.5%;max-width:87.5%}.ant-col-xxxl-push-21{left:87.5%}.ant-col-xxxl-pull-21{right:87.5%}.ant-col-xxxl-offset-21{margin-left:87.5%}.ant-col-xxxl-order-21{order:21}.ant-col-xxxl-20{display:block;flex:0 0 83.33333333%;max-width:83.33333333%}.ant-col-xxxl-push-20{left:83.33333333%}.ant-col-xxxl-pull-20{right:83.33333333%}.ant-col-xxxl-offset-20{margin-left:83.33333333%}.ant-col-xxxl-order-20{order:20}.ant-col-xxxl-19{display:block;flex:0 0 79.16666667%;max-width:79.16666667%}.ant-col-xxxl-push-19{left:79.16666667%}.ant-col-xxxl-pull-19{right:79.16666667%}.ant-col-xxxl-offset-19{margin-left:79.16666667%}.ant-col-xxxl-order-19{order:19}.ant-col-xxxl-18{display:block;flex:0 0 75%;max-width:75%}.ant-col-xxxl-push-18{left:75%}.ant-col-xxxl-pull-18{right:75%}.ant-col-xxxl-offset-18{margin-left:75%}.ant-col-xxxl-order-18{order:18}.ant-col-xxxl-17{display:block;flex:0 0 70.83333333%;max-width:70.83333333%}.ant-col-xxxl-push-17{left:70.83333333%}.ant-col-xxxl-pull-17{right:70.83333333%}.ant-col-xxxl-offset-17{margin-left:70.83333333%}.ant-col-xxxl-order-17{order:17}.ant-col-xxxl-16{display:block;flex:0 0 66.66666667%;max-width:66.66666667%}.ant-col-xxxl-push-16{left:66.66666667%}.ant-col-xxxl-pull-16{right:66.66666667%}.ant-col-xxxl-offset-16{margin-left:66.66666667%}.ant-col-xxxl-order-16{order:16}.ant-col-xxxl-15{display:block;flex:0 0 62.5%;max-width:62.5%}.ant-col-xxxl-push-15{left:62.5%}.ant-col-xxxl-pull-15{right:62.5%}.ant-col-xxxl-offset-15{margin-left:62.5%}.ant-col-xxxl-order-15{order:15}.ant-col-xxxl-14{display:block;flex:0 0 58.33333333%;max-width:58.33333333%}.ant-col-xxxl-push-14{left:58.33333333%}.ant-col-xxxl-pull-14{right:58.33333333%}.ant-col-xxxl-offset-14{margin-left:58.33333333%}.ant-col-xxxl-order-14{order:14}.ant-col-xxxl-13{display:block;flex:0 0 54.16666667%;max-width:54.16666667%}.ant-col-xxxl-push-13{left:54.16666667%}.ant-col-xxxl-pull-13{right:54.16666667%}.ant-col-xxxl-offset-13{margin-left:54.16666667%}.ant-col-xxxl-order-13{order:13}.ant-col-xxxl-12{display:block;flex:0 0 50%;max-width:50%}.ant-col-xxxl-push-12{left:50%}.ant-col-xxxl-pull-12{right:50%}.ant-col-xxxl-offset-12{margin-left:50%}.ant-col-xxxl-order-12{order:12}.ant-col-xxxl-11{display:block;flex:0 0 45.83333333%;max-width:45.83333333%}.ant-col-xxxl-push-11{left:45.83333333%}.ant-col-xxxl-pull-11{right:45.83333333%}.ant-col-xxxl-offset-11{margin-left:45.83333333%}.ant-col-xxxl-order-11{order:11}.ant-col-xxxl-10{display:block;flex:0 0 41.66666667%;max-width:41.66666667%}.ant-col-xxxl-push-10{left:41.66666667%}.ant-col-xxxl-pull-10{right:41.66666667%}.ant-col-xxxl-offset-10{margin-left:41.66666667%}.ant-col-xxxl-order-10{order:10}.ant-col-xxxl-9{display:block;flex:0 0 37.5%;max-width:37.5%}.ant-col-xxxl-push-9{left:37.5%}.ant-col-xxxl-pull-9{right:37.5%}.ant-col-xxxl-offset-9{margin-left:37.5%}.ant-col-xxxl-order-9{order:9}.ant-col-xxxl-8{display:block;flex:0 0 33.33333333%;max-width:33.33333333%}.ant-col-xxxl-push-8{left:33.33333333%}.ant-col-xxxl-pull-8{right:33.33333333%}.ant-col-xxxl-offset-8{margin-left:33.33333333%}.ant-col-xxxl-order-8{order:8}.ant-col-xxxl-7{display:block;flex:0 0 29.16666667%;max-width:29.16666667%}.ant-col-xxxl-push-7{left:29.16666667%}.ant-col-xxxl-pull-7{right:29.16666667%}.ant-col-xxxl-offset-7{margin-left:29.16666667%}.ant-col-xxxl-order-7{order:7}.ant-col-xxxl-6{display:block;flex:0 0 25%;max-width:25%}.ant-col-xxxl-push-6{left:25%}.ant-col-xxxl-pull-6{right:25%}.ant-col-xxxl-offset-6{margin-left:25%}.ant-col-xxxl-order-6{order:6}.ant-col-xxxl-5{display:block;flex:0 0 20.83333333%;max-width:20.83333333%}.ant-col-xxxl-push-5{left:20.83333333%}.ant-col-xxxl-pull-5{right:20.83333333%}.ant-col-xxxl-offset-5{margin-left:20.83333333%}.ant-col-xxxl-order-5{order:5}.ant-col-xxxl-4{display:block;flex:0 0 16.66666667%;max-width:16.66666667%}.ant-col-xxxl-push-4{left:16.66666667%}.ant-col-xxxl-pull-4{right:16.66666667%}.ant-col-xxxl-offset-4{margin-left:16.66666667%}.ant-col-xxxl-order-4{order:4}.ant-col-xxxl-3{display:block;flex:0 0 12.5%;max-width:12.5%}.ant-col-xxxl-push-3{left:12.5%}.ant-col-xxxl-pull-3{right:12.5%}.ant-col-xxxl-offset-3{margin-left:12.5%}.ant-col-xxxl-order-3{order:3}.ant-col-xxxl-2{display:block;flex:0 0 8.33333333%;max-width:8.33333333%}.ant-col-xxxl-push-2{left:8.33333333%}.ant-col-xxxl-pull-2{right:8.33333333%}.ant-col-xxxl-offset-2{margin-left:8.33333333%}.ant-col-xxxl-order-2{order:2}.ant-col-xxxl-1{display:block;flex:0 0 4.16666667%;max-width:4.16666667%}.ant-col-xxxl-push-1{left:4.16666667%}.ant-col-xxxl-pull-1{right:4.16666667%}.ant-col-xxxl-offset-1{margin-left:4.16666667%}.ant-col-xxxl-order-1{order:1}.ant-col-xxxl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xxxl-push-0{left:auto}.ant-col-xxxl-pull-0{right:auto}.ant-col-xxxl-offset-0{margin-left:0}.ant-col-xxxl-order-0{order:0}.ant-col-push-0.ant-col-rtl{right:auto}.ant-col-pull-0.ant-col-rtl{left:auto}.ant-col-xxxl-push-0.ant-col-rtl{right:auto}.ant-col-xxxl-pull-0.ant-col-rtl{left:auto}.ant-col-xxxl-offset-0.ant-col-rtl{margin-right:0}.ant-col-xxxl-push-1.ant-col-rtl{right:4.16666667%;left:auto}.ant-col-xxxl-pull-1.ant-col-rtl{right:auto;left:4.16666667%}.ant-col-xxxl-offset-1.ant-col-rtl{margin-right:4.16666667%;margin-left:0}.ant-col-xxxl-push-2.ant-col-rtl{right:8.33333333%;left:auto}.ant-col-xxxl-pull-2.ant-col-rtl{right:auto;left:8.33333333%}.ant-col-xxxl-offset-2.ant-col-rtl{margin-right:8.33333333%;margin-left:0}.ant-col-xxxl-push-3.ant-col-rtl{right:12.5%;left:auto}.ant-col-xxxl-pull-3.ant-col-rtl{right:auto;left:12.5%}.ant-col-xxxl-offset-3.ant-col-rtl{margin-right:12.5%;margin-left:0}.ant-col-xxxl-push-4.ant-col-rtl{right:16.66666667%;left:auto}.ant-col-xxxl-pull-4.ant-col-rtl{right:auto;left:16.66666667%}.ant-col-xxxl-offset-4.ant-col-rtl{margin-right:16.66666667%;margin-left:0}.ant-col-xxxl-push-5.ant-col-rtl{right:20.83333333%;left:auto}.ant-col-xxxl-pull-5.ant-col-rtl{right:auto;left:20.83333333%}.ant-col-xxxl-offset-5.ant-col-rtl{margin-right:20.83333333%;margin-left:0}.ant-col-xxxl-push-6.ant-col-rtl{right:25%;left:auto}.ant-col-xxxl-pull-6.ant-col-rtl{right:auto;left:25%}.ant-col-xxxl-offset-6.ant-col-rtl{margin-right:25%;margin-left:0}.ant-col-xxxl-push-7.ant-col-rtl{right:29.16666667%;left:auto}.ant-col-xxxl-pull-7.ant-col-rtl{right:auto;left:29.16666667%}.ant-col-xxxl-offset-7.ant-col-rtl{margin-right:29.16666667%;margin-left:0}.ant-col-xxxl-push-8.ant-col-rtl{right:33.33333333%;left:auto}.ant-col-xxxl-pull-8.ant-col-rtl{right:auto;left:33.33333333%}.ant-col-xxxl-offset-8.ant-col-rtl{margin-right:33.33333333%;margin-left:0}.ant-col-xxxl-push-9.ant-col-rtl{right:37.5%;left:auto}.ant-col-xxxl-pull-9.ant-col-rtl{right:auto;left:37.5%}.ant-col-xxxl-offset-9.ant-col-rtl{margin-right:37.5%;margin-left:0}.ant-col-xxxl-push-10.ant-col-rtl{right:41.66666667%;left:auto}.ant-col-xxxl-pull-10.ant-col-rtl{right:auto;left:41.66666667%}.ant-col-xxxl-offset-10.ant-col-rtl{margin-right:41.66666667%;margin-left:0}.ant-col-xxxl-push-11.ant-col-rtl{right:45.83333333%;left:auto}.ant-col-xxxl-pull-11.ant-col-rtl{right:auto;left:45.83333333%}.ant-col-xxxl-offset-11.ant-col-rtl{margin-right:45.83333333%;margin-left:0}.ant-col-xxxl-push-12.ant-col-rtl{right:50%;left:auto}.ant-col-xxxl-pull-12.ant-col-rtl{right:auto;left:50%}.ant-col-xxxl-offset-12.ant-col-rtl{margin-right:50%;margin-left:0}.ant-col-xxxl-push-13.ant-col-rtl{right:54.16666667%;left:auto}.ant-col-xxxl-pull-13.ant-col-rtl{right:auto;left:54.16666667%}.ant-col-xxxl-offset-13.ant-col-rtl{margin-right:54.16666667%;margin-left:0}.ant-col-xxxl-push-14.ant-col-rtl{right:58.33333333%;left:auto}.ant-col-xxxl-pull-14.ant-col-rtl{right:auto;left:58.33333333%}.ant-col-xxxl-offset-14.ant-col-rtl{margin-right:58.33333333%;margin-left:0}.ant-col-xxxl-push-15.ant-col-rtl{right:62.5%;left:auto}.ant-col-xxxl-pull-15.ant-col-rtl{right:auto;left:62.5%}.ant-col-xxxl-offset-15.ant-col-rtl{margin-right:62.5%;margin-left:0}.ant-col-xxxl-push-16.ant-col-rtl{right:66.66666667%;left:auto}.ant-col-xxxl-pull-16.ant-col-rtl{right:auto;left:66.66666667%}.ant-col-xxxl-offset-16.ant-col-rtl{margin-right:66.66666667%;margin-left:0}.ant-col-xxxl-push-17.ant-col-rtl{right:70.83333333%;left:auto}.ant-col-xxxl-pull-17.ant-col-rtl{right:auto;left:70.83333333%}.ant-col-xxxl-offset-17.ant-col-rtl{margin-right:70.83333333%;margin-left:0}.ant-col-xxxl-push-18.ant-col-rtl{right:75%;left:auto}.ant-col-xxxl-pull-18.ant-col-rtl{right:auto;left:75%}.ant-col-xxxl-offset-18.ant-col-rtl{margin-right:75%;margin-left:0}.ant-col-xxxl-push-19.ant-col-rtl{right:79.16666667%;left:auto}.ant-col-xxxl-pull-19.ant-col-rtl{right:auto;left:79.16666667%}.ant-col-xxxl-offset-19.ant-col-rtl{margin-right:79.16666667%;margin-left:0}.ant-col-xxxl-push-20.ant-col-rtl{right:83.33333333%;left:auto}.ant-col-xxxl-pull-20.ant-col-rtl{right:auto;left:83.33333333%}.ant-col-xxxl-offset-20.ant-col-rtl{margin-right:83.33333333%;margin-left:0}.ant-col-xxxl-push-21.ant-col-rtl{right:87.5%;left:auto}.ant-col-xxxl-pull-21.ant-col-rtl{right:auto;left:87.5%}.ant-col-xxxl-offset-21.ant-col-rtl{margin-right:87.5%;margin-left:0}.ant-col-xxxl-push-22.ant-col-rtl{right:91.66666667%;left:auto}.ant-col-xxxl-pull-22.ant-col-rtl{right:auto;left:91.66666667%}.ant-col-xxxl-offset-22.ant-col-rtl{margin-right:91.66666667%;margin-left:0}.ant-col-xxxl-push-23.ant-col-rtl{right:95.83333333%;left:auto}.ant-col-xxxl-pull-23.ant-col-rtl{right:auto;left:95.83333333%}.ant-col-xxxl-offset-23.ant-col-rtl{margin-right:95.83333333%;margin-left:0}.ant-col-xxxl-push-24.ant-col-rtl{right:100%;left:auto}.ant-col-xxxl-pull-24.ant-col-rtl{right:auto;left:100%}.ant-col-xxxl-offset-24.ant-col-rtl{margin-right:100%;margin-left:0}}.ant-row-rtl{direction:rtl} diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-808a3f6b.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-808a3f6b.js new file mode 100644 index 0000000000000000000000000000000000000000..caa3297333b05442d7159af90fefb003befe6511 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-808a3f6b.js @@ -0,0 +1 @@ +import{d as x,$,aI as g,dl as b,r as w,S as p,T as d,U as a,c as r,a1 as i,V as u,W as n,X as I,a6 as B,a2 as m,x as V,y as _,z as v,ah as W,ai as D,dm as N,Z as R}from"./index-93a218ca.js";/* empty css */const F={class:"container"},L={class:"actions"},T={class:"uni-desc"},U={class:"snapshot"},q=x({__name:"index",props:{tabIdx:{},paneIdx:{},id:{},paneKey:{}},setup(z){const h=$(),t=g(),f=e=>{h.tabList=V(e.tabs)},k=b(async e=>{await N(`workspace_snapshot_${e.id}`),t.snapshots=t.snapshots.filter(c=>c.id!==e.id),_.success(v("deleteSuccess"))}),o=w(""),y=async()=>{if(!o.value){_.error(v("nameRequired"));return}const e=t.createSnapshot(o.value);await t.addSnapshot(e),_.success(v("saveCompleted"))};return(e,c)=>{const C=W,l=D;return p(),d("div",F,[a("div",L,[r(C,{value:o.value,"onUpdate:value":c[0]||(c[0]=s=>o.value=s),placeholder:e.$t("name"),style:{"max-width":"300px"}},null,8,["value","placeholder"]),r(l,{type:"primary",onClick:y},{default:i(()=>[u(n(e.$t("saveWorkspaceSnapshot")),1)]),_:1})]),a("p",T,n(e.$t("WorkspaceSnapshotDesc")),1),a("ul",U,[(p(!0),d(I,null,B(m(t).snapshots,s=>(p(),d("li",{key:s.id},[a("div",null,[a("span",null,n(s.name),1)]),a("div",null,[r(l,{onClick:S=>f(s)},{default:i(()=>[u(n(e.$t("restore")),1)]),_:2},1032,["onClick"]),r(l,{onClick:S=>m(k)(s)},{default:i(()=>[u(n(e.$t("remove")),1)]),_:2},1032,["onClick"])])]))),128))])])}}});const G=R(q,[["__scopeId","data-v-2c44013c"]]);export{G as default}; diff --git a/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-93a218ca.js b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-93a218ca.js new file mode 100644 index 0000000000000000000000000000000000000000..051a3912fea2e44acb5d15b3bc7460c4497f9ae7 --- /dev/null +++ b/extensions/sd-webui-infinite-image-browsing/vue/dist/assets/index-93a218ca.js @@ -0,0 +1,218 @@ +(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))r(a);new MutationObserver(a=>{for(const i of a)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(a){const i={};return a.integrity&&(i.integrity=a.integrity),a.referrerPolicy&&(i.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?i.credentials="include":a.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(a){if(a.ep)return;a.ep=!0;const i=n(a);fetch(a.href,i)}})();function Qf(t,e){const n=Object.create(null),r=t.split(",");for(let a=0;a!!n[a.toLowerCase()]:a=>!!n[a]}const ut={},ii=[],Un=()=>{},Rx=()=>!1,$x=/^on[^a-z]/,jl=t=>$x.test(t),ed=t=>t.startsWith("onUpdate:"),bt=Object.assign,td=(t,e)=>{const n=t.indexOf(e);n>-1&&t.splice(n,1)},Lx=Object.prototype.hasOwnProperty,Ue=(t,e)=>Lx.call(t,e),Pe=Array.isArray,oi=t=>zl(t)==="[object Map]",qb=t=>zl(t)==="[object Set]",Le=t=>typeof t=="function",mt=t=>typeof t=="string",nd=t=>typeof t=="symbol",ot=t=>t!==null&&typeof t=="object",Yb=t=>ot(t)&&Le(t.then)&&Le(t.catch),Xb=Object.prototype.toString,zl=t=>Xb.call(t),Dx=t=>zl(t).slice(8,-1),Jb=t=>zl(t)==="[object Object]",rd=t=>mt(t)&&t!=="NaN"&&t[0]!=="-"&&""+parseInt(t,10)===t,Hs=Qf(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Wl=t=>{const e=Object.create(null);return n=>e[n]||(e[n]=t(n))},Fx=/-(\w)/g,Kn=Wl(t=>t.replace(Fx,(e,n)=>n?n.toUpperCase():"")),Bx=/\B([A-Z])/g,$a=Wl(t=>t.replace(Bx,"-$1").toLowerCase()),Hl=Wl(t=>t.charAt(0).toUpperCase()+t.slice(1)),Vs=Wl(t=>t?`on${Hl(t)}`:""),wo=(t,e)=>!Object.is(t,e),Du=(t,e)=>{for(let n=0;n{Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value:n})},jx=t=>{const e=parseFloat(t);return isNaN(e)?t:e},zx=t=>{const e=mt(t)?Number(t):NaN;return isNaN(e)?t:e};let Ep;const Mc=()=>Ep||(Ep=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function kr(t){if(Pe(t)){const e={};for(let n=0;n{if(n){const r=n.split(Hx);r.length>1&&(e[r[0].trim()]=r[1].trim())}}),e}function gn(t){let e="";if(mt(t))e=t;else if(Pe(t))for(let n=0;nmt(t)?t:t==null?"":Pe(t)||ot(t)&&(t.toString===Xb||!Le(t.toString))?JSON.stringify(t,Qb,2):String(t),Qb=(t,e)=>e&&e.__v_isRef?Qb(t,e.value):oi(e)?{[`Map(${e.size})`]:[...e.entries()].reduce((n,[r,a])=>(n[`${r} =>`]=a,n),{})}:qb(e)?{[`Set(${e.size})`]:[...e.values()]}:ot(e)&&!Pe(e)&&!Jb(e)?String(e):e;let hn;class e0{constructor(e=!1){this.detached=e,this._active=!0,this.effects=[],this.cleanups=[],this.parent=hn,!e&&hn&&(this.index=(hn.scopes||(hn.scopes=[])).push(this)-1)}get active(){return this._active}run(e){if(this._active){const n=hn;try{return hn=this,e()}finally{hn=n}}}on(){hn=this}off(){hn=this.parent}stop(e){if(this._active){let n,r;for(n=0,r=this.effects.length;n{const e=new Set(t);return e.w=0,e.n=0,e},t0=t=>(t.w&ra)>0,n0=t=>(t.n&ra)>0,Yx=({deps:t})=>{if(t.length)for(let e=0;e{const{deps:e}=t;if(e.length){let n=0;for(let r=0;r{(c==="length"||c>=l)&&s.push(u)})}else switch(n!==void 0&&s.push(o.get(n)),e){case"add":Pe(t)?rd(n)&&s.push(o.get("length")):(s.push(o.get(Ia)),oi(t)&&s.push(o.get(Nc)));break;case"delete":Pe(t)||(s.push(o.get(Ia)),oi(t)&&s.push(o.get(Nc)));break;case"set":oi(t)&&s.push(o.get(Ia));break}if(s.length===1)s[0]&&Rc(s[0]);else{const l=[];for(const u of s)u&&l.push(...u);Rc(od(l))}}function Rc(t,e){const n=Pe(t)?t:[...t];for(const r of n)r.computed&&Pp(r);for(const r of n)r.computed||Pp(r)}function Pp(t,e){(t!==zn||t.allowRecurse)&&(t.scheduler?t.scheduler():t.run())}function Jx(t,e){var n;return(n=ol.get(t))==null?void 0:n.get(e)}const Zx=Qf("__proto__,__v_isRef,__isVue"),i0=new Set(Object.getOwnPropertyNames(Symbol).filter(t=>t!=="arguments"&&t!=="caller").map(t=>Symbol[t]).filter(nd)),Qx=ld(),eE=ld(!1,!0),tE=ld(!0),Tp=nE();function nE(){const t={};return["includes","indexOf","lastIndexOf"].forEach(e=>{t[e]=function(...n){const r=Me(this);for(let i=0,o=this.length;i{t[e]=function(...n){Ii();const r=Me(this)[e].apply(this,n);return Ai(),r}}),t}function rE(t){const e=Me(this);return un(e,"has",t),e.hasOwnProperty(t)}function ld(t=!1,e=!1){return function(r,a,i){if(a==="__v_isReactive")return!t;if(a==="__v_isReadonly")return t;if(a==="__v_isShallow")return e;if(a==="__v_raw"&&i===(t?e?bE:c0:e?u0:l0).get(r))return r;const o=Pe(r);if(!t){if(o&&Ue(Tp,a))return Reflect.get(Tp,a,i);if(a==="hasOwnProperty")return rE}const s=Reflect.get(r,a,i);return(nd(a)?i0.has(a):Zx(a))||(t||un(r,"get",a),e)?s:it(s)?o&&rd(a)?s:s.value:ot(s)?t?Kl(s):nt(s):s}}const aE=o0(),iE=o0(!0);function o0(t=!1){return function(n,r,a,i){let o=n[r];if(vi(o)&&it(o)&&!it(a))return!1;if(!t&&(!sl(a)&&!vi(a)&&(o=Me(o),a=Me(a)),!Pe(n)&&it(o)&&!it(a)))return o.value=a,!0;const s=Pe(n)&&rd(r)?Number(r)t,Ul=t=>Reflect.getPrototypeOf(t);function vs(t,e,n=!1,r=!1){t=t.__v_raw;const a=Me(t),i=Me(e);n||(e!==i&&un(a,"get",e),un(a,"get",i));const{has:o}=Ul(a),s=r?ud:n?dd:_o;if(o.call(a,e))return s(t.get(e));if(o.call(a,i))return s(t.get(i));t!==a&&t.get(e)}function ps(t,e=!1){const n=this.__v_raw,r=Me(n),a=Me(t);return e||(t!==a&&un(r,"has",t),un(r,"has",a)),t===a?n.has(t):n.has(t)||n.has(a)}function hs(t,e=!1){return t=t.__v_raw,!e&&un(Me(t),"iterate",Ia),Reflect.get(t,"size",t)}function Ip(t){t=Me(t);const e=Me(this);return Ul(e).has.call(e,t)||(e.add(t),Nr(e,"add",t,t)),this}function Ap(t,e){e=Me(e);const n=Me(this),{has:r,get:a}=Ul(n);let i=r.call(n,t);i||(t=Me(t),i=r.call(n,t));const o=a.call(n,t);return n.set(t,e),i?wo(e,o)&&Nr(n,"set",t,e):Nr(n,"add",t,e),this}function Mp(t){const e=Me(this),{has:n,get:r}=Ul(e);let a=n.call(e,t);a||(t=Me(t),a=n.call(e,t)),r&&r.call(e,t);const i=e.delete(t);return a&&Nr(e,"delete",t,void 0),i}function kp(){const t=Me(this),e=t.size!==0,n=t.clear();return e&&Nr(t,"clear",void 0,void 0),n}function ms(t,e){return function(r,a){const i=this,o=i.__v_raw,s=Me(o),l=e?ud:t?dd:_o;return!t&&un(s,"iterate",Ia),o.forEach((u,c)=>r.call(a,l(u),l(c),i))}}function gs(t,e,n){return function(...r){const a=this.__v_raw,i=Me(a),o=oi(i),s=t==="entries"||t===Symbol.iterator&&o,l=t==="keys"&&o,u=a[t](...r),c=n?ud:e?dd:_o;return!e&&un(i,"iterate",l?Nc:Ia),{next(){const{value:d,done:v}=u.next();return v?{value:d,done:v}:{value:s?[c(d[0]),c(d[1])]:c(d),done:v}},[Symbol.iterator](){return this}}}}function Br(t){return function(...e){return t==="delete"?!1:this}}function fE(){const t={get(i){return vs(this,i)},get size(){return hs(this)},has:ps,add:Ip,set:Ap,delete:Mp,clear:kp,forEach:ms(!1,!1)},e={get(i){return vs(this,i,!1,!0)},get size(){return hs(this)},has:ps,add:Ip,set:Ap,delete:Mp,clear:kp,forEach:ms(!1,!0)},n={get(i){return vs(this,i,!0)},get size(){return hs(this,!0)},has(i){return ps.call(this,i,!0)},add:Br("add"),set:Br("set"),delete:Br("delete"),clear:Br("clear"),forEach:ms(!0,!1)},r={get(i){return vs(this,i,!0,!0)},get size(){return hs(this,!0)},has(i){return ps.call(this,i,!0)},add:Br("add"),set:Br("set"),delete:Br("delete"),clear:Br("clear"),forEach:ms(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(i=>{t[i]=gs(i,!1,!1),n[i]=gs(i,!0,!1),e[i]=gs(i,!1,!0),r[i]=gs(i,!0,!0)}),[t,n,e,r]}const[dE,vE,pE,hE]=fE();function cd(t,e){const n=e?t?hE:pE:t?vE:dE;return(r,a,i)=>a==="__v_isReactive"?!t:a==="__v_isReadonly"?t:a==="__v_raw"?r:Reflect.get(Ue(n,a)&&a in r?n:r,a,i)}const mE={get:cd(!1,!1)},gE={get:cd(!1,!0)},yE={get:cd(!0,!1)},l0=new WeakMap,u0=new WeakMap,c0=new WeakMap,bE=new WeakMap;function wE(t){switch(t){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function _E(t){return t.__v_skip||!Object.isExtensible(t)?0:wE(Dx(t))}function nt(t){return vi(t)?t:fd(t,!1,s0,mE,l0)}function CE(t){return fd(t,!1,cE,gE,u0)}function Kl(t){return fd(t,!0,uE,yE,c0)}function fd(t,e,n,r,a){if(!ot(t)||t.__v_raw&&!(e&&t.__v_isReactive))return t;const i=a.get(t);if(i)return i;const o=_E(t);if(o===0)return t;const s=new Proxy(t,o===2?r:n);return a.set(t,s),s}function Ir(t){return vi(t)?Ir(t.__v_raw):!!(t&&t.__v_isReactive)}function vi(t){return!!(t&&t.__v_isReadonly)}function sl(t){return!!(t&&t.__v_isShallow)}function f0(t){return Ir(t)||vi(t)}function Me(t){const e=t&&t.__v_raw;return e?Me(e):t}function Gl(t){return il(t,"__v_skip",!0),t}const _o=t=>ot(t)?nt(t):t,dd=t=>ot(t)?Kl(t):t;function vd(t){Zr&&zn&&(t=Me(t),a0(t.dep||(t.dep=od())))}function pd(t,e){t=Me(t);const n=t.dep;n&&Rc(n)}function it(t){return!!(t&&t.__v_isRef===!0)}function z(t){return d0(t,!1)}function Wn(t){return d0(t,!0)}function d0(t,e){return it(t)?t:new SE(t,e)}class SE{constructor(e,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?e:Me(e),this._value=n?e:_o(e)}get value(){return vd(this),this._value}set value(e){const n=this.__v_isShallow||sl(e)||vi(e);e=n?e:Me(e),wo(e,this._rawValue)&&(this._rawValue=e,this._value=n?e:_o(e),pd(this))}}function xe(t){return it(t)?t.value:t}const xE={get:(t,e,n)=>xe(Reflect.get(t,e,n)),set:(t,e,n,r)=>{const a=t[e];return it(a)&&!it(n)?(a.value=n,!0):Reflect.set(t,e,n,r)}};function v0(t){return Ir(t)?t:new Proxy(t,xE)}class EE{constructor(e){this.dep=void 0,this.__v_isRef=!0;const{get:n,set:r}=e(()=>vd(this),()=>pd(this));this._get=n,this._set=r}get value(){return this._get()}set value(e){this._set(e)}}function OE(t){return new EE(t)}function p0(t){const e=Pe(t)?new Array(t.length):{};for(const n in t)e[n]=h0(t,n);return e}class PE{constructor(e,n,r){this._object=e,this._key=n,this._defaultValue=r,this.__v_isRef=!0}get value(){const e=this._object[this._key];return e===void 0?this._defaultValue:e}set value(e){this._object[this._key]=e}get dep(){return Jx(Me(this._object),this._key)}}class TE{constructor(e){this._getter=e,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function Jt(t,e,n){return it(t)?t:Le(t)?new TE(t):ot(t)&&arguments.length>1?h0(t,e,n):z(t)}function h0(t,e,n){const r=t[e];return it(r)?r:new PE(t,e,n)}class IE{constructor(e,n,r,a){this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this._dirty=!0,this.effect=new sd(e,()=>{this._dirty||(this._dirty=!0,pd(this))}),this.effect.computed=this,this.effect.active=this._cacheable=!a,this.__v_isReadonly=r}get value(){const e=Me(this);return vd(e),(e._dirty||!e._cacheable)&&(e._dirty=!1,e._value=e.effect.run()),e._value}set value(e){this._setter(e)}}function AE(t,e,n=!1){let r,a;const i=Le(t);return i?(r=t,a=Un):(r=t.get,a=t.set),new IE(r,a,i||!a,n)}function Qr(t,e,n,r){let a;try{a=r?t(...r):t()}catch(i){qo(i,e,n)}return a}function Mn(t,e,n,r){if(Le(t)){const i=Qr(t,e,n,r);return i&&Yb(i)&&i.catch(o=>{qo(o,e,n)}),i}const a=[];for(let i=0;i>>1;So(Vt[r])lr&&Vt.splice(e,1)}function RE(t){Pe(t)?si.push(...t):(!Tr||!Tr.includes(t,t.allowRecurse?wa+1:wa))&&si.push(t),g0()}function Np(t,e=Co?lr+1:0){for(;eSo(n)-So(r)),wa=0;wat.id==null?1/0:t.id,$E=(t,e)=>{const n=So(t)-So(e);if(n===0){if(t.pre&&!e.pre)return-1;if(e.pre&&!t.pre)return 1}return n};function b0(t){$c=!1,Co=!0,Vt.sort($E);const e=Un;try{for(lr=0;lrmt(h)?h.trim():h)),d&&(a=n.map(jx))}let s,l=r[s=Vs(e)]||r[s=Vs(Kn(e))];!l&&i&&(l=r[s=Vs($a(e))]),l&&Mn(l,t,6,a);const u=r[s+"Once"];if(u){if(!t.emitted)t.emitted={};else if(t.emitted[s])return;t.emitted[s]=!0,Mn(u,t,6,a)}}function w0(t,e,n=!1){const r=e.emitsCache,a=r.get(t);if(a!==void 0)return a;const i=t.emits;let o={},s=!1;if(!Le(t)){const l=u=>{const c=w0(u,e,!0);c&&(s=!0,bt(o,c))};!n&&e.mixins.length&&e.mixins.forEach(l),t.extends&&l(t.extends),t.mixins&&t.mixins.forEach(l)}return!i&&!s?(ot(t)&&r.set(t,null),null):(Pe(i)?i.forEach(l=>o[l]=null):bt(o,i),ot(t)&&r.set(t,o),o)}function Yl(t,e){return!t||!jl(e)?!1:(e=e.slice(2).replace(/Once$/,""),Ue(t,e[0].toLowerCase()+e.slice(1))||Ue(t,$a(e))||Ue(t,e))}let $t=null,Xl=null;function ll(t){const e=$t;return $t=t,Xl=t&&t.type.__scopeId||null,e}function _0(t){Xl=t}function C0(){Xl=null}const DE=t=>Et;function Et(t,e=$t,n){if(!e||t._n)return t;const r=(...a)=>{r._d&&Up(-1);const i=ll(e);let o;try{o=t(...a)}finally{ll(i),r._d&&Up(1)}return o};return r._n=!0,r._c=!0,r._d=!0,r}function Fu(t){const{type:e,vnode:n,proxy:r,withProxy:a,props:i,propsOptions:[o],slots:s,attrs:l,emit:u,render:c,renderCache:d,data:v,setupState:h,ctx:f,inheritAttrs:p}=t;let g,m;const y=ll(t);try{if(n.shapeFlag&4){const w=a||r;g=or(c.call(w,w,d,i,h,v,f)),m=l}else{const w=e;g=or(w.length>1?w(i,{attrs:l,slots:s,emit:u}):w(i,null)),m=e.props?l:FE(l)}}catch(w){uo.length=0,qo(w,t,1),g=x(yn)}let b=g;if(m&&p!==!1){const w=Object.keys(m),{shapeFlag:_}=b;w.length&&_&7&&(o&&w.some(ed)&&(m=BE(m,o)),b=Gn(b,m))}return n.dirs&&(b=Gn(b),b.dirs=b.dirs?b.dirs.concat(n.dirs):n.dirs),n.transition&&(b.transition=n.transition),g=b,ll(y),g}const FE=t=>{let e;for(const n in t)(n==="class"||n==="style"||jl(n))&&((e||(e={}))[n]=t[n]);return e},BE=(t,e)=>{const n={};for(const r in t)(!ed(r)||!(r.slice(9)in e))&&(n[r]=t[r]);return n};function jE(t,e,n){const{props:r,children:a,component:i}=t,{props:o,children:s,patchFlag:l}=e,u=i.emitsOptions;if(e.dirs||e.transition)return!0;if(n&&l>=0){if(l&1024)return!0;if(l&16)return r?Rp(r,o,u):!!o;if(l&8){const c=e.dynamicProps;for(let d=0;dt.__isSuspense;function HE(t,e){e&&e.pendingBranch?Pe(t)?e.effects.push(...t):e.effects.push(t):RE(t)}function dt(t,e){return Jl(t,null,e)}function VE(t,e){return Jl(t,null,{flush:"post"})}const ys={};function ve(t,e,n){return Jl(t,e,n)}function Jl(t,e,{immediate:n,deep:r,flush:a,onTrack:i,onTrigger:o}=ut){var s;const l=Vl()===((s=St)==null?void 0:s.scope)?St:null;let u,c=!1,d=!1;if(it(t)?(u=()=>t.value,c=sl(t)):Ir(t)?(u=()=>t,r=!0):Pe(t)?(d=!0,c=t.some(w=>Ir(w)||sl(w)),u=()=>t.map(w=>{if(it(w))return w.value;if(Ir(w))return Oa(w);if(Le(w))return Qr(w,l,2)})):Le(t)?e?u=()=>Qr(t,l,2):u=()=>{if(!(l&&l.isUnmounted))return v&&v(),Mn(t,l,3,[h])}:u=Un,e&&r){const w=u;u=()=>Oa(w())}let v,h=w=>{v=y.onStop=()=>{Qr(w,l,4)}},f;if(hi)if(h=Un,e?n&&Mn(e,l,3,[u(),d?[]:void 0,h]):u(),a==="sync"){const w=DO();f=w.__watcherHandles||(w.__watcherHandles=[])}else return Un;let p=d?new Array(t.length).fill(ys):ys;const g=()=>{if(y.active)if(e){const w=y.run();(r||c||(d?w.some((_,C)=>wo(_,p[C])):wo(w,p)))&&(v&&v(),Mn(e,l,3,[w,p===ys?void 0:d&&p[0]===ys?[]:p,h]),p=w)}else y.run()};g.allowRecurse=!!e;let m;a==="sync"?m=g:a==="post"?m=()=>on(g,l&&l.suspense):(g.pre=!0,l&&(g.id=l.uid),m=()=>ql(g));const y=new sd(u,m);e?n?g():p=y.run():a==="post"?on(y.run.bind(y),l&&l.suspense):y.run();const b=()=>{y.stop(),l&&l.scope&&td(l.scope.effects,y)};return f&&f.push(b),b}function UE(t,e,n){const r=this.proxy,a=mt(t)?t.includes(".")?S0(r,t):()=>r[t]:t.bind(r,r);let i;Le(e)?i=e:(i=e.handler,n=e);const o=St;pi(this);const s=Jl(a,i.bind(r),n);return o?pi(o):Aa(),s}function S0(t,e){const n=e.split(".");return()=>{let r=t;for(let a=0;a{Oa(n,e)});else if(Jb(t))for(const n in t)Oa(t[n],e);return t}function Jn(t,e){const n=$t;if(n===null)return t;const r=eu(n)||n.proxy,a=t.dirs||(t.dirs=[]);for(let i=0;i{t.isMounted=!0}),Ze(()=>{t.isUnmounting=!0}),t}const Sn=[Function,Array],E0={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Sn,onEnter:Sn,onAfterEnter:Sn,onEnterCancelled:Sn,onBeforeLeave:Sn,onLeave:Sn,onAfterLeave:Sn,onLeaveCancelled:Sn,onBeforeAppear:Sn,onAppear:Sn,onAfterAppear:Sn,onAppearCancelled:Sn},KE={name:"BaseTransition",props:E0,setup(t,{slots:e}){const n=Ct(),r=x0();let a;return()=>{const i=e.default&&md(e.default(),!0);if(!i||!i.length)return;let o=i[0];if(i.length>1){for(const p of i)if(p.type!==yn){o=p;break}}const s=Me(t),{mode:l}=s;if(r.isLeaving)return Bu(o);const u=$p(o);if(!u)return Bu(o);const c=xo(u,s,r,n);Eo(u,c);const d=n.subTree,v=d&&$p(d);let h=!1;const{getTransitionKey:f}=u.type;if(f){const p=f();a===void 0?a=p:p!==a&&(a=p,h=!0)}if(v&&v.type!==yn&&(!_a(u,v)||h)){const p=xo(v,s,r,n);if(Eo(v,p),l==="out-in")return r.isLeaving=!0,p.afterLeave=()=>{r.isLeaving=!1,n.update.active!==!1&&n.update()},Bu(o);l==="in-out"&&u.type!==yn&&(p.delayLeave=(g,m,y)=>{const b=O0(r,v);b[String(v.key)]=v,g._leaveCb=()=>{m(),g._leaveCb=void 0,delete c.delayedLeave},c.delayedLeave=y})}return o}}},GE=KE;function O0(t,e){const{leavingVNodes:n}=t;let r=n.get(e.type);return r||(r=Object.create(null),n.set(e.type,r)),r}function xo(t,e,n,r){const{appear:a,mode:i,persisted:o=!1,onBeforeEnter:s,onEnter:l,onAfterEnter:u,onEnterCancelled:c,onBeforeLeave:d,onLeave:v,onAfterLeave:h,onLeaveCancelled:f,onBeforeAppear:p,onAppear:g,onAfterAppear:m,onAppearCancelled:y}=e,b=String(t.key),w=O0(n,t),_=(A,E)=>{A&&Mn(A,r,9,E)},C=(A,E)=>{const N=E[1];_(A,E),Pe(A)?A.every(R=>R.length<=1)&&N():A.length<=1&&N()},O={mode:i,persisted:o,beforeEnter(A){let E=s;if(!n.isMounted)if(a)E=p||s;else return;A._leaveCb&&A._leaveCb(!0);const N=w[b];N&&_a(t,N)&&N.el._leaveCb&&N.el._leaveCb(),_(E,[A])},enter(A){let E=l,N=u,R=c;if(!n.isMounted)if(a)E=g||l,N=m||u,R=y||c;else return;let D=!1;const B=A._enterCb=j=>{D||(D=!0,j?_(R,[A]):_(N,[A]),O.delayedLeave&&O.delayedLeave(),A._enterCb=void 0)};E?C(E,[A,B]):B()},leave(A,E){const N=String(t.key);if(A._enterCb&&A._enterCb(!0),n.isUnmounting)return E();_(d,[A]);let R=!1;const D=A._leaveCb=B=>{R||(R=!0,E(),B?_(f,[A]):_(h,[A]),A._leaveCb=void 0,w[N]===t&&delete w[N])};w[N]=t,v?C(v,[A,D]):D()},clone(A){return xo(A,e,n,r)}};return O}function Bu(t){if(Yo(t))return t=Gn(t),t.children=null,t}function $p(t){return Yo(t)?t.children?t.children[0]:void 0:t}function Eo(t,e){t.shapeFlag&6&&t.component?Eo(t.component.subTree,e):t.shapeFlag&128?(t.ssContent.transition=e.clone(t.ssContent),t.ssFallback.transition=e.clone(t.ssFallback)):t.transition=e}function md(t,e=!1,n){let r=[],a=0;for(let i=0;i1)for(let i=0;ibt({name:t.name},e,{setup:t}))():t}const oo=t=>!!t.type.__asyncLoader;function tr(t){Le(t)&&(t={loader:t});const{loader:e,loadingComponent:n,errorComponent:r,delay:a=200,timeout:i,suspensible:o=!0,onError:s}=t;let l=null,u,c=0;const d=()=>(c++,l=null,v()),v=()=>{let h;return l||(h=l=e().catch(f=>{if(f=f instanceof Error?f:new Error(String(f)),s)return new Promise((p,g)=>{s(f,()=>p(d()),()=>g(f),c+1)});throw f}).then(f=>h!==l&&l?l:(f&&(f.__esModule||f[Symbol.toStringTag]==="Module")&&(f=f.default),u=f,f)))};return de({name:"AsyncComponentWrapper",__asyncLoader:v,get __asyncResolved(){return u},setup(){const h=St;if(u)return()=>ju(u,h);const f=y=>{l=null,qo(y,h,13,!r)};if(o&&h.suspense||hi)return v().then(y=>()=>ju(y,h)).catch(y=>(f(y),()=>r?x(r,{error:y}):null));const p=z(!1),g=z(),m=z(!!a);return a&&setTimeout(()=>{m.value=!1},a),i!=null&&setTimeout(()=>{if(!p.value&&!g.value){const y=new Error(`Async component timed out after ${i}ms.`);f(y),g.value=y}},i),v().then(()=>{p.value=!0,h.parent&&Yo(h.parent.vnode)&&ql(h.parent.update)}).catch(y=>{f(y),g.value=y}),()=>{if(p.value&&u)return ju(u,h);if(g.value&&r)return x(r,{error:g.value});if(n&&!m.value)return x(n)}}})}function ju(t,e){const{ref:n,props:r,children:a,ce:i}=e.vnode,o=x(t,r,a);return o.ref=n,o.ce=i,delete e.vnode.ce,o}const Yo=t=>t.type.__isKeepAlive;function qE(t,e){P0(t,"a",e)}function YE(t,e){P0(t,"da",e)}function P0(t,e,n=St){const r=t.__wdc||(t.__wdc=()=>{let a=n;for(;a;){if(a.isDeactivated)return;a=a.parent}return t()});if(Zl(e,r,n),n){let a=n.parent;for(;a&&a.parent;)Yo(a.parent.vnode)&&XE(r,e,n,a),a=a.parent}}function XE(t,e,n,r){const a=Zl(e,t,r,!0);cn(()=>{td(r[e],a)},n)}function Zl(t,e,n=St,r=!1){if(n){const a=n[t]||(n[t]=[]),i=e.__weh||(e.__weh=(...o)=>{if(n.isUnmounted)return;Ii(),pi(n);const s=Mn(e,n,t,o);return Aa(),Ai(),s});return r?a.unshift(i):a.push(i),i}}const $r=t=>(e,n=St)=>(!hi||t==="sp")&&Zl(t,(...r)=>e(...r),n),gd=$r("bm"),De=$r("m"),T0=$r("bu"),oa=$r("u"),Ze=$r("bum"),cn=$r("um"),JE=$r("sp"),ZE=$r("rtg"),QE=$r("rtc");function eO(t,e=St){Zl("ec",t,e)}const yd="components",tO="directives";function Xo(t,e){return bd(yd,t,!0,e)||t}const I0=Symbol.for("v-ndc");function nO(t){return mt(t)?bd(yd,t,!1)||t:t||I0}function rO(t){return bd(tO,t)}function bd(t,e,n=!0,r=!1){const a=$t||St;if(a){const i=a.type;if(t===yd){const s=RO(i,!1);if(s&&(s===e||s===Kn(e)||s===Hl(Kn(e))))return i}const o=Lp(a[t]||i[t],e)||Lp(a.appContext[t],e);return!o&&r?i:o}}function Lp(t,e){return t&&(t[e]||t[Kn(e)]||t[Hl(Kn(e))])}function ul(t,e,n,r){let a;const i=n&&n[r];if(Pe(t)||mt(t)){a=new Array(t.length);for(let o=0,s=t.length;oe(o,s,void 0,i&&i[s]));else{const o=Object.keys(t);a=new Array(o.length);for(let s=0,l=o.length;spr(e)?!(e.type===yn||e.type===Ae&&!A0(e.children)):!0)?t:null}function fU(t,e){const n={};for(const r in t)n[e&&/[A-Z]/.test(r)?`on:${r}`:Vs(r)]=t[r];return n}const Lc=t=>t?z0(t)?eu(t)||t.proxy:Lc(t.parent):null,so=bt(Object.create(null),{$:t=>t,$el:t=>t.vnode.el,$data:t=>t.data,$props:t=>t.props,$attrs:t=>t.attrs,$slots:t=>t.slots,$refs:t=>t.refs,$parent:t=>Lc(t.parent),$root:t=>Lc(t.root),$emit:t=>t.emit,$options:t=>wd(t),$forceUpdate:t=>t.f||(t.f=()=>ql(t.update)),$nextTick:t=>t.n||(t.n=ze.bind(t.proxy)),$watch:t=>UE.bind(t)}),zu=(t,e)=>t!==ut&&!t.__isScriptSetup&&Ue(t,e),aO={get({_:t},e){const{ctx:n,setupState:r,data:a,props:i,accessCache:o,type:s,appContext:l}=t;let u;if(e[0]!=="$"){const h=o[e];if(h!==void 0)switch(h){case 1:return r[e];case 2:return a[e];case 4:return n[e];case 3:return i[e]}else{if(zu(r,e))return o[e]=1,r[e];if(a!==ut&&Ue(a,e))return o[e]=2,a[e];if((u=t.propsOptions[0])&&Ue(u,e))return o[e]=3,i[e];if(n!==ut&&Ue(n,e))return o[e]=4,n[e];Dc&&(o[e]=0)}}const c=so[e];let d,v;if(c)return e==="$attrs"&&un(t,"get",e),c(t);if((d=s.__cssModules)&&(d=d[e]))return d;if(n!==ut&&Ue(n,e))return o[e]=4,n[e];if(v=l.config.globalProperties,Ue(v,e))return v[e]},set({_:t},e,n){const{data:r,setupState:a,ctx:i}=t;return zu(a,e)?(a[e]=n,!0):r!==ut&&Ue(r,e)?(r[e]=n,!0):Ue(t.props,e)||e[0]==="$"&&e.slice(1)in t?!1:(i[e]=n,!0)},has({_:{data:t,setupState:e,accessCache:n,ctx:r,appContext:a,propsOptions:i}},o){let s;return!!n[o]||t!==ut&&Ue(t,o)||zu(e,o)||(s=i[0])&&Ue(s,o)||Ue(r,o)||Ue(so,o)||Ue(a.config.globalProperties,o)},defineProperty(t,e,n){return n.get!=null?t._.accessCache[e]=0:Ue(n,"value")&&this.set(t,e,n.value,null),Reflect.defineProperty(t,e,n)}};function dU(t,e,n){const r=Ct();if(n&&n.local){const a=z(t[e]);return ve(()=>t[e],i=>a.value=i),ve(a,i=>{i!==t[e]&&r.emit(`update:${e}`,i)}),a}else return{__v_isRef:!0,get value(){return t[e]},set value(a){r.emit(`update:${e}`,a)}}}function fl(t){return Pe(t)?t.reduce((e,n)=>(e[n]=null,e),{}):t}function vU(t,e){return!t||!e?t||e:Pe(t)&&Pe(e)?t.concat(e):bt({},fl(t),fl(e))}let Dc=!0;function iO(t){const e=wd(t),n=t.proxy,r=t.ctx;Dc=!1,e.beforeCreate&&Dp(e.beforeCreate,t,"bc");const{data:a,computed:i,methods:o,watch:s,provide:l,inject:u,created:c,beforeMount:d,mounted:v,beforeUpdate:h,updated:f,activated:p,deactivated:g,beforeDestroy:m,beforeUnmount:y,destroyed:b,unmounted:w,render:_,renderTracked:C,renderTriggered:O,errorCaptured:A,serverPrefetch:E,expose:N,inheritAttrs:R,components:D,directives:B,filters:j}=e;if(u&&oO(u,r,null),o)for(const P in o){const k=o[P];Le(k)&&(r[P]=k.bind(n))}if(a){const P=a.call(n,n);ot(P)&&(t.data=nt(P))}if(Dc=!0,i)for(const P in i){const k=i[P],L=Le(k)?k.bind(n,n):Le(k.get)?k.get.bind(n,n):Un,K=!Le(k)&&Le(k.set)?k.set.bind(n):Un,X=G({get:L,set:K});Object.defineProperty(r,P,{enumerable:!0,configurable:!0,get:()=>X.value,set:ee=>X.value=ee})}if(s)for(const P in s)M0(s[P],r,n,P);if(l){const P=Le(l)?l.call(n):l;Reflect.ownKeys(P).forEach(k=>{pt(k,P[k])})}c&&Dp(c,t,"c");function T(P,k){Pe(k)?k.forEach(L=>P(L.bind(n))):k&&P(k.bind(n))}if(T(gd,d),T(De,v),T(T0,h),T(oa,f),T(qE,p),T(YE,g),T(eO,A),T(QE,C),T(ZE,O),T(Ze,y),T(cn,w),T(JE,E),Pe(N))if(N.length){const P=t.exposed||(t.exposed={});N.forEach(k=>{Object.defineProperty(P,k,{get:()=>n[k],set:L=>n[k]=L})})}else t.exposed||(t.exposed={});_&&t.render===Un&&(t.render=_),R!=null&&(t.inheritAttrs=R),D&&(t.components=D),B&&(t.directives=B)}function oO(t,e,n=Un){Pe(t)&&(t=Fc(t));for(const r in t){const a=t[r];let i;ot(a)?"default"in a?i=Je(a.from||r,a.default,!0):i=Je(a.from||r):i=Je(a),it(i)?Object.defineProperty(e,r,{enumerable:!0,configurable:!0,get:()=>i.value,set:o=>i.value=o}):e[r]=i}}function Dp(t,e,n){Mn(Pe(t)?t.map(r=>r.bind(e.proxy)):t.bind(e.proxy),e,n)}function M0(t,e,n,r){const a=r.includes(".")?S0(n,r):()=>n[r];if(mt(t)){const i=e[t];Le(i)&&ve(a,i)}else if(Le(t))ve(a,t.bind(n));else if(ot(t))if(Pe(t))t.forEach(i=>M0(i,e,n,r));else{const i=Le(t.handler)?t.handler.bind(n):e[t.handler];Le(i)&&ve(a,i,t)}}function wd(t){const e=t.type,{mixins:n,extends:r}=e,{mixins:a,optionsCache:i,config:{optionMergeStrategies:o}}=t.appContext,s=i.get(e);let l;return s?l=s:!a.length&&!n&&!r?l=e:(l={},a.length&&a.forEach(u=>dl(l,u,o,!0)),dl(l,e,o)),ot(e)&&i.set(e,l),l}function dl(t,e,n,r=!1){const{mixins:a,extends:i}=e;i&&dl(t,i,n,!0),a&&a.forEach(o=>dl(t,o,n,!0));for(const o in e)if(!(r&&o==="expose")){const s=sO[o]||n&&n[o];t[o]=s?s(t[o],e[o]):e[o]}return t}const sO={data:Fp,props:Bp,emits:Bp,methods:eo,computed:eo,beforeCreate:Yt,created:Yt,beforeMount:Yt,mounted:Yt,beforeUpdate:Yt,updated:Yt,beforeDestroy:Yt,beforeUnmount:Yt,destroyed:Yt,unmounted:Yt,activated:Yt,deactivated:Yt,errorCaptured:Yt,serverPrefetch:Yt,components:eo,directives:eo,watch:uO,provide:Fp,inject:lO};function Fp(t,e){return e?t?function(){return bt(Le(t)?t.call(this,this):t,Le(e)?e.call(this,this):e)}:e:t}function lO(t,e){return eo(Fc(t),Fc(e))}function Fc(t){if(Pe(t)){const e={};for(let n=0;n1)return n&&Le(e)?e.call(r&&r.proxy):e}}function dO(){return!!(St||$t||Oo)}function vO(t,e,n,r=!1){const a={},i={};il(i,Ql,1),t.propsDefaults=Object.create(null),N0(t,e,a,i);for(const o in t.propsOptions[0])o in a||(a[o]=void 0);n?t.props=r?a:CE(a):t.type.props?t.props=a:t.props=i,t.attrs=i}function pO(t,e,n,r){const{props:a,attrs:i,vnode:{patchFlag:o}}=t,s=Me(a),[l]=t.propsOptions;let u=!1;if((r||o>0)&&!(o&16)){if(o&8){const c=t.vnode.dynamicProps;for(let d=0;d{l=!0;const[v,h]=R0(d,e,!0);bt(o,v),h&&s.push(...h)};!n&&e.mixins.length&&e.mixins.forEach(c),t.extends&&c(t.extends),t.mixins&&t.mixins.forEach(c)}if(!i&&!l)return ot(t)&&r.set(t,ii),ii;if(Pe(i))for(let c=0;c-1,h[1]=p<0||f-1||Ue(h,"default"))&&s.push(d)}}}const u=[o,s];return ot(t)&&r.set(t,u),u}function jp(t){return t[0]!=="$"}function zp(t){const e=t&&t.toString().match(/^\s*(function|class) (\w+)/);return e?e[2]:t===null?"null":""}function Wp(t,e){return zp(t)===zp(e)}function Hp(t,e){return Pe(e)?e.findIndex(n=>Wp(n,t)):Le(e)&&Wp(e,t)?0:-1}const $0=t=>t[0]==="_"||t==="$stable",_d=t=>Pe(t)?t.map(or):[or(t)],hO=(t,e,n)=>{if(e._n)return e;const r=Et((...a)=>_d(e(...a)),n);return r._c=!1,r},L0=(t,e,n)=>{const r=t._ctx;for(const a in t){if($0(a))continue;const i=t[a];if(Le(i))e[a]=hO(a,i,r);else if(i!=null){const o=_d(i);e[a]=()=>o}}},D0=(t,e)=>{const n=_d(e);t.slots.default=()=>n},mO=(t,e)=>{if(t.vnode.shapeFlag&32){const n=e._;n?(t.slots=Me(e),il(e,"_",n)):L0(e,t.slots={})}else t.slots={},e&&D0(t,e);il(t.slots,Ql,1)},gO=(t,e,n)=>{const{vnode:r,slots:a}=t;let i=!0,o=ut;if(r.shapeFlag&32){const s=e._;s?n&&s===1?i=!1:(bt(a,e),!n&&s===1&&delete a._):(i=!e.$stable,L0(e,a)),o=e}else e&&(D0(t,e),o={default:1});if(i)for(const s in a)!$0(s)&&!(s in o)&&delete a[s]};function jc(t,e,n,r,a=!1){if(Pe(t)){t.forEach((v,h)=>jc(v,e&&(Pe(e)?e[h]:e),n,r,a));return}if(oo(r)&&!a)return;const i=r.shapeFlag&4?eu(r.component)||r.component.proxy:r.el,o=a?null:i,{i:s,r:l}=t,u=e&&e.r,c=s.refs===ut?s.refs={}:s.refs,d=s.setupState;if(u!=null&&u!==l&&(mt(u)?(c[u]=null,Ue(d,u)&&(d[u]=null)):it(u)&&(u.value=null)),Le(l))Qr(l,s,12,[o,c]);else{const v=mt(l),h=it(l);if(v||h){const f=()=>{if(t.f){const p=v?Ue(d,l)?d[l]:c[l]:l.value;a?Pe(p)&&td(p,i):Pe(p)?p.includes(i)||p.push(i):v?(c[l]=[i],Ue(d,l)&&(d[l]=c[l])):(l.value=[i],t.k&&(c[t.k]=l.value))}else v?(c[l]=o,Ue(d,l)&&(d[l]=o)):h&&(l.value=o,t.k&&(c[t.k]=o))};o?(f.id=-1,on(f,n)):f()}}}const on=HE;function yO(t){return bO(t)}function bO(t,e){const n=Mc();n.__VUE__=!0;const{insert:r,remove:a,patchProp:i,createElement:o,createText:s,createComment:l,setText:u,setElementText:c,parentNode:d,nextSibling:v,setScopeId:h=Un,insertStaticContent:f}=t,p=(F,S,I,W=null,U=null,V=null,oe=!1,ie=null,re=!!S.dynamicChildren)=>{if(F===S)return;F&&!_a(F,S)&&(W=pe(F),ee(F,U,V,!0),F=null),S.patchFlag===-2&&(re=!1,S.dynamicChildren=null);const{type:Z,ref:H,shapeFlag:q}=S;switch(Z){case La:g(F,S,I,W);break;case yn:m(F,S,I,W);break;case Us:F==null&&y(S,I,W,oe);break;case Ae:D(F,S,I,W,U,V,oe,ie,re);break;default:q&1?_(F,S,I,W,U,V,oe,ie,re):q&6?B(F,S,I,W,U,V,oe,ie,re):(q&64||q&128)&&Z.process(F,S,I,W,U,V,oe,ie,re,he)}H!=null&&U&&jc(H,F&&F.ref,V,S||F,!S)},g=(F,S,I,W)=>{if(F==null)r(S.el=s(S.children),I,W);else{const U=S.el=F.el;S.children!==F.children&&u(U,S.children)}},m=(F,S,I,W)=>{F==null?r(S.el=l(S.children||""),I,W):S.el=F.el},y=(F,S,I,W)=>{[F.el,F.anchor]=f(F.children,S,I,W,F.el,F.anchor)},b=({el:F,anchor:S},I,W)=>{let U;for(;F&&F!==S;)U=v(F),r(F,I,W),F=U;r(S,I,W)},w=({el:F,anchor:S})=>{let I;for(;F&&F!==S;)I=v(F),a(F),F=I;a(S)},_=(F,S,I,W,U,V,oe,ie,re)=>{oe=oe||S.type==="svg",F==null?C(S,I,W,U,V,oe,ie,re):E(F,S,U,V,oe,ie,re)},C=(F,S,I,W,U,V,oe,ie)=>{let re,Z;const{type:H,props:q,shapeFlag:ue,transition:ae,dirs:ce}=F;if(re=F.el=o(F.type,V,q&&q.is,q),ue&8?c(re,F.children):ue&16&&A(F.children,re,null,W,U,V&&H!=="foreignObject",oe,ie),ce&&fa(F,null,W,"created"),O(re,F,F.scopeId,oe,W),q){for(const we in q)we!=="value"&&!Hs(we)&&i(re,we,null,q[we],V,F.children,W,U,se);"value"in q&&i(re,"value",null,q.value),(Z=q.onVnodeBeforeMount)&&nr(Z,W,F)}ce&&fa(F,null,W,"beforeMount");const me=(!U||U&&!U.pendingBranch)&&ae&&!ae.persisted;me&&ae.beforeEnter(re),r(re,S,I),((Z=q&&q.onVnodeMounted)||me||ce)&&on(()=>{Z&&nr(Z,W,F),me&&ae.enter(re),ce&&fa(F,null,W,"mounted")},U)},O=(F,S,I,W,U)=>{if(I&&h(F,I),W)for(let V=0;V{for(let Z=re;Z{const ie=S.el=F.el;let{patchFlag:re,dynamicChildren:Z,dirs:H}=S;re|=F.patchFlag&16;const q=F.props||ut,ue=S.props||ut;let ae;I&&da(I,!1),(ae=ue.onVnodeBeforeUpdate)&&nr(ae,I,S,F),H&&fa(S,F,I,"beforeUpdate"),I&&da(I,!0);const ce=U&&S.type!=="foreignObject";if(Z?N(F.dynamicChildren,Z,ie,I,W,ce,V):oe||k(F,S,ie,null,I,W,ce,V,!1),re>0){if(re&16)R(ie,S,q,ue,I,W,U);else if(re&2&&q.class!==ue.class&&i(ie,"class",null,ue.class,U),re&4&&i(ie,"style",q.style,ue.style,U),re&8){const me=S.dynamicProps;for(let we=0;we{ae&&nr(ae,I,S,F),H&&fa(S,F,I,"updated")},W)},N=(F,S,I,W,U,V,oe)=>{for(let ie=0;ie{if(I!==W){if(I!==ut)for(const ie in I)!Hs(ie)&&!(ie in W)&&i(F,ie,I[ie],null,oe,S.children,U,V,se);for(const ie in W){if(Hs(ie))continue;const re=W[ie],Z=I[ie];re!==Z&&ie!=="value"&&i(F,ie,Z,re,oe,S.children,U,V,se)}"value"in W&&i(F,"value",I.value,W.value)}},D=(F,S,I,W,U,V,oe,ie,re)=>{const Z=S.el=F?F.el:s(""),H=S.anchor=F?F.anchor:s("");let{patchFlag:q,dynamicChildren:ue,slotScopeIds:ae}=S;ae&&(ie=ie?ie.concat(ae):ae),F==null?(r(Z,I,W),r(H,I,W),A(S.children,I,H,U,V,oe,ie,re)):q>0&&q&64&&ue&&F.dynamicChildren?(N(F.dynamicChildren,ue,I,U,V,oe,ie),(S.key!=null||U&&S===U.subTree)&&Cd(F,S,!0)):k(F,S,I,H,U,V,oe,ie,re)},B=(F,S,I,W,U,V,oe,ie,re)=>{S.slotScopeIds=ie,F==null?S.shapeFlag&512?U.ctx.activate(S,I,W,oe,re):j(S,I,W,U,V,oe,re):$(F,S,re)},j=(F,S,I,W,U,V,oe)=>{const ie=F.component=IO(F,W,U);if(Yo(F)&&(ie.ctx.renderer=he),AO(ie),ie.asyncDep){if(U&&U.registerDep(ie,T),!F.el){const re=ie.subTree=x(yn);m(null,re,S,I)}return}T(ie,F,S,I,U,V,oe)},$=(F,S,I)=>{const W=S.component=F.component;if(jE(F,S,I))if(W.asyncDep&&!W.asyncResolved){P(W,S,I);return}else W.next=S,NE(W.update),W.update();else S.el=F.el,W.vnode=S},T=(F,S,I,W,U,V,oe)=>{const ie=()=>{if(F.isMounted){let{next:H,bu:q,u:ue,parent:ae,vnode:ce}=F,me=H,we;da(F,!1),H?(H.el=ce.el,P(F,H,oe)):H=ce,q&&Du(q),(we=H.props&&H.props.onVnodeBeforeUpdate)&&nr(we,ae,H,ce),da(F,!0);const Se=Fu(F),Ve=F.subTree;F.subTree=Se,p(Ve,Se,d(Ve.el),pe(Ve),F,U,V),H.el=Se.el,me===null&&zE(F,Se.el),ue&&on(ue,U),(we=H.props&&H.props.onVnodeUpdated)&&on(()=>nr(we,ae,H,ce),U)}else{let H;const{el:q,props:ue}=S,{bm:ae,m:ce,parent:me}=F,we=oo(S);if(da(F,!1),ae&&Du(ae),!we&&(H=ue&&ue.onVnodeBeforeMount)&&nr(H,me,S),da(F,!0),q&&_e){const Se=()=>{F.subTree=Fu(F),_e(q,F.subTree,F,U,null)};we?S.type.__asyncLoader().then(()=>!F.isUnmounted&&Se()):Se()}else{const Se=F.subTree=Fu(F);p(null,Se,I,W,F,U,V),S.el=Se.el}if(ce&&on(ce,U),!we&&(H=ue&&ue.onVnodeMounted)){const Se=S;on(()=>nr(H,me,Se),U)}(S.shapeFlag&256||me&&oo(me.vnode)&&me.vnode.shapeFlag&256)&&F.a&&on(F.a,U),F.isMounted=!0,S=I=W=null}},re=F.effect=new sd(ie,()=>ql(Z),F.scope),Z=F.update=()=>re.run();Z.id=F.uid,da(F,!0),Z()},P=(F,S,I)=>{S.component=F;const W=F.vnode.props;F.vnode=S,F.next=null,pO(F,S.props,W,I),gO(F,S.children,I),Ii(),Np(),Ai()},k=(F,S,I,W,U,V,oe,ie,re=!1)=>{const Z=F&&F.children,H=F?F.shapeFlag:0,q=S.children,{patchFlag:ue,shapeFlag:ae}=S;if(ue>0){if(ue&128){K(Z,q,I,W,U,V,oe,ie,re);return}else if(ue&256){L(Z,q,I,W,U,V,oe,ie,re);return}}ae&8?(H&16&&se(Z,U,V),q!==Z&&c(I,q)):H&16?ae&16?K(Z,q,I,W,U,V,oe,ie,re):se(Z,U,V,!0):(H&8&&c(I,""),ae&16&&A(q,I,W,U,V,oe,ie,re))},L=(F,S,I,W,U,V,oe,ie,re)=>{F=F||ii,S=S||ii;const Z=F.length,H=S.length,q=Math.min(Z,H);let ue;for(ue=0;ueH?se(F,U,V,!0,!1,q):A(S,I,W,U,V,oe,ie,re,q)},K=(F,S,I,W,U,V,oe,ie,re)=>{let Z=0;const H=S.length;let q=F.length-1,ue=H-1;for(;Z<=q&&Z<=ue;){const ae=F[Z],ce=S[Z]=re?Kr(S[Z]):or(S[Z]);if(_a(ae,ce))p(ae,ce,I,null,U,V,oe,ie,re);else break;Z++}for(;Z<=q&&Z<=ue;){const ae=F[q],ce=S[ue]=re?Kr(S[ue]):or(S[ue]);if(_a(ae,ce))p(ae,ce,I,null,U,V,oe,ie,re);else break;q--,ue--}if(Z>q){if(Z<=ue){const ae=ue+1,ce=aeue)for(;Z<=q;)ee(F[Z],U,V,!0),Z++;else{const ae=Z,ce=Z,me=new Map;for(Z=ce;Z<=ue;Z++){const gt=S[Z]=re?Kr(S[Z]):or(S[Z]);gt.key!=null&&me.set(gt.key,Z)}let we,Se=0;const Ve=ue-ce+1;let Ht=!1,Dn=0;const Kt=new Array(Ve);for(Z=0;Z=Ve){ee(gt,U,V,!0);continue}let Ft;if(gt.key!=null)Ft=me.get(gt.key);else for(we=ce;we<=ue;we++)if(Kt[we-ce]===0&&_a(gt,S[we])){Ft=we;break}Ft===void 0?ee(gt,U,V,!0):(Kt[Ft-ce]=Z+1,Ft>=Dn?Dn=Ft:Ht=!0,p(gt,S[Ft],I,null,U,V,oe,ie,re),Se++)}const Gt=Ht?wO(Kt):ii;for(we=Gt.length-1,Z=Ve-1;Z>=0;Z--){const gt=ce+Z,Ft=S[gt],Fr=gt+1{const{el:V,type:oe,transition:ie,children:re,shapeFlag:Z}=F;if(Z&6){X(F.component.subTree,S,I,W);return}if(Z&128){F.suspense.move(S,I,W);return}if(Z&64){oe.move(F,S,I,he);return}if(oe===Ae){r(V,S,I);for(let q=0;qie.enter(V),U);else{const{leave:q,delayLeave:ue,afterLeave:ae}=ie,ce=()=>r(V,S,I),me=()=>{q(V,()=>{ce(),ae&&ae()})};ue?ue(V,ce,me):me()}else r(V,S,I)},ee=(F,S,I,W=!1,U=!1)=>{const{type:V,props:oe,ref:ie,children:re,dynamicChildren:Z,shapeFlag:H,patchFlag:q,dirs:ue}=F;if(ie!=null&&jc(ie,null,I,F,!0),H&256){S.ctx.deactivate(F);return}const ae=H&1&&ue,ce=!oo(F);let me;if(ce&&(me=oe&&oe.onVnodeBeforeUnmount)&&nr(me,S,F),H&6)ne(F.component,I,W);else{if(H&128){F.suspense.unmount(I,W);return}ae&&fa(F,null,S,"beforeUnmount"),H&64?F.type.remove(F,S,I,U,he,W):Z&&(V!==Ae||q>0&&q&64)?se(Z,S,I,!1,!0):(V===Ae&&q&384||!U&&H&16)&&se(re,S,I),W&&J(F)}(ce&&(me=oe&&oe.onVnodeUnmounted)||ae)&&on(()=>{me&&nr(me,S,F),ae&&fa(F,null,S,"unmounted")},I)},J=F=>{const{type:S,el:I,anchor:W,transition:U}=F;if(S===Ae){Y(I,W);return}if(S===Us){w(F);return}const V=()=>{a(I),U&&!U.persisted&&U.afterLeave&&U.afterLeave()};if(F.shapeFlag&1&&U&&!U.persisted){const{leave:oe,delayLeave:ie}=U,re=()=>oe(I,V);ie?ie(F.el,V,re):re()}else V()},Y=(F,S)=>{let I;for(;F!==S;)I=v(F),a(F),F=I;a(S)},ne=(F,S,I)=>{const{bum:W,scope:U,update:V,subTree:oe,um:ie}=F;W&&Du(W),U.stop(),V&&(V.active=!1,ee(oe,F,S,I)),ie&&on(ie,S),on(()=>{F.isUnmounted=!0},S),S&&S.pendingBranch&&!S.isUnmounted&&F.asyncDep&&!F.asyncResolved&&F.suspenseId===S.pendingId&&(S.deps--,S.deps===0&&S.resolve())},se=(F,S,I,W=!1,U=!1,V=0)=>{for(let oe=V;oeF.shapeFlag&6?pe(F.component.subTree):F.shapeFlag&128?F.suspense.next():v(F.anchor||F.el),ye=(F,S,I)=>{F==null?S._vnode&&ee(S._vnode,null,null,!0):p(S._vnode||null,F,S,null,null,null,I),Np(),y0(),S._vnode=F},he={p,um:ee,m:X,r:J,mt:j,mc:A,pc:k,pbc:N,n:pe,o:t};let ge,_e;return e&&([ge,_e]=e(he)),{render:ye,hydrate:ge,createApp:fO(ye,ge)}}function da({effect:t,update:e},n){t.allowRecurse=e.allowRecurse=n}function Cd(t,e,n=!1){const r=t.children,a=e.children;if(Pe(r)&&Pe(a))for(let i=0;i>1,t[n[s]]0&&(e[r]=n[i-1]),n[i]=r)}}for(i=n.length,o=n[i-1];i-- >0;)n[i]=o,o=e[o];return n}const _O=t=>t.__isTeleport,lo=t=>t&&(t.disabled||t.disabled===""),Vp=t=>typeof SVGElement<"u"&&t instanceof SVGElement,zc=(t,e)=>{const n=t&&t.to;return mt(n)?e?e(n):null:n},CO={__isTeleport:!0,process(t,e,n,r,a,i,o,s,l,u){const{mc:c,pc:d,pbc:v,o:{insert:h,querySelector:f,createText:p,createComment:g}}=u,m=lo(e.props);let{shapeFlag:y,children:b,dynamicChildren:w}=e;if(t==null){const _=e.el=p(""),C=e.anchor=p("");h(_,n,r),h(C,n,r);const O=e.target=zc(e.props,f),A=e.targetAnchor=p("");O&&(h(A,O),o=o||Vp(O));const E=(N,R)=>{y&16&&c(b,N,R,a,i,o,s,l)};m?E(n,C):O&&E(O,A)}else{e.el=t.el;const _=e.anchor=t.anchor,C=e.target=t.target,O=e.targetAnchor=t.targetAnchor,A=lo(t.props),E=A?n:C,N=A?_:O;if(o=o||Vp(C),w?(v(t.dynamicChildren,w,E,a,i,o,s),Cd(t,e,!0)):l||d(t,e,E,N,a,i,o,s,!1),m)A||bs(e,n,_,u,1);else if((e.props&&e.props.to)!==(t.props&&t.props.to)){const R=e.target=zc(e.props,f);R&&bs(e,R,null,u,0)}else A&&bs(e,C,O,u,1)}F0(e)},remove(t,e,n,r,{um:a,o:{remove:i}},o){const{shapeFlag:s,children:l,anchor:u,targetAnchor:c,target:d,props:v}=t;if(d&&i(c),(o||!lo(v))&&(i(u),s&16))for(let h=0;h0?Hn||ii:null,xO(),Po>0&&Hn&&Hn.push(t),t}function at(t,e,n,r,a,i){return B0(ct(t,e,n,r,a,i,!0))}function jt(t,e,n,r,a){return B0(x(t,e,n,r,a,!0))}function pr(t){return t?t.__v_isVNode===!0:!1}function _a(t,e){return t.type===e.type&&t.key===e.key}const Ql="__vInternal",j0=({key:t})=>t??null,Ks=({ref:t,ref_key:e,ref_for:n})=>(typeof t=="number"&&(t=""+t),t!=null?mt(t)||it(t)||Le(t)?{i:$t,r:t,k:e,f:!!n}:t:null);function ct(t,e=null,n=null,r=0,a=null,i=t===Ae?0:1,o=!1,s=!1){const l={__v_isVNode:!0,__v_skip:!0,type:t,props:e,key:e&&j0(e),ref:e&&Ks(e),scopeId:Xl,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:r,dynamicProps:a,dynamicChildren:null,appContext:null,ctx:$t};return s?(xd(l,n),i&128&&t.normalize(l)):n&&(l.shapeFlag|=mt(n)?8:16),Po>0&&!o&&Hn&&(l.patchFlag>0||i&6)&&l.patchFlag!==32&&Hn.push(l),l}const x=EO;function EO(t,e=null,n=null,r=0,a=null,i=!1){if((!t||t===I0)&&(t=yn),pr(t)){const s=Gn(t,e,!0);return n&&xd(s,n),Po>0&&!i&&Hn&&(s.shapeFlag&6?Hn[Hn.indexOf(t)]=s:Hn.push(s)),s.patchFlag|=-2,s}if($O(t)&&(t=t.__vccOpts),e){e=OO(e);let{class:s,style:l}=e;s&&!mt(s)&&(e.class=gn(s)),ot(l)&&(f0(l)&&!Pe(l)&&(l=bt({},l)),e.style=kr(l))}const o=mt(t)?1:WE(t)?128:_O(t)?64:ot(t)?4:Le(t)?2:0;return ct(t,e,n,r,a,o,i,!0)}function OO(t){return t?f0(t)||Ql in t?bt({},t):t:null}function Gn(t,e,n=!1){const{props:r,ref:a,patchFlag:i,children:o}=t,s=e?Ed(r||{},e):r;return{__v_isVNode:!0,__v_skip:!0,type:t.type,props:s,key:s&&j0(s),ref:e&&e.ref?n&&a?Pe(a)?a.concat(Ks(e)):[a,Ks(e)]:Ks(e):a,scopeId:t.scopeId,slotScopeIds:t.slotScopeIds,children:o,target:t.target,targetAnchor:t.targetAnchor,staticCount:t.staticCount,shapeFlag:t.shapeFlag,patchFlag:e&&t.type!==Ae?i===-1?16:i|16:i,dynamicProps:t.dynamicProps,dynamicChildren:t.dynamicChildren,appContext:t.appContext,dirs:t.dirs,transition:t.transition,component:t.component,suspense:t.suspense,ssContent:t.ssContent&&Gn(t.ssContent),ssFallback:t.ssFallback&&Gn(t.ssFallback),el:t.el,anchor:t.anchor,ctx:t.ctx,ce:t.ce}}function kn(t=" ",e=0){return x(La,null,t,e)}function ur(t="",e=!1){return e?($e(),jt(yn,null,t)):x(yn,null,t)}function or(t){return t==null||typeof t=="boolean"?x(yn):Pe(t)?x(Ae,null,t.slice()):typeof t=="object"?Kr(t):x(La,null,String(t))}function Kr(t){return t.el===null&&t.patchFlag!==-1||t.memo?t:Gn(t)}function xd(t,e){let n=0;const{shapeFlag:r}=t;if(e==null)e=null;else if(Pe(e))n=16;else if(typeof e=="object")if(r&65){const a=e.default;a&&(a._c&&(a._d=!1),xd(t,a()),a._c&&(a._d=!0));return}else{n=32;const a=e._;!a&&!(Ql in e)?e._ctx=$t:a===3&&$t&&($t.slots._===1?e._=1:(e._=2,t.patchFlag|=1024))}else Le(e)?(e={default:e,_ctx:$t},n=32):(e=String(e),r&64?(n=16,e=[kn(e)]):n=8);t.children=e,t.shapeFlag|=n}function Ed(...t){const e={};for(let n=0;nSt||$t;let Od,Va,Kp="__VUE_INSTANCE_SETTERS__";(Va=Mc()[Kp])||(Va=Mc()[Kp]=[]),Va.push(t=>St=t),Od=t=>{Va.length>1?Va.forEach(e=>e(t)):Va[0](t)};const pi=t=>{Od(t),t.scope.on()},Aa=()=>{St&&St.scope.off(),Od(null)};function z0(t){return t.vnode.shapeFlag&4}let hi=!1;function AO(t,e=!1){hi=e;const{props:n,children:r}=t.vnode,a=z0(t);vO(t,n,a,e),mO(t,r);const i=a?MO(t,e):void 0;return hi=!1,i}function MO(t,e){const n=t.type;t.accessCache=Object.create(null),t.proxy=Gl(new Proxy(t.ctx,aO));const{setup:r}=n;if(r){const a=t.setupContext=r.length>1?NO(t):null;pi(t),Ii();const i=Qr(r,t,0,[t.props,a]);if(Ai(),Aa(),Yb(i)){if(i.then(Aa,Aa),e)return i.then(o=>{Gp(t,o,e)}).catch(o=>{qo(o,t,0)});t.asyncDep=i}else Gp(t,i,e)}else W0(t,e)}function Gp(t,e,n){Le(e)?t.type.__ssrInlineRender?t.ssrRender=e:t.render=e:ot(e)&&(t.setupState=v0(e)),W0(t,n)}let qp;function W0(t,e,n){const r=t.type;if(!t.render){if(!e&&qp&&!r.render){const a=r.template||wd(t).template;if(a){const{isCustomElement:i,compilerOptions:o}=t.appContext.config,{delimiters:s,compilerOptions:l}=r,u=bt(bt({isCustomElement:i,delimiters:s},o),l);r.render=qp(a,u)}}t.render=r.render||Un}pi(t),Ii(),iO(t),Ai(),Aa()}function kO(t){return t.attrsProxy||(t.attrsProxy=new Proxy(t.attrs,{get(e,n){return un(t,"get","$attrs"),e[n]}}))}function NO(t){const e=n=>{t.exposed=n||{}};return{get attrs(){return kO(t)},slots:t.slots,emit:t.emit,expose:e}}function eu(t){if(t.exposed)return t.exposeProxy||(t.exposeProxy=new Proxy(v0(Gl(t.exposed)),{get(e,n){if(n in e)return e[n];if(n in so)return so[n](t)},has(e,n){return n in e||n in so}}))}function RO(t,e=!0){return Le(t)?t.displayName||t.name:t.name||e&&t.__name}function $O(t){return Le(t)&&"__vccOpts"in t}const G=(t,e)=>AE(t,e,hi);function aa(t,e,n){const r=arguments.length;return r===2?ot(e)&&!Pe(e)?pr(e)?x(t,null,[e]):x(t,e):x(t,null,e):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&pr(n)&&(n=[n]),x(t,e,n))}const LO=Symbol.for("v-scx"),DO=()=>Je(LO),FO="3.3.4",BO="http://www.w3.org/2000/svg",Ca=typeof document<"u"?document:null,Yp=Ca&&Ca.createElement("template"),jO={insert:(t,e,n)=>{e.insertBefore(t,n||null)},remove:t=>{const e=t.parentNode;e&&e.removeChild(t)},createElement:(t,e,n,r)=>{const a=e?Ca.createElementNS(BO,t):Ca.createElement(t,n?{is:n}:void 0);return t==="select"&&r&&r.multiple!=null&&a.setAttribute("multiple",r.multiple),a},createText:t=>Ca.createTextNode(t),createComment:t=>Ca.createComment(t),setText:(t,e)=>{t.nodeValue=e},setElementText:(t,e)=>{t.textContent=e},parentNode:t=>t.parentNode,nextSibling:t=>t.nextSibling,querySelector:t=>Ca.querySelector(t),setScopeId(t,e){t.setAttribute(e,"")},insertStaticContent(t,e,n,r,a,i){const o=n?n.previousSibling:e.lastChild;if(a&&(a===i||a.nextSibling))for(;e.insertBefore(a.cloneNode(!0),n),!(a===i||!(a=a.nextSibling)););else{Yp.innerHTML=r?`${t}`:t;const s=Yp.content;if(r){const l=s.firstChild;for(;l.firstChild;)s.appendChild(l.firstChild);s.removeChild(l)}e.insertBefore(s,n)}return[o?o.nextSibling:e.firstChild,n?n.previousSibling:e.lastChild]}};function zO(t,e,n){const r=t._vtc;r&&(e=(e?[e,...r]:[...r]).join(" ")),e==null?t.removeAttribute("class"):n?t.setAttribute("class",e):t.className=e}function WO(t,e,n){const r=t.style,a=mt(n);if(n&&!a){if(e&&!mt(e))for(const i in e)n[i]==null&&Wc(r,i,"");for(const i in n)Wc(r,i,n[i])}else{const i=r.display;a?e!==n&&(r.cssText=n):e&&t.removeAttribute("style"),"_vod"in t&&(r.display=i)}}const Xp=/\s*!important$/;function Wc(t,e,n){if(Pe(n))n.forEach(r=>Wc(t,e,r));else if(n==null&&(n=""),e.startsWith("--"))t.setProperty(e,n);else{const r=HO(t,e);Xp.test(n)?t.setProperty($a(r),n.replace(Xp,""),"important"):t[r]=n}}const Jp=["Webkit","Moz","ms"],Wu={};function HO(t,e){const n=Wu[e];if(n)return n;let r=Kn(e);if(r!=="filter"&&r in t)return Wu[e]=r;r=Hl(r);for(let a=0;aHu||(XO.then(()=>Hu=0),Hu=Date.now());function ZO(t,e){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;Mn(QO(r,n.value),e,5,[r])};return n.value=t,n.attached=JO(),n}function QO(t,e){if(Pe(e)){const n=t.stopImmediatePropagation;return t.stopImmediatePropagation=()=>{n.call(t),t._stopped=!0},e.map(r=>a=>!a._stopped&&r&&r(a))}else return e}const eh=/^on[a-z]/,eP=(t,e,n,r,a=!1,i,o,s,l)=>{e==="class"?zO(t,r,a):e==="style"?WO(t,n,r):jl(e)?ed(e)||qO(t,e,n,r,o):(e[0]==="."?(e=e.slice(1),!0):e[0]==="^"?(e=e.slice(1),!1):tP(t,e,r,a))?UO(t,e,r,i,o,s,l):(e==="true-value"?t._trueValue=r:e==="false-value"&&(t._falseValue=r),VO(t,e,r,a))};function tP(t,e,n,r){return r?!!(e==="innerHTML"||e==="textContent"||e in t&&eh.test(e)&&Le(n)):e==="spellcheck"||e==="draggable"||e==="translate"||e==="form"||e==="list"&&t.tagName==="INPUT"||e==="type"&&t.tagName==="TEXTAREA"||eh.test(e)&&mt(n)?!1:e in t}function pU(t){const e=Ct();if(!e)return;const n=e.ut=(a=t(e.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner="${e.uid}"]`)).forEach(i=>Vc(i,a))},r=()=>{const a=t(e.proxy);Hc(e.subTree,a),n(a)};VE(r),De(()=>{const a=new MutationObserver(r);a.observe(e.subTree.el.parentNode,{childList:!0}),cn(()=>a.disconnect())})}function Hc(t,e){if(t.shapeFlag&128){const n=t.suspense;t=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push(()=>{Hc(n.activeBranch,e)})}for(;t.component;)t=t.component.subTree;if(t.shapeFlag&1&&t.el)Vc(t.el,e);else if(t.type===Ae)t.children.forEach(n=>Hc(n,e));else if(t.type===Us){let{el:n,anchor:r}=t;for(;n&&(Vc(n,e),n!==r);)n=n.nextSibling}}function Vc(t,e){if(t.nodeType===1){const n=t.style;for(const r in e)n.setProperty(`--${r}`,e[r])}}const jr="transition",ji="animation",Zn=(t,{slots:e})=>aa(GE,V0(t),e);Zn.displayName="Transition";const H0={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},nP=Zn.props=bt({},E0,H0),va=(t,e=[])=>{Pe(t)?t.forEach(n=>n(...e)):t&&t(...e)},th=t=>t?Pe(t)?t.some(e=>e.length>1):t.length>1:!1;function V0(t){const e={};for(const D in t)D in H0||(e[D]=t[D]);if(t.css===!1)return e;const{name:n="v",type:r,duration:a,enterFromClass:i=`${n}-enter-from`,enterActiveClass:o=`${n}-enter-active`,enterToClass:s=`${n}-enter-to`,appearFromClass:l=i,appearActiveClass:u=o,appearToClass:c=s,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:v=`${n}-leave-active`,leaveToClass:h=`${n}-leave-to`}=t,f=rP(a),p=f&&f[0],g=f&&f[1],{onBeforeEnter:m,onEnter:y,onEnterCancelled:b,onLeave:w,onLeaveCancelled:_,onBeforeAppear:C=m,onAppear:O=y,onAppearCancelled:A=b}=e,E=(D,B,j)=>{Vr(D,B?c:s),Vr(D,B?u:o),j&&j()},N=(D,B)=>{D._isLeaving=!1,Vr(D,d),Vr(D,h),Vr(D,v),B&&B()},R=D=>(B,j)=>{const $=D?O:y,T=()=>E(B,D,j);va($,[B,T]),nh(()=>{Vr(B,D?l:i),Er(B,D?c:s),th($)||rh(B,r,p,T)})};return bt(e,{onBeforeEnter(D){va(m,[D]),Er(D,i),Er(D,o)},onBeforeAppear(D){va(C,[D]),Er(D,l),Er(D,u)},onEnter:R(!1),onAppear:R(!0),onLeave(D,B){D._isLeaving=!0;const j=()=>N(D,B);Er(D,d),K0(),Er(D,v),nh(()=>{D._isLeaving&&(Vr(D,d),Er(D,h),th(w)||rh(D,r,g,j))}),va(w,[D,j])},onEnterCancelled(D){E(D,!1),va(b,[D])},onAppearCancelled(D){E(D,!0),va(A,[D])},onLeaveCancelled(D){N(D),va(_,[D])}})}function rP(t){if(t==null)return null;if(ot(t))return[Vu(t.enter),Vu(t.leave)];{const e=Vu(t);return[e,e]}}function Vu(t){return zx(t)}function Er(t,e){e.split(/\s+/).forEach(n=>n&&t.classList.add(n)),(t._vtc||(t._vtc=new Set)).add(e)}function Vr(t,e){e.split(/\s+/).forEach(r=>r&&t.classList.remove(r));const{_vtc:n}=t;n&&(n.delete(e),n.size||(t._vtc=void 0))}function nh(t){requestAnimationFrame(()=>{requestAnimationFrame(t)})}let aP=0;function rh(t,e,n,r){const a=t._endId=++aP,i=()=>{a===t._endId&&r()};if(n)return setTimeout(i,n);const{type:o,timeout:s,propCount:l}=U0(t,e);if(!o)return r();const u=o+"end";let c=0;const d=()=>{t.removeEventListener(u,v),i()},v=h=>{h.target===t&&++c>=l&&d()};setTimeout(()=>{c(n[f]||"").split(", "),a=r(`${jr}Delay`),i=r(`${jr}Duration`),o=ah(a,i),s=r(`${ji}Delay`),l=r(`${ji}Duration`),u=ah(s,l);let c=null,d=0,v=0;e===jr?o>0&&(c=jr,d=o,v=i.length):e===ji?u>0&&(c=ji,d=u,v=l.length):(d=Math.max(o,u),c=d>0?o>u?jr:ji:null,v=c?c===jr?i.length:l.length:0);const h=c===jr&&/\b(transform|all)(,|$)/.test(r(`${jr}Property`).toString());return{type:c,timeout:d,propCount:v,hasTransform:h}}function ah(t,e){for(;t.lengthih(n)+ih(t[r])))}function ih(t){return Number(t.slice(0,-1).replace(",","."))*1e3}function K0(){return document.body.offsetHeight}const G0=new WeakMap,q0=new WeakMap,Y0={name:"TransitionGroup",props:bt({},nP,{tag:String,moveClass:String}),setup(t,{slots:e}){const n=Ct(),r=x0();let a,i;return oa(()=>{if(!a.length)return;const o=t.moveClass||`${t.name||"v"}-move`;if(!cP(a[0].el,n.vnode.el,o))return;a.forEach(sP),a.forEach(lP);const s=a.filter(uP);K0(),s.forEach(l=>{const u=l.el,c=u.style;Er(u,o),c.transform=c.webkitTransform=c.transitionDuration="";const d=u._moveCb=v=>{v&&v.target!==u||(!v||/transform$/.test(v.propertyName))&&(u.removeEventListener("transitionend",d),u._moveCb=null,Vr(u,o))};u.addEventListener("transitionend",d)})}),()=>{const o=Me(t),s=V0(o);let l=o.tag||Ae;a=i,i=e.default?md(e.default()):[];for(let u=0;udelete t.mode;Y0.props;const oP=Y0;function sP(t){const e=t.el;e._moveCb&&e._moveCb(),e._enterCb&&e._enterCb()}function lP(t){q0.set(t,t.el.getBoundingClientRect())}function uP(t){const e=G0.get(t),n=q0.get(t),r=e.left-n.left,a=e.top-n.top;if(r||a){const i=t.el.style;return i.transform=i.webkitTransform=`translate(${r}px,${a}px)`,i.transitionDuration="0s",t}}function cP(t,e,n){const r=t.cloneNode();t._vtc&&t._vtc.forEach(o=>{o.split(/\s+/).forEach(s=>s&&r.classList.remove(s))}),n.split(/\s+/).forEach(o=>o&&r.classList.add(o)),r.style.display="none";const a=e.nodeType===1?e:e.parentNode;a.appendChild(r);const{hasTransform:i}=U0(r);return a.removeChild(r),i}const fP=["ctrl","shift","alt","meta"],dP={stop:t=>t.stopPropagation(),prevent:t=>t.preventDefault(),self:t=>t.target!==t.currentTarget,ctrl:t=>!t.ctrlKey,shift:t=>!t.shiftKey,alt:t=>!t.altKey,meta:t=>!t.metaKey,left:t=>"button"in t&&t.button!==0,middle:t=>"button"in t&&t.button!==1,right:t=>"button"in t&&t.button!==2,exact:(t,e)=>fP.some(n=>t[`${n}Key`]&&!e.includes(n))},Vn=(t,e)=>(n,...r)=>{for(let a=0;an=>{if(!("key"in n))return;const r=$a(n.key);if(e.some(a=>a===r||vP[a]===r))return t(n)},Jo={beforeMount(t,{value:e},{transition:n}){t._vod=t.style.display==="none"?"":t.style.display,n&&e?n.beforeEnter(t):zi(t,e)},mounted(t,{value:e},{transition:n}){n&&e&&n.enter(t)},updated(t,{value:e,oldValue:n},{transition:r}){!e!=!n&&(r?e?(r.beforeEnter(t),zi(t,!0),r.enter(t)):r.leave(t,()=>{zi(t,!1)}):zi(t,e))},beforeUnmount(t,{value:e}){zi(t,e)}};function zi(t,e){t.style.display=e?t._vod:"none"}const pP=bt({patchProp:eP},jO);let oh;function X0(){return oh||(oh=yO(pP))}const vl=(...t)=>{X0().render(...t)},hP=(...t)=>{const e=X0().createApp(...t),{mount:n}=e;return e.mount=r=>{const a=mP(r);if(!a)return;const i=e._component;!Le(i)&&!i.render&&!i.template&&(i.template=a.innerHTML),a.innerHTML="";const o=n(a,!1,a instanceof SVGElement);return a instanceof Element&&(a.removeAttribute("v-cloak"),a.setAttribute("data-v-app","")),o},e};function mP(t){return mt(t)?document.querySelector(t):t}const gP="modulepreload",yP=function(t){return"/infinite_image_browsing/fe-static/"+t},sh={},Bn=function(e,n,r){if(!n||n.length===0)return e();const a=document.getElementsByTagName("link");return Promise.all(n.map(i=>{if(i=yP(i),i in sh)return;sh[i]=!0;const o=i.endsWith(".css"),s=o?'[rel="stylesheet"]':"";if(!!r)for(let c=a.length-1;c>=0;c--){const d=a[c];if(d.href===i&&(!o||d.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${i}"]${s}`))return;const u=document.createElement("link");if(u.rel=o?"stylesheet":gP,o||(u.as="script",u.crossOrigin=""),u.href=i,document.head.appendChild(u),o)return new Promise((c,d)=>{u.addEventListener("load",c),u.addEventListener("error",()=>d(new Error(`Unable to preload CSS for ${i}`)))})})).then(()=>e())};function He(t){"@babel/helpers - typeof";return He=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},He(t)}function bP(t,e){if(He(t)!=="object"||t===null)return t;var n=t[Symbol.toPrimitive];if(n!==void 0){var r=n.call(t,e||"default");if(He(r)!=="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(e==="string"?String:Number)(t)}function J0(t){var e=bP(t,"string");return He(e)==="symbol"?e:String(e)}function te(t,e,n){return e=J0(e),e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function lh(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable})),n.push.apply(n,r)}return n}function M(t){for(var e=1;e1&&arguments[1]!==void 0?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return typeof t=="function"?t(e):t??n}function be(){for(var t=[],e=0;e0},t.prototype.connect_=function(){!Uc||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),LP?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},t.prototype.disconnect_=function(){!Uc||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},t.prototype.onTransitionEnd_=function(e){var n=e.propertyName,r=n===void 0?"":n,a=$P.some(function(i){return!!~r.indexOf(i)});a&&this.refresh()},t.getInstance=function(){return this.instance_||(this.instance_=new t),this.instance_},t.instance_=null,t}(),ew=function(t,e){for(var n=0,r=Object.keys(e);n"u"||!(Element instanceof Object))){if(!(e instanceof mi(e).Element))throw new TypeError('parameter 1 is not of type "Element".');var n=this.observations_;n.has(e)||(n.set(e,new UP(e)),this.controller_.addObserver(this),this.controller_.refresh())}},t.prototype.unobserve=function(e){if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");if(!(typeof Element>"u"||!(Element instanceof Object))){if(!(e instanceof mi(e).Element))throw new TypeError('parameter 1 is not of type "Element".');var n=this.observations_;n.has(e)&&(n.delete(e),n.size||this.controller_.removeObserver(this))}},t.prototype.disconnect=function(){this.clearActive(),this.observations_.clear(),this.controller_.removeObserver(this)},t.prototype.gatherActive=function(){var e=this;this.clearActive(),this.observations_.forEach(function(n){n.isActive()&&e.activeObservations_.push(n)})},t.prototype.broadcastActive=function(){if(this.hasActive()){var e=this.callbackCtx_,n=this.activeObservations_.map(function(r){return new KP(r.target,r.broadcastRect())});this.callback_.call(e,n,e),this.clearActive()}},t.prototype.clearActive=function(){this.activeObservations_.splice(0)},t.prototype.hasActive=function(){return this.activeObservations_.length>0},t}(),nw=typeof WeakMap<"u"?new WeakMap:new Q0,rw=function(){function t(e){if(!(this instanceof t))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=DP.getInstance(),r=new GP(e,n,this);nw.set(this,r)}return t}();["observe","unobserve","disconnect"].forEach(function(t){rw.prototype[t]=function(){var e;return(e=nw.get(this))[t].apply(e,arguments)}});var aw=function(){return typeof pl.ResizeObserver<"u"?pl.ResizeObserver:rw}();function iw(t){if(Array.isArray(t))return t}function qP(t,e){var n=t==null?null:typeof Symbol<"u"&&t[Symbol.iterator]||t["@@iterator"];if(n!=null){var r,a,i,o,s=[],l=!0,u=!1;try{if(i=(n=n.call(t)).next,e===0){if(Object(n)!==n)return;l=!1}else for(;!(l=(r=i.call(n)).done)&&(s.push(r.value),s.length!==e);l=!0);}catch(c){u=!0,a=c}finally{try{if(!l&&n.return!=null&&(o=n.return(),Object(o)!==o))return}finally{if(u)throw a}}return s}}function Kc(t,e){(e==null||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n0&&arguments[0]!==void 0?arguments[0]:"",n=arguments.length>1?arguments[1]:void 0,r={},a=/;(?![^(]*\))/g,i=/:(.+)/;return He(e)==="object"?e:(e.split(a).forEach(function(o){if(o){var s=o.split(i);if(s.length>1){var l=n?Pd(s[0].trim()):s[0].trim();r[l]=s[1].trim()}}}),r)},Za=function(e,n){return e[n]!==void 0},bn=function t(){var e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,r=Array.isArray(e)?e:[e],a=[];return r.forEach(function(i){Array.isArray(i)?a.push.apply(a,Ge(t(i,n))):i&&i.type===Ae?a.push.apply(a,Ge(t(i.children,n))):i&&pr(i)?n&&!pw(i)?a.push(i):n||a.push(i):Gc(i)&&a.push(i)}),a},bT=function(e){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"default",r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};if(pr(e))return e.type===Ae?n==="default"?bn(e.children):[]:e.children&&e.children[n]?bn(e.children[n](r)):[];var a=e.$slots[n]&&e.$slots[n](r);return bn(a)},Pa=function(e){for(var n,r=(e==null||(n=e.vnode)===null||n===void 0?void 0:n.el)||e&&(e.$el||e);r&&!r.tagName;)r=r.nextSibling;return r},wT=function(e){var n={};if(e.$&&e.$.vnode){var r=e.$.vnode.props||{};Object.keys(e.$props).forEach(function(s){var l=e.$props[s],u=TP(s);(l!==void 0||u in r)&&(n[s]=l)})}else if(pr(e)&&He(e.type)==="object"){var a=e.props||{},i={};Object.keys(a).forEach(function(s){i[Pd(s)]=a[s]});var o=e.type.props||{};Object.keys(o).forEach(function(s){var l=AP(o,i,s,i[s]);(l!==void 0||s in i)&&(n[s]=l)})}return n},vw=function(e){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"default",r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:e,a=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!0,i=void 0;if(e.$){var o=e[n];if(o!==void 0)return typeof o=="function"&&a?o(r):o;i=e.$slots[n],i=a&&i?i(r):i}else if(pr(e)){var s=e.props&&e.props[n];if(s!==void 0&&e.props!==null)return typeof s=="function"&&a?s(r):s;e.type===Ae?i=e.children:e.children&&e.children[n]&&(i=e.children[n],i=a&&i?i(r):i)}return Array.isArray(i)&&(i=bn(i),i=i.length===1?i[0]:i,i=i.length===0?void 0:i),i};function dh(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,n={};return t.$?n=M(M({},n),t.$attrs):n=M(M({},n),t.props),dw(n)[e?"onEvents":"events"]}function _T(t,e){var n=(pr(t)?t.props:t.$attrs)||{},r=n.style||{};if(typeof r=="string")r=yT(r,e);else if(e&&r){var a={};return Object.keys(r).forEach(function(i){return a[Pd(i)]=r[i]}),a}return r}function pw(t){return t&&(t.type===yn||t.type===Ae&&t.children.length===0||t.type===La&&t.children.trim()==="")}function Mi(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],e=[];return t.forEach(function(n){Array.isArray(n)?e.push.apply(e,Ge(n)):(n==null?void 0:n.type)===Ae?e.push.apply(e,Ge(Mi(n.children))):e.push(n)}),e.filter(function(n){return!pw(n)})}function qn(t){return Array.isArray(t)&&t.length===1&&(t=t[0]),t&&t.__v_isVNode&&He(t.type)!=="symbol"}function An(t,e){var n,r,a=arguments.length>2&&arguments[2]!==void 0?arguments[2]:"default";return(n=e[a])!==null&&n!==void 0?n:(r=t[a])===null||r===void 0?void 0:r.call(t)}const gi=de({compatConfig:{MODE:3},name:"ResizeObserver",props:{disabled:Boolean,onResize:Function},emits:["resize"],setup:function(e,n){var r=n.slots,a=nt({width:0,height:0,offsetHeight:0,offsetWidth:0}),i=null,o=null,s=function(){o&&(o.disconnect(),o=null)},l=function(v){var h=e.onResize,f=v[0].target,p=f.getBoundingClientRect(),g=p.width,m=p.height,y=f.offsetWidth,b=f.offsetHeight,w=Math.floor(g),_=Math.floor(m);if(a.width!==w||a.height!==_||a.offsetWidth!==y||a.offsetHeight!==b){var C={width:w,height:_,offsetWidth:y,offsetHeight:b};Lt(a,C),h&&Promise.resolve().then(function(){h(M(M({},C),{},{offsetWidth:y,offsetHeight:b}),f)})}},u=Ct(),c=function(){var v=e.disabled;if(v){s();return}var h=Pa(u),f=h!==i;f&&(s(),i=h),!o&&h&&(o=new aw(l),o.observe(h))};return De(function(){c()}),oa(function(){c()}),cn(function(){s()}),ve(function(){return e.disabled},function(){c()},{flush:"post"}),function(){var d;return(d=r.default)===null||d===void 0?void 0:d.call(r)[0]}}});var hw=function(e){return setTimeout(e,16)},mw=function(e){return clearTimeout(e)};typeof window<"u"&&"requestAnimationFrame"in window&&(hw=function(e){return window.requestAnimationFrame(e)},mw=function(e){return window.cancelAnimationFrame(e)});var vh=0,Ad=new Map;function gw(t){Ad.delete(t)}function Fe(t){var e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:1;vh+=1;var n=vh;function r(a){if(a===0)gw(n),t();else{var i=hw(function(){r(a-1)});Ad.set(n,i)}}return r(e),n}Fe.cancel=function(t){var e=Ad.get(t);return gw(e),mw(e)};var Da=function(){for(var e=arguments.length,n=new Array(e),r=0;r=0)&&(n[a]=t[a]);return n}function vt(t,e){if(t==null)return{};var n=CT(t,e),r,a;if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(a=0;a=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(n[r]=t[r])}return n}const bw={items_per_page:"/ page",jump_to:"Go to",jump_to_confirm:"confirm",page:"",prev_page:"Previous Page",next_page:"Next Page",prev_5:"Previous 5 Pages",next_5:"Next 5 Pages",prev_3:"Previous 3 Pages",next_3:"Next 3 Pages"};var ST={locale:"en_US",today:"Today",now:"Now",backToToday:"Back to today",ok:"Ok",clear:"Clear",month:"Month",year:"Year",timeSelect:"select time",dateSelect:"select date",weekSelect:"Choose a week",monthSelect:"Choose a month",yearSelect:"Choose a year",decadeSelect:"Choose a decade",yearFormat:"YYYY",dateFormat:"M/D/YYYY",dayFormat:"D",dateTimeFormat:"M/D/YYYY HH:mm:ss",monthBeforeYear:!0,previousMonth:"Previous month (PageUp)",nextMonth:"Next month (PageDown)",previousYear:"Last year (Control + left)",nextYear:"Next year (Control + right)",previousDecade:"Last decade",nextDecade:"Next decade",previousCentury:"Last century",nextCentury:"Next century"};const xT=ST;var ET={placeholder:"Select time",rangePlaceholder:["Start time","End time"]};const ww=ET;var OT={lang:M({placeholder:"Select date",yearPlaceholder:"Select year",quarterPlaceholder:"Select quarter",monthPlaceholder:"Select month",weekPlaceholder:"Select week",rangePlaceholder:["Start date","End date"],rangeYearPlaceholder:["Start year","End year"],rangeQuarterPlaceholder:["Start quarter","End quarter"],rangeMonthPlaceholder:["Start month","End month"],rangeWeekPlaceholder:["Start week","End week"]},xT),timePickerLocale:M({},ww)};const hh=OT;var vn="${label} is not a valid ${type}",PT={locale:"en",Pagination:bw,DatePicker:hh,TimePicker:ww,Calendar:hh,global:{placeholder:"Please select"},Table:{filterTitle:"Filter menu",filterConfirm:"OK",filterReset:"Reset",filterEmptyText:"No filters",filterCheckall:"Select all items",filterSearchPlaceholder:"Search in filters",emptyText:"No data",selectAll:"Select current page",selectInvert:"Invert current page",selectNone:"Clear all data",selectionAll:"Select all data",sortTitle:"Sort",expand:"Expand row",collapse:"Collapse row",triggerDesc:"Click to sort descending",triggerAsc:"Click to sort ascending",cancelSort:"Click to cancel sorting"},Modal:{okText:"OK",cancelText:"Cancel",justOkText:"OK"},Popconfirm:{okText:"OK",cancelText:"Cancel"},Transfer:{titles:["",""],searchPlaceholder:"Search here",itemUnit:"item",itemsUnit:"items",remove:"Remove",selectCurrent:"Select current page",removeCurrent:"Remove current page",selectAll:"Select all data",removeAll:"Remove all data",selectInvert:"Invert current page"},Upload:{uploading:"Uploading...",removeFile:"Remove file",uploadError:"Upload error",previewFile:"Preview file",downloadFile:"Download file"},Empty:{description:"No Data"},Icon:{icon:"icon"},Text:{edit:"Edit",copy:"Copy",copied:"Copied",expand:"Expand"},PageHeader:{back:"Back"},Form:{optional:"(optional)",defaultValidateMessages:{default:"Field validation error for ${label}",required:"Please enter ${label}",enum:"${label} must be one of [${enum}]",whitespace:"${label} cannot be a blank character",date:{format:"${label} date format is invalid",parse:"${label} cannot be converted to a date",invalid:"${label} is an invalid date"},types:{string:vn,method:vn,array:vn,object:vn,number:vn,date:vn,boolean:vn,integer:vn,float:vn,regexp:vn,email:vn,url:vn,hex:vn},string:{len:"${label} must be ${len} characters",min:"${label} must be at least ${min} characters",max:"${label} must be up to ${max} characters",range:"${label} must be between ${min}-${max} characters"},number:{len:"${label} must be equal to ${len}",min:"${label} must be minimum ${min}",max:"${label} must be maximum ${max}",range:"${label} must be between ${min}-${max}"},array:{len:"Must be ${len} ${label}",min:"At least ${min} ${label}",max:"At most ${max} ${label}",range:"The amount of ${label} must be between ${min}-${max}"},pattern:{mismatch:"${label} does not match the pattern ${pattern}"}}},Image:{preview:"Preview"}};const To=PT,_w=de({compatConfig:{MODE:3},name:"LocaleReceiver",props:{componentName:String,defaultLocale:{type:[Object,Function]},children:{type:Function}},setup:function(e,n){var r=n.slots,a=Je("localeData",{}),i=G(function(){var s=e.componentName,l=s===void 0?"global":s,u=e.defaultLocale,c=u||To[l||"global"],d=a.antLocale,v=l&&d?d[l]:{};return M(M({},typeof c=="function"?c():c),v||{})}),o=G(function(){var s=a.antLocale,l=s&&s.locale;return s&&s.exist&&!l?To.locale:l});return function(){var s=e.children||r.default,l=a.antLocale;return s==null?void 0:s(i.value,o.value,l)}}});function Md(t,e,n){var r=Je("localeData",{}),a=G(function(){var i=r.antLocale,o=xe(e)||To[t||"global"],s=t&&i?i[t]:{};return M(M(M({},typeof o=="function"?o():o),s||{}),xe(n)||{})});return[a]}var Cw=function(){var e=Qe("empty",{}),n=e.getPrefixCls,r=n("empty-img-default");return x("svg",{class:r,width:"184",height:"152",viewBox:"0 0 184 152"},[x("g",{fill:"none","fill-rule":"evenodd"},[x("g",{transform:"translate(24 31.67)"},[x("ellipse",{class:"".concat(r,"-ellipse"),cx:"67.797",cy:"106.89",rx:"67.797",ry:"12.668"},null),x("path",{class:"".concat(r,"-path-1"),d:"M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z"},null),x("path",{class:"".concat(r,"-path-2"),d:"M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z",transform:"translate(13.56)"},null),x("path",{class:"".concat(r,"-path-3"),d:"M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"},null),x("path",{class:"".concat(r,"-path-4"),d:"M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z"},null)]),x("path",{class:"".concat(r,"-path-5"),d:"M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z"},null),x("g",{class:"".concat(r,"-g"),transform:"translate(149.65 15.383)"},[x("ellipse",{cx:"20.654",cy:"3.167",rx:"2.849",ry:"2.815"},null),x("path",{d:"M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"},null)])])])};Cw.PRESENTED_IMAGE_DEFAULT=!0;const TT=Cw;var Sw=function(){var e=Qe("empty",{}),n=e.getPrefixCls,r=n("empty-img-simple");return x("svg",{class:r,width:"64",height:"41",viewBox:"0 0 64 41"},[x("g",{transform:"translate(0 1)",fill:"none","fill-rule":"evenodd"},[x("ellipse",{class:"".concat(r,"-ellipse"),fill:"#F5F5F5",cx:"32",cy:"33",rx:"32",ry:"7"},null),x("g",{class:"".concat(r,"-g"),"fill-rule":"nonzero",stroke:"#D9D9D9"},[x("path",{d:"M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"},null),x("path",{d:"M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z",fill:"#FAFAFA",class:"".concat(r,"-path")},null)])])])};Sw.PRESENTED_IMAGE_SIMPLE=!0;const IT=Sw;function mh(t,e){for(var n=0;n=0||(a[n]=t[n]);return a}function gh(t){return((e=t)!=null&&typeof e=="object"&&Array.isArray(e)===!1)==1&&Object.prototype.toString.call(t)==="[object Object]";var e}var Pw=Object.prototype,Tw=Pw.toString,AT=Pw.hasOwnProperty,Iw=/^\s*function (\w+)/;function yh(t){var e,n=(e=t==null?void 0:t.type)!==null&&e!==void 0?e:t;if(n){var r=n.toString().match(Iw);return r?r[1]:""}return""}var Ma=function(t){var e,n;return gh(t)!==!1&&typeof(e=t.constructor)=="function"&&gh(n=e.prototype)!==!1&&n.hasOwnProperty("isPrototypeOf")!==!1},MT=function(t){return t},en=MT,Io=function(t,e){return AT.call(t,e)},kT=Number.isInteger||function(t){return typeof t=="number"&&isFinite(t)&&Math.floor(t)===t},yi=Array.isArray||function(t){return Tw.call(t)==="[object Array]"},bi=function(t){return Tw.call(t)==="[object Function]"},ml=function(t){return Ma(t)&&Io(t,"_vueTypes_name")},Aw=function(t){return Ma(t)&&(Io(t,"type")||["_vueTypes_name","validator","default","required"].some(function(e){return Io(t,e)}))};function kd(t,e){return Object.defineProperty(t.bind(e),"__original",{value:t})}function Fa(t,e,n){var r;n===void 0&&(n=!1);var a=!0,i="";r=Ma(t)?t:{type:t};var o=ml(r)?r._vueTypes_name+" - ":"";if(Aw(r)&&r.type!==null){if(r.type===void 0||r.type===!0||!r.required&&e===void 0)return a;yi(r.type)?(a=r.type.some(function(d){return Fa(d,e,!0)===!0}),i=r.type.map(function(d){return yh(d)}).join(" or ")):a=(i=yh(r))==="Array"?yi(e):i==="Object"?Ma(e):i==="String"||i==="Number"||i==="Boolean"||i==="Function"?function(d){if(d==null)return"";var v=d.constructor.toString().match(Iw);return v?v[1]:""}(e)===i:e instanceof r.type}if(!a){var s=o+'value "'+e+'" should be of type "'+i+'"';return n===!1?(en(s),!1):s}if(Io(r,"validator")&&bi(r.validator)){var l=en,u=[];if(en=function(d){u.push(d)},a=r.validator(e),en=l,!a){var c=(u.length>1?"* ":"")+u.join(` +* `);return u.length=0,n===!1?(en(c),a):c}}return a}function wn(t,e){var n=Object.defineProperties(e,{_vueTypes_name:{value:t,writable:!0},isRequired:{get:function(){return this.required=!0,this}},def:{value:function(a){return a!==void 0||this.default?bi(a)||Fa(this,a,!0)===!0?(this.default=yi(a)?function(){return[].concat(a)}:Ma(a)?function(){return Object.assign({},a)}:a,this):(en(this._vueTypes_name+' - invalid default value: "'+a+'"'),this):this}}}),r=n.validator;return bi(r)&&(n.validator=kd(r,n)),n}function mr(t,e){var n=wn(t,e);return Object.defineProperty(n,"validate",{value:function(r){return bi(this.validator)&&en(this._vueTypes_name+` - calling .validate() will overwrite the current custom validator function. Validator info: +`+JSON.stringify(this)),this.validator=kd(r,this),this}})}function bh(t,e,n){var r,a,i=(r=e,a={},Object.getOwnPropertyNames(r).forEach(function(d){a[d]=Object.getOwnPropertyDescriptor(r,d)}),Object.defineProperties({},a));if(i._vueTypes_name=t,!Ma(n))return i;var o,s,l=n.validator,u=Ow(n,["validator"]);if(bi(l)){var c=i.validator;c&&(c=(s=(o=c).__original)!==null&&s!==void 0?s:o),i.validator=kd(c?function(d){return c.call(this,d)&&l.call(this,d)}:l,i)}return Object.assign(i,u)}function nu(t){return t.replace(/^(?!\s*$)/gm," ")}var NT=function(){return mr("any",{})},RT=function(){return mr("function",{type:Function})},$T=function(){return mr("boolean",{type:Boolean})},LT=function(){return mr("string",{type:String})},DT=function(){return mr("number",{type:Number})},FT=function(){return mr("array",{type:Array})},BT=function(){return mr("object",{type:Object})},jT=function(){return wn("integer",{type:Number,validator:function(t){return kT(t)}})},zT=function(){return wn("symbol",{validator:function(t){return typeof t=="symbol"}})};function WT(t,e){if(e===void 0&&(e="custom validation failed"),typeof t!="function")throw new TypeError("[VueTypes error]: You must provide a function as argument");return wn(t.name||"<>",{validator:function(n){var r=t(n);return r||en(this._vueTypes_name+" - "+e),r}})}function HT(t){if(!yi(t))throw new TypeError("[VueTypes error]: You must provide an array as argument.");var e='oneOf - value should be one of "'+t.join('", "')+'".',n=t.reduce(function(r,a){if(a!=null){var i=a.constructor;r.indexOf(i)===-1&&r.push(i)}return r},[]);return wn("oneOf",{type:n.length>0?n:void 0,validator:function(r){var a=t.indexOf(r)!==-1;return a||en(e),a}})}function VT(t){if(!yi(t))throw new TypeError("[VueTypes error]: You must provide an array as argument");for(var e=!1,n=[],r=0;r0&&n.some(function(l){return o.indexOf(l)===-1})){var s=n.filter(function(l){return o.indexOf(l)===-1});return en(s.length===1?'shape - required property "'+s[0]+'" is not defined.':'shape - required properties "'+s.join('", "')+'" are not defined.'),!1}return o.every(function(l){if(e.indexOf(l)===-1)return i._vueTypes_isLoose===!0||(en('shape - shape definition does not include a "'+l+'" property. Allowed keys: "'+e.join('", "')+'".'),!1);var u=Fa(t[l],a[l],!0);return typeof u=="string"&&en('shape - "'+l+`" property validation error: + `+nu(u)),u===!0})}});return Object.defineProperty(r,"_vueTypes_isLoose",{writable:!0,value:!1}),Object.defineProperty(r,"loose",{get:function(){return this._vueTypes_isLoose=!0,this}}),r}var ar=function(){function t(){}return t.extend=function(e){var n=this;if(yi(e))return e.forEach(function(d){return n.extend(d)}),this;var r=e.name,a=e.validate,i=a!==void 0&&a,o=e.getter,s=o!==void 0&&o,l=Ow(e,["name","validate","getter"]);if(Io(this,r))throw new TypeError('[VueTypes error]: Type "'+r+'" already defined');var u,c=l.type;return ml(c)?(delete l.type,Object.defineProperty(this,r,s?{get:function(){return bh(r,c,l)}}:{value:function(){var d,v=bh(r,c,l);return v.validator&&(v.validator=(d=v.validator).bind.apply(d,[v].concat([].slice.call(arguments)))),v}})):(u=s?{get:function(){var d=Object.assign({},l);return i?mr(r,d):wn(r,d)},enumerable:!0}:{value:function(){var d,v,h=Object.assign({},l);return d=i?mr(r,h):wn(r,h),h.validator&&(d.validator=(v=h.validator).bind.apply(v,[d].concat([].slice.call(arguments)))),d},enumerable:!0},Object.defineProperty(this,r,u))},xw(t,null,[{key:"any",get:function(){return NT()}},{key:"func",get:function(){return RT().def(this.defaults.func)}},{key:"bool",get:function(){return $T().def(this.defaults.bool)}},{key:"string",get:function(){return LT().def(this.defaults.string)}},{key:"number",get:function(){return DT().def(this.defaults.number)}},{key:"array",get:function(){return FT().def(this.defaults.array)}},{key:"object",get:function(){return BT().def(this.defaults.object)}},{key:"integer",get:function(){return jT().def(this.defaults.integer)}},{key:"symbol",get:function(){return zT()}}]),t}();function Mw(t){var e;return t===void 0&&(t={func:function(){},bool:!0,string:"",number:0,array:function(){return[]},object:function(){return{}},integer:0}),(e=function(n){function r(){return n.apply(this,arguments)||this}return Ew(r,n),xw(r,null,[{key:"sensibleDefaults",get:function(){return Gs({},this.defaults)},set:function(a){this.defaults=a!==!1?Gs({},a!==!0?a:t):{}}}]),r}(ar)).defaults=Gs({},t),e}ar.defaults={},ar.custom=WT,ar.oneOf=HT,ar.instanceOf=KT,ar.oneOfType=VT,ar.arrayOf=UT,ar.objectOf=GT,ar.shape=qT,ar.utils={validate:function(t,e){return Fa(e,t,!0)===!0},toType:function(t,e,n){return n===void 0&&(n=!1),n?mr(t,e):wn(t,e)}};(function(t){function e(){return t.apply(this,arguments)||this}return Ew(e,t),e})(Mw());var kw=Mw({func:void 0,bool:void 0,string:void 0,number:void 0,array:void 0,object:void 0,integer:void 0});kw.extend([{name:"looseBool",getter:!0,type:Boolean,default:void 0},{name:"style",getter:!0,type:[String,Object],default:void 0},{name:"VueNode",getter:!0,type:null}]);function gU(t){return t.default=void 0,t}const Q=kw;var YT=["image","description","imageStyle","class"],Nw=x(TT,null,null),Rw=x(IT,null,null),Ni=function(e,n){var r,a=n.slots,i=a===void 0?{}:a,o=n.attrs,s=Qe("empty",e),l=s.direction,u=s.prefixCls,c=u.value,d=M(M({},e),o),v=d.image,h=v===void 0?Nw:v,f=d.description,p=f===void 0?((r=i.description)===null||r===void 0?void 0:r.call(i))||void 0:f,g=d.imageStyle,m=d.class,y=m===void 0?"":m,b=vt(d,YT);return x(_w,{componentName:"Empty",children:function(_){var C,O=typeof p<"u"?p:_.description,A=typeof O=="string"?O:"empty",E=null;return typeof h=="string"?E=x("img",{alt:A,src:h},null):E=h,x("div",M({class:be(c,y,(C={},te(C,"".concat(c,"-normal"),h===Rw),te(C,"".concat(c,"-rtl"),l.value==="rtl"),C))},b),[x("div",{class:"".concat(c,"-image"),style:g},[E]),O&&x("p",{class:"".concat(c,"-description")},[O]),i.default&&x("div",{class:"".concat(c,"-footer")},[Mi(i.default())])])}},null)};Ni.displayName="AEmpty";Ni.PRESENTED_IMAGE_DEFAULT=Nw;Ni.PRESENTED_IMAGE_SIMPLE=Rw;Ni.inheritAttrs=!1;Ni.props={prefixCls:String,image:Q.any,description:Q.any,imageStyle:{type:Object,default:void 0}};const Vi=ki(Ni);var XT=function(e){var n=Qe("empty",e),r=n.prefixCls,a=function(o){switch(o){case"Table":case"List":return x(Vi,{image:Vi.PRESENTED_IMAGE_SIMPLE},null);case"Select":case"TreeSelect":case"Cascader":case"Transfer":case"Mentions":return x(Vi,{image:Vi.PRESENTED_IMAGE_SIMPLE,class:"".concat(r.value,"-small")},null);default:return x(Vi,null,null)}};return a(e.componentName)};function $w(t){return x(XT,{componentName:t},null)}var wh={};function JT(t,e){}function ZT(t,e,n){!e&&!wh[n]&&(t(!1,n),wh[n]=!0)}function Lw(t,e){ZT(JT,t,e)}const ru=function(t,e){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:"";Lw(t,"[antdv: ".concat(e,"] ").concat(n))};var qc="internalMark",qs=de({compatConfig:{MODE:3},name:"ALocaleProvider",props:{locale:{type:Object},ANT_MARK__:String},setup:function(e,n){var r=n.slots;ru(e.ANT_MARK__===qc,"LocaleProvider","`LocaleProvider` is deprecated. Please use `locale` with `ConfigProvider` instead");var a=nt({antLocale:M(M({},e.locale),{},{exist:!0}),ANT_MARK__:qc});return pt("localeData",a),ve(function(){return e.locale},function(){a.antLocale=M(M({},e.locale),{},{exist:!0})},{immediate:!0}),function(){var i;return(i=r.default)===null||i===void 0?void 0:i.call(r)}}});qs.install=function(t){return t.component(qs.name,qs),t};const QT=ki(qs);Da("bottomLeft","bottomRight","topLeft","topRight");var Zo=function(e){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},r=M(e?{name:e,appear:!0,enterFromClass:"".concat(e,"-enter ").concat(e,"-enter-prepare"),enterActiveClass:"".concat(e,"-enter ").concat(e,"-enter-prepare"),enterToClass:"".concat(e,"-enter ").concat(e,"-enter-active"),leaveFromClass:" ".concat(e,"-leave"),leaveActiveClass:"".concat(e,"-leave ").concat(e,"-leave-active"),leaveToClass:"".concat(e,"-leave ").concat(e,"-leave-active")}:{css:!1},n);return r},eI=function(e){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},r=M(e?{name:e,appear:!0,appearActiveClass:"".concat(e),appearToClass:"".concat(e,"-appear ").concat(e,"-appear-active"),enterFromClass:"".concat(e,"-appear ").concat(e,"-enter ").concat(e,"-appear-prepare ").concat(e,"-enter-prepare"),enterActiveClass:"".concat(e),enterToClass:"".concat(e,"-enter ").concat(e,"-appear ").concat(e,"-appear-active ").concat(e,"-enter-active"),leaveActiveClass:"".concat(e," ").concat(e,"-leave"),leaveToClass:"".concat(e,"-leave-active")}:{css:!1},n);return r},ka=function(e,n,r){return r!==void 0?r:"".concat(e,"-").concat(n)};const tI=de({name:"Notice",inheritAttrs:!1,props:["prefixCls","duration","updateMark","noticeKey","closeIcon","closable","props","onClick","onClose","holder","visible"],setup:function(e,n){var r=n.attrs,a=n.slots,i,o=!1,s=G(function(){return e.duration===void 0?4.5:e.duration}),l=function(){s.value&&!o&&(i=setTimeout(function(){c()},s.value*1e3))},u=function(){i&&(clearTimeout(i),i=null)},c=function(h){h&&h.stopPropagation(),u();var f=e.onClose,p=e.noticeKey;f&&f(p)},d=function(){u(),l()};return De(function(){l()}),cn(function(){o=!0,u()}),ve([s,function(){return e.updateMark},function(){return e.visible}],function(v,h){var f=Oe(v,3),p=f[0],g=f[1],m=f[2],y=Oe(h,3),b=y[0],w=y[1],_=y[2];(p!==b||g!==w||m!==_&&_)&&d()},{flush:"post"}),function(){var v,h,f=e.prefixCls,p=e.closable,g=e.closeIcon,m=g===void 0?(v=a.closeIcon)===null||v===void 0?void 0:v.call(a):g,y=e.onClick,b=e.holder,w=r.class,_=r.style,C="".concat(f,"-notice"),O=Object.keys(r).reduce(function(E,N){return(N.substr(0,5)==="data-"||N.substr(0,5)==="aria-"||N==="role")&&(E[N]=r[N]),E},{}),A=x("div",M({class:be(C,w,te({},"".concat(C,"-closable"),p)),style:_,onMouseenter:u,onMouseleave:l,onClick:y},O),[x("div",{class:"".concat(C,"-content")},[(h=a.default)===null||h===void 0?void 0:h.call(a)]),p?x("a",{tabindex:0,onClick:c,class:"".concat(C,"-close")},[m||x("span",{class:"".concat(C,"-close-x")},null)]):null]);return b?x(Sd,{to:b},{default:function(){return A}}):A}}});var nI=["name","getContainer","appContext","prefixCls","rootPrefixCls","transitionName","hasTransitionName"],_h=0,rI=Date.now();function Ch(){var t=_h;return _h+=1,"rcNotification_".concat(rI,"_").concat(t)}var Yc=de({name:"Notification",inheritAttrs:!1,props:["prefixCls","transitionName","animation","maxCount","closeIcon"],setup:function(e,n){var r=n.attrs,a=n.expose,i=n.slots,o=new Map,s=z([]),l=G(function(){var d=e.prefixCls,v=e.animation,h=v===void 0?"fade":v,f=e.transitionName;return!f&&h&&(f="".concat(d,"-").concat(h)),eI(f)}),u=function(v,h){var f=v.key||Ch(),p=M(M({},v),{},{key:f}),g=e.maxCount,m=s.value.map(function(b){return b.notice.key}).indexOf(f),y=s.value.concat();m!==-1?y.splice(m,1,{notice:p,holderCallback:h}):(g&&s.value.length>=g&&(p.key=y[0].notice.key,p.updateMark=Ch(),p.userPassKey=f,y.shift()),y.push({notice:p,holderCallback:h})),s.value=y},c=function(v){s.value=s.value.filter(function(h){var f=h.notice,p=f.key,g=f.userPassKey,m=g||p;return m!==v})};return a({add:u,remove:c,notices:s}),function(){var d,v,h=e.prefixCls,f=e.closeIcon,p=f===void 0?(d=i.closeIcon)===null||d===void 0?void 0:d.call(i,{prefixCls:h}):f,g=s.value.map(function(y,b){var w=y.notice,_=y.holderCallback,C=b===s.value.length-1?w.updateMark:void 0,O=w.key,A=w.userPassKey,E=w.content,N=M(M(M({prefixCls:h,closeIcon:typeof p=="function"?p({prefixCls:h}):p},w),w.props),{},{key:O,noticeKey:A||O,updateMark:C,onClose:function(D){var B;c(D),(B=w.onClose)===null||B===void 0||B.call(w)},onClick:w.onClick});return _?x("div",{key:O,class:"".concat(h,"-hook-holder"),ref:function(D){typeof O>"u"||(D?(o.set(O,D),_(D,N)):o.delete(O))}},null):x(tI,N,{default:function(){return[typeof E=="function"?E({prefixCls:h}):E]}})}),m=(v={},te(v,h,1),te(v,r.class,!!r.class),v);return x("div",{class:m,style:r.style||{top:"65px",left:"50%"}},[x(oP,M({tag:"div"},l.value),{default:function(){return[g]}})])}}});Yc.newInstance=function(e,n){var r=e||{},a=r.name,i=a===void 0?"notification":a,o=r.getContainer,s=r.appContext,l=r.prefixCls,u=r.rootPrefixCls,c=r.transitionName,d=r.hasTransitionName,v=vt(r,nI),h=document.createElement("div");if(o){var f=o();f.appendChild(h)}else document.body.appendChild(h);var p=de({compatConfig:{MODE:3},name:"NotificationWrapper",setup:function(y,b){var w=b.attrs,_=z();return De(function(){n({notice:function(O){var A;(A=_.value)===null||A===void 0||A.add(O)},removeNotice:function(O){var A;(A=_.value)===null||A===void 0||A.remove(O)},destroy:function(){vl(null,h),h.parentNode&&h.parentNode.removeChild(h)},component:_})}),function(){var C=sn,O=C.getPrefixCls(i,l),A=C.getRootPrefixCls(u,O),E=d?c:"".concat(A,"-").concat(c);return x(ui,M(M({},C),{},{notUpdateGlobalConfig:!0,prefixCls:A}),{default:function(){return[x(Yc,M(M({ref:_},w),{},{prefixCls:O,transitionName:E}),null)]}})}}}),g=x(p,v);g.appContext=s||g.appContext,vl(g,h)};const Dw=Yc;var aI={icon:{tag:"svg",attrs:{viewBox:"0 0 1024 1024",focusable:"false"},children:[{tag:"path",attrs:{d:"M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"}}]},name:"loading",theme:"outlined"};const iI=aI;function zt(t,e){oI(t)&&(t="100%");var n=sI(t);return t=e===360?t:Math.min(e,Math.max(0,parseFloat(t))),n&&(t=parseInt(String(t*e),10)/100),Math.abs(t-e)<1e-6?1:(e===360?t=(t<0?t%e+e:t%e)/parseFloat(String(e)):t=t%e/parseFloat(String(e)),t)}function ws(t){return Math.min(1,Math.max(0,t))}function oI(t){return typeof t=="string"&&t.indexOf(".")!==-1&&parseFloat(t)===1}function sI(t){return typeof t=="string"&&t.indexOf("%")!==-1}function Fw(t){return t=parseFloat(t),(isNaN(t)||t<0||t>1)&&(t=1),t}function _s(t){return t<=1?"".concat(Number(t)*100,"%"):t}function Ta(t){return t.length===1?"0"+t:String(t)}function lI(t,e,n){return{r:zt(t,255)*255,g:zt(e,255)*255,b:zt(n,255)*255}}function Sh(t,e,n){t=zt(t,255),e=zt(e,255),n=zt(n,255);var r=Math.max(t,e,n),a=Math.min(t,e,n),i=0,o=0,s=(r+a)/2;if(r===a)o=0,i=0;else{var l=r-a;switch(o=s>.5?l/(2-r-a):l/(r+a),r){case t:i=(e-n)/l+(e1&&(n-=1),n<1/6?t+(e-t)*(6*n):n<1/2?e:n<2/3?t+(e-t)*(2/3-n)*6:t}function uI(t,e,n){var r,a,i;if(t=zt(t,360),e=zt(e,100),n=zt(n,100),e===0)a=n,i=n,r=n;else{var o=n<.5?n*(1+e):n+e-n*e,s=2*n-o;r=Uu(s,o,t+1/3),a=Uu(s,o,t),i=Uu(s,o,t-1/3)}return{r:r*255,g:a*255,b:i*255}}function Xc(t,e,n){t=zt(t,255),e=zt(e,255),n=zt(n,255);var r=Math.max(t,e,n),a=Math.min(t,e,n),i=0,o=r,s=r-a,l=r===0?0:s/r;if(r===a)i=0;else{switch(r){case t:i=(e-n)/s+(e>16,g:(t&65280)>>8,b:t&255}}var Zc={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",goldenrod:"#daa520",gold:"#ffd700",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavenderblush:"#fff0f5",lavender:"#e6e6fa",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};function Qa(t){var e={r:0,g:0,b:0},n=1,r=null,a=null,i=null,o=!1,s=!1;return typeof t=="string"&&(t=mI(t)),typeof t=="object"&&(wr(t.r)&&wr(t.g)&&wr(t.b)?(e=lI(t.r,t.g,t.b),o=!0,s=String(t.r).substr(-1)==="%"?"prgb":"rgb"):wr(t.h)&&wr(t.s)&&wr(t.v)?(r=_s(t.s),a=_s(t.v),e=cI(t.h,r,a),o=!0,s="hsv"):wr(t.h)&&wr(t.s)&&wr(t.l)&&(r=_s(t.s),i=_s(t.l),e=uI(t.h,r,i),o=!0,s="hsl"),Object.prototype.hasOwnProperty.call(t,"a")&&(n=t.a)),n=Fw(n),{ok:o,format:t.format||s,r:Math.min(255,Math.max(e.r,0)),g:Math.min(255,Math.max(e.g,0)),b:Math.min(255,Math.max(e.b,0)),a:n}}var pI="[-\\+]?\\d+%?",hI="[-\\+]?\\d*\\.\\d+%?",Yr="(?:".concat(hI,")|(?:").concat(pI,")"),Ku="[\\s|\\(]+(".concat(Yr,")[,|\\s]+(").concat(Yr,")[,|\\s]+(").concat(Yr,")\\s*\\)?"),Gu="[\\s|\\(]+(".concat(Yr,")[,|\\s]+(").concat(Yr,")[,|\\s]+(").concat(Yr,")[,|\\s]+(").concat(Yr,")\\s*\\)?"),jn={CSS_UNIT:new RegExp(Yr),rgb:new RegExp("rgb"+Ku),rgba:new RegExp("rgba"+Gu),hsl:new RegExp("hsl"+Ku),hsla:new RegExp("hsla"+Gu),hsv:new RegExp("hsv"+Ku),hsva:new RegExp("hsva"+Gu),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/};function mI(t){if(t=t.trim().toLowerCase(),t.length===0)return!1;var e=!1;if(Zc[t])t=Zc[t],e=!0;else if(t==="transparent")return{r:0,g:0,b:0,a:0,format:"name"};var n=jn.rgb.exec(t);return n?{r:n[1],g:n[2],b:n[3]}:(n=jn.rgba.exec(t),n?{r:n[1],g:n[2],b:n[3],a:n[4]}:(n=jn.hsl.exec(t),n?{h:n[1],s:n[2],l:n[3]}:(n=jn.hsla.exec(t),n?{h:n[1],s:n[2],l:n[3],a:n[4]}:(n=jn.hsv.exec(t),n?{h:n[1],s:n[2],v:n[3]}:(n=jn.hsva.exec(t),n?{h:n[1],s:n[2],v:n[3],a:n[4]}:(n=jn.hex8.exec(t),n?{r:pn(n[1]),g:pn(n[2]),b:pn(n[3]),a:xh(n[4]),format:e?"name":"hex8"}:(n=jn.hex6.exec(t),n?{r:pn(n[1]),g:pn(n[2]),b:pn(n[3]),format:e?"name":"hex"}:(n=jn.hex4.exec(t),n?{r:pn(n[1]+n[1]),g:pn(n[2]+n[2]),b:pn(n[3]+n[3]),a:xh(n[4]+n[4]),format:e?"name":"hex8"}:(n=jn.hex3.exec(t),n?{r:pn(n[1]+n[1]),g:pn(n[2]+n[2]),b:pn(n[3]+n[3]),format:e?"name":"hex"}:!1)))))))))}function wr(t){return!!jn.CSS_UNIT.exec(String(t))}var qu=function(){function t(e,n){e===void 0&&(e=""),n===void 0&&(n={});var r;if(e instanceof t)return e;typeof e=="number"&&(e=vI(e)),this.originalInput=e;var a=Qa(e);this.originalInput=e,this.r=a.r,this.g=a.g,this.b=a.b,this.a=a.a,this.roundA=Math.round(100*this.a)/100,this.format=(r=n.format)!==null&&r!==void 0?r:a.format,this.gradientType=n.gradientType,this.r<1&&(this.r=Math.round(this.r)),this.g<1&&(this.g=Math.round(this.g)),this.b<1&&(this.b=Math.round(this.b)),this.isValid=a.ok}return t.prototype.isDark=function(){return this.getBrightness()<128},t.prototype.isLight=function(){return!this.isDark()},t.prototype.getBrightness=function(){var e=this.toRgb();return(e.r*299+e.g*587+e.b*114)/1e3},t.prototype.getLuminance=function(){var e=this.toRgb(),n,r,a,i=e.r/255,o=e.g/255,s=e.b/255;return i<=.03928?n=i/12.92:n=Math.pow((i+.055)/1.055,2.4),o<=.03928?r=o/12.92:r=Math.pow((o+.055)/1.055,2.4),s<=.03928?a=s/12.92:a=Math.pow((s+.055)/1.055,2.4),.2126*n+.7152*r+.0722*a},t.prototype.getAlpha=function(){return this.a},t.prototype.setAlpha=function(e){return this.a=Fw(e),this.roundA=Math.round(100*this.a)/100,this},t.prototype.isMonochrome=function(){var e=this.toHsl().s;return e===0},t.prototype.toHsv=function(){var e=Xc(this.r,this.g,this.b);return{h:e.h*360,s:e.s,v:e.v,a:this.a}},t.prototype.toHsvString=function(){var e=Xc(this.r,this.g,this.b),n=Math.round(e.h*360),r=Math.round(e.s*100),a=Math.round(e.v*100);return this.a===1?"hsv(".concat(n,", ").concat(r,"%, ").concat(a,"%)"):"hsva(".concat(n,", ").concat(r,"%, ").concat(a,"%, ").concat(this.roundA,")")},t.prototype.toHsl=function(){var e=Sh(this.r,this.g,this.b);return{h:e.h*360,s:e.s,l:e.l,a:this.a}},t.prototype.toHslString=function(){var e=Sh(this.r,this.g,this.b),n=Math.round(e.h*360),r=Math.round(e.s*100),a=Math.round(e.l*100);return this.a===1?"hsl(".concat(n,", ").concat(r,"%, ").concat(a,"%)"):"hsla(".concat(n,", ").concat(r,"%, ").concat(a,"%, ").concat(this.roundA,")")},t.prototype.toHex=function(e){return e===void 0&&(e=!1),Jc(this.r,this.g,this.b,e)},t.prototype.toHexString=function(e){return e===void 0&&(e=!1),"#"+this.toHex(e)},t.prototype.toHex8=function(e){return e===void 0&&(e=!1),fI(this.r,this.g,this.b,this.a,e)},t.prototype.toHex8String=function(e){return e===void 0&&(e=!1),"#"+this.toHex8(e)},t.prototype.toHexShortString=function(e){return e===void 0&&(e=!1),this.a===1?this.toHexString(e):this.toHex8String(e)},t.prototype.toRgb=function(){return{r:Math.round(this.r),g:Math.round(this.g),b:Math.round(this.b),a:this.a}},t.prototype.toRgbString=function(){var e=Math.round(this.r),n=Math.round(this.g),r=Math.round(this.b);return this.a===1?"rgb(".concat(e,", ").concat(n,", ").concat(r,")"):"rgba(".concat(e,", ").concat(n,", ").concat(r,", ").concat(this.roundA,")")},t.prototype.toPercentageRgb=function(){var e=function(n){return"".concat(Math.round(zt(n,255)*100),"%")};return{r:e(this.r),g:e(this.g),b:e(this.b),a:this.a}},t.prototype.toPercentageRgbString=function(){var e=function(n){return Math.round(zt(n,255)*100)};return this.a===1?"rgb(".concat(e(this.r),"%, ").concat(e(this.g),"%, ").concat(e(this.b),"%)"):"rgba(".concat(e(this.r),"%, ").concat(e(this.g),"%, ").concat(e(this.b),"%, ").concat(this.roundA,")")},t.prototype.toName=function(){if(this.a===0)return"transparent";if(this.a<1)return!1;for(var e="#"+Jc(this.r,this.g,this.b,!1),n=0,r=Object.entries(Zc);n=0,i=!n&&a&&(e.startsWith("hex")||e==="name");return i?e==="name"&&this.a===0?this.toName():this.toRgbString():(e==="rgb"&&(r=this.toRgbString()),e==="prgb"&&(r=this.toPercentageRgbString()),(e==="hex"||e==="hex6")&&(r=this.toHexString()),e==="hex3"&&(r=this.toHexString(!0)),e==="hex4"&&(r=this.toHex8String(!0)),e==="hex8"&&(r=this.toHex8String()),e==="name"&&(r=this.toName()),e==="hsl"&&(r=this.toHslString()),e==="hsv"&&(r=this.toHsvString()),r||this.toHexString())},t.prototype.toNumber=function(){return(Math.round(this.r)<<16)+(Math.round(this.g)<<8)+Math.round(this.b)},t.prototype.clone=function(){return new t(this.toString())},t.prototype.lighten=function(e){e===void 0&&(e=10);var n=this.toHsl();return n.l+=e/100,n.l=ws(n.l),new t(n)},t.prototype.brighten=function(e){e===void 0&&(e=10);var n=this.toRgb();return n.r=Math.max(0,Math.min(255,n.r-Math.round(255*-(e/100)))),n.g=Math.max(0,Math.min(255,n.g-Math.round(255*-(e/100)))),n.b=Math.max(0,Math.min(255,n.b-Math.round(255*-(e/100)))),new t(n)},t.prototype.darken=function(e){e===void 0&&(e=10);var n=this.toHsl();return n.l-=e/100,n.l=ws(n.l),new t(n)},t.prototype.tint=function(e){return e===void 0&&(e=10),this.mix("white",e)},t.prototype.shade=function(e){return e===void 0&&(e=10),this.mix("black",e)},t.prototype.desaturate=function(e){e===void 0&&(e=10);var n=this.toHsl();return n.s-=e/100,n.s=ws(n.s),new t(n)},t.prototype.saturate=function(e){e===void 0&&(e=10);var n=this.toHsl();return n.s+=e/100,n.s=ws(n.s),new t(n)},t.prototype.greyscale=function(){return this.desaturate(100)},t.prototype.spin=function(e){var n=this.toHsl(),r=(n.h+e)%360;return n.h=r<0?360+r:r,new t(n)},t.prototype.mix=function(e,n){n===void 0&&(n=50);var r=this.toRgb(),a=new t(e).toRgb(),i=n/100,o={r:(a.r-r.r)*i+r.r,g:(a.g-r.g)*i+r.g,b:(a.b-r.b)*i+r.b,a:(a.a-r.a)*i+r.a};return new t(o)},t.prototype.analogous=function(e,n){e===void 0&&(e=6),n===void 0&&(n=30);var r=this.toHsl(),a=360/n,i=[this];for(r.h=(r.h-(a*e>>1)+720)%360;--e;)r.h=(r.h+a)%360,i.push(new t(r));return i},t.prototype.complement=function(){var e=this.toHsl();return e.h=(e.h+180)%360,new t(e)},t.prototype.monochromatic=function(e){e===void 0&&(e=6);for(var n=this.toHsv(),r=n.h,a=n.s,i=n.v,o=[],s=1/e;e--;)o.push(new t({h:r,s:a,v:i})),i=(i+s)%1;return o},t.prototype.splitcomplement=function(){var e=this.toHsl(),n=e.h;return[this,new t({h:(n+72)%360,s:e.s,l:e.l}),new t({h:(n+216)%360,s:e.s,l:e.l})]},t.prototype.onBackground=function(e){var n=this.toRgb(),r=new t(e).toRgb(),a=n.a+r.a*(1-n.a);return new t({r:(n.r*n.a+r.r*r.a*(1-n.a))/a,g:(n.g*n.a+r.g*r.a*(1-n.a))/a,b:(n.b*n.a+r.b*r.a*(1-n.a))/a,a})},t.prototype.triad=function(){return this.polyad(3)},t.prototype.tetrad=function(){return this.polyad(4)},t.prototype.polyad=function(e){for(var n=this.toHsl(),r=n.h,a=[this],i=360/e,o=1;o=60&&Math.round(t.h)<=240?r=n?Math.round(t.h)-Cs*e:Math.round(t.h)+Cs*e:r=n?Math.round(t.h)+Cs*e:Math.round(t.h)-Cs*e,r<0?r+=360:r>=360&&(r-=360),r}function Th(t,e,n){if(t.h===0&&t.s===0)return t.s;var r;return n?r=t.s-Eh*e:e===jw?r=t.s+Eh:r=t.s+gI*e,r>1&&(r=1),n&&e===Bw&&r>.1&&(r=.1),r<.06&&(r=.06),Number(r.toFixed(2))}function Ih(t,e,n){var r;return n?r=t.v+yI*e:r=t.v-bI*e,r>1&&(r=1),Number(r.toFixed(2))}function Ao(t){for(var e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},n=[],r=Qa(t),a=Bw;a>0;a-=1){var i=Oh(r),o=Ss(Qa({h:Ph(i,a,!0),s:Th(i,a,!0),v:Ih(i,a,!0)}));n.push(o)}n.push(Ss(r));for(var s=1;s<=jw;s+=1){var l=Oh(r),u=Ss(Qa({h:Ph(l,s),s:Th(l,s),v:Ih(l,s)}));n.push(u)}return e.theme==="dark"?wI.map(function(c){var d=c.index,v=c.opacity,h=Ss(_I(Qa(e.backgroundColor||"#141414"),Qa(n[d]),v*100));return h}):n}var Yu={red:"#F5222D",volcano:"#FA541C",orange:"#FA8C16",gold:"#FAAD14",yellow:"#FADB14",lime:"#A0D911",green:"#52C41A",cyan:"#13C2C2",blue:"#1890FF",geekblue:"#2F54EB",purple:"#722ED1",magenta:"#EB2F96",grey:"#666666"},Xu={},Ju={};Object.keys(Yu).forEach(function(t){Xu[t]=Ao(Yu[t]),Xu[t].primary=Xu[t][5],Ju[t]=Ao(Yu[t],{theme:"dark",backgroundColor:"#141414"}),Ju[t].primary=Ju[t][5]});var Ah=[],Ui=[],CI="insert-css: You need to provide a CSS string. Usage: insertCss(cssString[, options]).";function SI(){var t=document.createElement("style");return t.setAttribute("type","text/css"),t}function xI(t,e){if(e=e||{},t===void 0)throw new Error(CI);var n=e.prepend===!0?"prepend":"append",r=e.container!==void 0?e.container:document.querySelector("head"),a=Ah.indexOf(r);a===-1&&(a=Ah.push(r)-1,Ui[a]={});var i;return Ui[a]!==void 0&&Ui[a][n]!==void 0?i=Ui[a][n]:(i=Ui[a][n]=SI(),n==="prepend"?r.insertBefore(i,r.childNodes[0]):r.appendChild(i)),t.charCodeAt(0)===65279&&(t=t.substr(1,t.length)),i.styleSheet?i.styleSheet.cssText+=t:i.textContent+=t,i}function Mh(t){for(var e=1;e * { + line-height: 1; +} + +.anticon svg { + display: inline-block; +} + +.anticon::before { + display: none; +} + +.anticon .anticon-icon { + display: block; +} + +.anticon[tabindex] { + cursor: pointer; +} + +.anticon-spin::before, +.anticon-spin { + display: inline-block; + -webkit-animation: loadingCircle 1s infinite linear; + animation: loadingCircle 1s infinite linear; +} + +@-webkit-keyframes loadingCircle { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes loadingCircle { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +`,Nh=!1,PI=function(){var e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:OI;ze(function(){Nh||(typeof window<"u"&&window.document&&window.document.documentElement&&xI(e,{prepend:!0}),Nh=!0)})},TI=["icon","primaryColor","secondaryColor"];function II(t,e){if(t==null)return{};var n=AI(t,e),r,a;if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(a=0;a=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(n[r]=t[r])}return n}function AI(t,e){if(t==null)return{};var n={},r=Object.keys(t),a,i;for(i=0;i=0)&&(n[a]=t[a]);return n}function Ys(t){for(var e=1;et.length)&&(e=t.length);for(var n=0,r=new Array(e);nt.length)&&(e=t.length);for(var n=0,r=new Array(e);n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(n[r]=t[r])}return n}function GI(t,e){if(t==null)return{};var n={},r=Object.keys(t),a,i;for(i=0;i=0)&&(n[a]=t[a]);return n}Hw("#1890ff");var $i=function(e,n){var r,a=Lh({},e,n.attrs),i=a.class,o=a.icon,s=a.spin,l=a.rotate,u=a.tabindex,c=a.twoToneColor,d=a.onClick,v=KI(a,jI),h=(r={anticon:!0},ef(r,"anticon-".concat(o.name),!!o.name),ef(r,i,i),r),f=s===""||s||o.name==="loading"?"anticon-spin":"",p=u;p===void 0&&d&&(p=-1,v.tabindex=p);var g=l?{msTransform:"rotate(".concat(l,"deg)"),transform:"rotate(".concat(l,"deg)")}:void 0,m=Ww(c),y=zI(m,2),b=y[0],w=y[1];return x("span",Lh({role:"img","aria-label":o.name},v,{onClick:d,class:h}),[x(Nd,{class:f,icon:o,primaryColor:b,secondaryColor:w,style:g},null)])};$i.props={spin:Boolean,rotate:Number,icon:Object,twoToneColor:String};$i.displayName="AntdIcon";$i.inheritAttrs=!1;$i.getTwoToneColor=BI;$i.setTwoToneColor=Hw;const st=$i;function Dh(t){for(var e=1;e=0;--L){var K=this.tryEntries[L],X=K.completion;if(K.tryLoc==="root")return k("end");if(K.tryLoc<=this.prev){var ee=i.call(K,"catchLoc"),J=i.call(K,"finallyLoc");if(ee&&J){if(this.prev=0;--k){var L=this.tryEntries[k];if(L.tryLoc<=this.prev&&i.call(L,"finallyLoc")&&this.prev=0;--P){var k=this.tryEntries[P];if(k.finallyLoc===T)return this.complete(k.completion,k.afterLoc),R(k),f}},catch:function(T){for(var P=this.tryEntries.length-1;P>=0;--P){var k=this.tryEntries[P];if(k.tryLoc===T){var L=k.completion;if(L.type==="throw"){var K=L.arg;R(k)}return K}}throw new Error("illegal catch attempt")},delegateYield:function(T,P,k){return this.delegate={iterator:B(T),resultName:P,nextLoc:k},this.method==="next"&&(this.arg=void 0),f}},r}t.exports=n,t.exports.__esModule=!0,t.exports.default=t.exports})(t1);var bA=t1.exports,Xs=bA(),wA=Xs;try{regeneratorRuntime=Xs}catch{typeof globalThis=="object"?globalThis.regeneratorRuntime=Xs:Function("r","regeneratorRuntime = r")(Xs)}const Hh=Bd(wA);var _A={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M699 353h-46.9c-10.2 0-19.9 4.9-25.9 13.3L469 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H325c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8a31.8 31.8 0 0051.7 0l210.6-292c3.9-5.3.1-12.7-6.4-12.7z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"check-circle",theme:"outlined"};const CA=_A;function Vh(t){for(var e=1;e1&&arguments[1]!==void 0?arguments[1]:a1,n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:i1,r;switch(t){case"topLeft":r={left:"0px",top:e,bottom:"auto"};break;case"topRight":r={right:"0px",top:e,bottom:"auto"};break;case"bottomLeft":r={left:"0px",top:"auto",bottom:n};break;default:r={right:"0px",top:"auto",bottom:n};break}return r}function FA(t,e){var n=t.prefixCls,r=t.placement,a=r===void 0?o1:r,i=t.getContainer,o=i===void 0?s1:i,s=t.top,l=t.bottom,u=t.closeIcon,c=u===void 0?l1:u,d=t.appContext,v=ZA(),h=v.getPrefixCls,f=h("notification",n||tf),p="".concat(f,"-").concat(a,"-").concat(nf),g=Sa[p];if(g){Promise.resolve(g).then(function(y){e(y)});return}var m=be("".concat(f,"-").concat(a),te({},"".concat(f,"-rtl"),nf===!0));Dw.newInstance({name:"notification",prefixCls:n||tf,class:m,style:DA(a,s,l),appContext:d,getContainer:o,closeIcon:function(b){var w=b.prefixCls,_=x("span",{class:"".concat(w,"-close-x")},[to(c,{},x(Ba,{class:"".concat(w,"-close-icon")},null))]);return _},maxCount:u1,hasTransitionName:!0},function(y){Sa[p]=y,e(y)})}var BA={success:zd,info:Hd,error:Mo,warning:iu};function jA(t){var e=t.icon,n=t.type,r=t.description,a=t.message,i=t.btn,o=t.duration===void 0?r1:t.duration;FA(t,function(s){s.notice({content:function(u){var c=u.prefixCls,d="".concat(c,"-notice"),v=null;if(e)v=function(){return x("span",{class:"".concat(d,"-icon")},[to(e)])};else if(n){var h=BA[n];v=function(){return x(h,{class:"".concat(d,"-icon ").concat(d,"-icon-").concat(n)},null)}}return x("div",{class:v?"".concat(d,"-with-icon"):""},[v&&v(),x("div",{class:"".concat(d,"-message")},[!r&&v?x("span",{class:"".concat(d,"-message-single-line-auto-margin")},null):null,to(a)]),x("div",{class:"".concat(d,"-description")},[to(r)]),i?x("span",{class:"".concat(d,"-btn")},[to(i)]):null])},duration:o,closable:!0,onClose:t.onClose,onClick:t.onClick,key:t.key,style:t.style||{},class:t.class})})}var ko={open:jA,close:function(e){Object.keys(Sa).forEach(function(n){return Promise.resolve(Sa[n]).then(function(r){r.removeNotice(e)})})},config:LA,destroy:function(){Object.keys(Sa).forEach(function(e){Promise.resolve(Sa[e]).then(function(n){n.destroy()}),delete Sa[e]})}},zA=["success","info","warning","error"];zA.forEach(function(t){ko[t]=function(e){return ko.open(M(M({},e),{},{type:t}))}});ko.warn=ko.warning;const WA=ko;function Qo(){return!!(typeof window<"u"&&window.document&&window.document.createElement)}var HA="vc-util-key";function c1(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},e=t.mark;return e?e.startsWith("data-")?e:"data-".concat(e):HA}function Gd(t){if(t.attachTo)return t.attachTo;var e=document.querySelector("head");return e||document.body}function Yh(t){var e,n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};if(!Qo())return null;var r=document.createElement("style");if((e=n.csp)!==null&&e!==void 0&&e.nonce){var a;r.nonce=(a=n.csp)===null||a===void 0?void 0:a.nonce}r.innerHTML=t;var i=Gd(n),o=i.firstChild;return n.prepend&&i.prepend?i.prepend(r):n.prepend&&o?i.insertBefore(r,o):i.appendChild(r),r}var rf=new Map;function VA(t){var e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},n=Gd(e);return Array.from(rf.get(n).children).find(function(r){return r.tagName==="STYLE"&&r.getAttribute(c1(e))===t})}function UA(t,e){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},r=Gd(n);if(!rf.has(r)){var a=Yh("",n),i=a.parentNode;rf.set(r,i),i.removeChild(a)}var o=VA(e,n);if(o){var s,l;if((s=n.csp)!==null&&s!==void 0&&s.nonce&&o.nonce!==((l=n.csp)===null||l===void 0?void 0:l.nonce)){var u;o.nonce=(u=n.csp)===null||u===void 0?void 0:u.nonce}return o.innerHTML!==t&&(o.innerHTML=t),o}var c=Yh(t,n);return c.setAttribute(c1(n),e),c}const Nn=function(t,e,n){Lw(t,"[ant-design-vue: ".concat(e,"] ").concat(n))};var KA="-ant-".concat(Date.now(),"-").concat(Math.random());function GA(t,e){var n={},r=function(c,d){var v=c.clone();return v=(d==null?void 0:d(v))||v,v.toRgbString()},a=function(c,d){var v=new qu(c),h=Ao(v.toRgbString());n["".concat(d,"-color")]=r(v),n["".concat(d,"-color-disabled")]=h[1],n["".concat(d,"-color-hover")]=h[4],n["".concat(d,"-color-active")]=h[6],n["".concat(d,"-color-outline")]=v.clone().setAlpha(.2).toRgbString(),n["".concat(d,"-color-deprecated-bg")]=h[1],n["".concat(d,"-color-deprecated-border")]=h[3]};if(e.primaryColor){a(e.primaryColor,"primary");var i=new qu(e.primaryColor),o=Ao(i.toRgbString());o.forEach(function(u,c){n["primary-".concat(c+1)]=u}),n["primary-color-deprecated-l-35"]=r(i,function(u){return u.lighten(35)}),n["primary-color-deprecated-l-20"]=r(i,function(u){return u.lighten(20)}),n["primary-color-deprecated-t-20"]=r(i,function(u){return u.tint(20)}),n["primary-color-deprecated-t-50"]=r(i,function(u){return u.tint(50)}),n["primary-color-deprecated-f-12"]=r(i,function(u){return u.setAlpha(u.getAlpha()*.12)});var s=new qu(o[0]);n["primary-color-active-deprecated-f-30"]=r(s,function(u){return u.setAlpha(u.getAlpha()*.3)}),n["primary-color-active-deprecated-d-02"]=r(s,function(u){return u.darken(2)})}e.successColor&&a(e.successColor,"success"),e.warningColor&&a(e.warningColor,"warning"),e.errorColor&&a(e.errorColor,"error"),e.infoColor&&a(e.infoColor,"info");var l=Object.keys(n).map(function(u){return"--".concat(t,"-").concat(u,": ").concat(n[u],";")});Qo()?UA(` + :root { + `.concat(l.join(` +`),` + } + `),"".concat(KA,"-dynamic-theme")):Nn(!1,"ConfigProvider","SSR do not support dynamic theme with css variables.")}var f1=Symbol("GlobalFormContextKey"),qA=function(e){pt(f1,e)},yU=function(){return Je(f1,{validateMessages:G(function(){})})},YA=function(){return{getTargetContainer:{type:Function},getPopupContainer:{type:Function},prefixCls:String,getPrefixCls:{type:Function},renderEmpty:{type:Function},transformCellText:{type:Function},csp:{type:Object,default:void 0},input:{type:Object},autoInsertSpaceInButton:{type:Boolean,default:void 0},locale:{type:Object,default:void 0},pageHeader:{type:Object},componentSize:{type:String},direction:{type:String},space:{type:Object},virtual:{type:Boolean,default:void 0},dropdownMatchSelectWidth:{type:[Number,Boolean],default:!0},form:{type:Object,default:void 0},notUpdateGlobalConfig:Boolean}},XA="ant";function li(){return sn.prefixCls||XA}var af=nt({}),d1=nt({}),sn=nt({});dt(function(){Lt(sn,af,d1),sn.prefixCls=li(),sn.getPrefixCls=function(t,e){return e||(t?"".concat(sn.prefixCls,"-").concat(t):sn.prefixCls)},sn.getRootPrefixCls=function(t,e){return t||(sn.prefixCls?sn.prefixCls:e&&e.includes("-")?e.replace(/^(.*)-[^-]*$/,"$1"):li())}});var Zu,JA=function(e){Zu&&Zu(),Zu=dt(function(){Lt(d1,nt(e)),Lt(sn,nt(e))}),e.theme&&GA(li(),e.theme)},ZA=function(){return{getPrefixCls:function(n,r){return r||(n?"".concat(li(),"-").concat(n):li())},getRootPrefixCls:function(n,r){return n||(sn.prefixCls?sn.prefixCls:r&&r.includes("-")?r.replace(/^(.*)-[^-]*$/,"$1"):li())}}},ui=de({compatConfig:{MODE:3},name:"AConfigProvider",inheritAttrs:!1,props:YA(),setup:function(e,n){var r=n.slots,a=function(d,v){var h=e.prefixCls,f=h===void 0?"ant":h;return v||(d?"".concat(f,"-").concat(d):f)},i=function(d){var v=e.renderEmpty||r.renderEmpty||$w;return v(d)},o=function(d,v){var h=e.prefixCls;if(v)return v;var f=h||a("");return d?"".concat(f,"-").concat(d):f},s=nt(M(M({},e),{},{getPrefixCls:o,renderEmpty:i}));Object.keys(e).forEach(function(c){ve(function(){return e[c]},function(){s[c]=e[c]})}),e.notUpdateGlobalConfig||(Lt(af,s),ve(s,function(){Lt(af,s)}));var l=G(function(){var c={};if(e.locale){var d,v;c=((d=e.locale.Form)===null||d===void 0?void 0:d.defaultValidateMessages)||((v=To.Form)===null||v===void 0?void 0:v.defaultValidateMessages)||{}}return e.form&&e.form.validateMessages&&(c=M(M({},c),e.form.validateMessages)),c});qA({validateMessages:l}),pt("configProvider",s);var u=function(d){var v;return x(QT,{locale:e.locale||d,ANT_MARK__:qc},{default:function(){return[(v=r.default)===null||v===void 0?void 0:v.call(r)]}})};return dt(function(){e.direction&&(Ar.config({rtl:e.direction==="rtl"}),WA.config({rtl:e.direction==="rtl"}))}),function(){return x(_w,{children:function(d,v,h){return u(h)}},null)}}}),QA=nt({getPrefixCls:function(e,n){return n||(e?"ant-".concat(e):"ant")},renderEmpty:$w,direction:"ltr"});ui.config=JA;ui.install=function(t){t.component(ui.name,ui)};const Qe=function(t,e){var n=Je("configProvider",QA),r=G(function(){return n.getPrefixCls(t,e.prefixCls)}),a=G(function(){var y;return(y=e.direction)!==null&&y!==void 0?y:n.direction}),i=G(function(){return n.getPrefixCls()}),o=G(function(){return n.autoInsertSpaceInButton}),s=G(function(){return n.renderEmpty}),l=G(function(){return n.space}),u=G(function(){return n.pageHeader}),c=G(function(){return n.form}),d=G(function(){return e.getTargetContainer||n.getTargetContainer}),v=G(function(){return e.getPopupContainer||n.getPopupContainer}),h=G(function(){var y;return(y=e.dropdownMatchSelectWidth)!==null&&y!==void 0?y:n.dropdownMatchSelectWidth}),f=G(function(){return(e.virtual===void 0?n.virtual!==!1:e.virtual!==!1)&&h.value!==!1}),p=G(function(){return e.size||n.componentSize}),g=G(function(){var y;return e.autocomplete||((y=n.input)===null||y===void 0?void 0:y.autocomplete)}),m=G(function(){return n.csp});return{configProvider:n,prefixCls:r,direction:a,size:p,getTargetContainer:d,getPopupContainer:v,space:l,pageHeader:u,form:c,autoInsertSpaceInButton:o,renderEmpty:s,virtual:f,dropdownMatchSelectWidth:h,rootPrefixCls:i,getPrefixCls:n.getPrefixCls,autocomplete:g,csp:m}};function Pt(t,e){for(var n=Lt({},t),r=0;r1&&arguments[1]!==void 0?arguments[1]:{},n=e.fieldNames,r=e.childrenAsData,a=[],i=v1(n,!1),o=i.label,s=i.value,l=i.options;function u(c,d){c.forEach(function(v){var h=v[o];if(d||!(l in v)){var f=v[s];a.push({key:Xh(v,a.length),groupOption:d,data:v,label:h,value:f})}else{var p=h;p===void 0&&r&&(p=v.label),a.push({key:Xh(v,a.length),group:!0,data:v,label:p}),u(v[l],!0)}})}return u(t,!1),a}function of(t){var e=M({},t);return"props"in e||Object.defineProperty(e,"props",{get:function(){return e}}),e}function nM(t,e){if(!e||!e.length)return null;var n=!1;function r(i,o){var s=eM(o),l=s[0],u=s.slice(1);if(!l)return[i];var c=i.split(l);return n=n||c.length>1,c.reduce(function(d,v){return[].concat(Ge(d),Ge(r(v,u)))},[]).filter(function(d){return d})}var a=r(t,e);return n?a:null}function xa(t,e){return t?t.contains(e):!1}var p1=["moz","ms","webkit"];function rM(){var t=0;return function(e){var n=new Date().getTime(),r=Math.max(0,16-(n-t)),a=window.setTimeout(function(){e(n+r)},r);return t=n+r,a}}function aM(){if(typeof window>"u")return function(){};if(window.requestAnimationFrame)return window.requestAnimationFrame.bind(window);var t=p1.filter(function(e){return"".concat(e,"RequestAnimationFrame")in window})[0];return t?window["".concat(t,"RequestAnimationFrame")]:rM()}function iM(t){if(typeof window>"u")return null;if(window.cancelAnimationFrame)return window.cancelAnimationFrame(t);var e=p1.filter(function(n){return"".concat(n,"CancelAnimationFrame")in window||"".concat(n,"CancelRequestAnimationFrame")in window})[0];return e?(window["".concat(e,"CancelAnimationFrame")]||window["".concat(e,"CancelRequestAnimationFrame")]).call(this,t):clearTimeout(t)}var Jh=aM(),oM=function(e){return iM(e.id)},sM=function(e){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,r=Date.now();function a(){Date.now()-r>=n?e.call():i.id=Jh(a)}var i={id:Jh(a)};return i},qd={visible:Boolean,prefixCls:String,zIndex:Number,destroyPopupOnHide:Boolean,forceRender:Boolean,animation:[String,Object],transitionName:String,stretch:{type:String},align:{type:Object},point:{type:Object},getRootDomNode:{type:Function},getClassNameFromAlign:{type:Function},onMouseenter:{type:Function},onMouseleave:{type:Function},onMousedown:{type:Function},onTouchstart:{type:Function}},lM=M(M({},qd),{},{mobile:{type:Object}}),uM=M(M({},qd),{},{mask:Boolean,mobile:{type:Object},maskAnimation:String,maskTransitionName:String});function h1(t){var e=t.prefixCls,n=t.animation,r=t.transitionName;return n?{name:"".concat(e,"-").concat(n)}:r?{name:r}:{}}function m1(t){var e=t.prefixCls,n=t.visible,r=t.zIndex,a=t.mask,i=t.maskAnimation,o=t.maskTransitionName;if(!a)return null;var s={};return(o||i)&&(s=h1({prefixCls:e,transitionName:o,animation:i})),x(Zn,M({appear:!0},s),{default:function(){return[Jn(x("div",{style:{zIndex:r},class:"".concat(e,"-mask")},null),[[rO("if"),n]])]}})}m1.displayName="Mask";const cM=de({compatConfig:{MODE:3},name:"MobilePopupInner",inheritAttrs:!1,props:lM,emits:["mouseenter","mouseleave","mousedown","touchstart","align"],setup:function(e,n){var r=n.expose,a=n.slots,i=z();return r({forceAlign:function(){},getElement:function(){return i.value}}),function(){var o,s=e.zIndex,l=e.visible,u=e.prefixCls,c=e.mobile,d=c===void 0?{}:c,v=d.popupClassName,h=d.popupStyle,f=d.popupMotion,p=f===void 0?{}:f,g=d.popupRender,m=M({zIndex:s},h),y=bn((o=a.default)===null||o===void 0?void 0:o.call(a));y.length>1&&(y=x("div",{class:"".concat(u,"-content")},[y])),g&&(y=g(y));var b=be(u,v);return x(Zn,M({ref:i},p),{default:function(){return[l?x("div",{class:b,style:m},[y]):null]}})}}});var Zh=["measure","align",null,"motion"];const fM=function(t,e){var n=z(null),r=z(),a=z(!1);function i(l){a.value||(n.value=l)}function o(){Fe.cancel(r.value)}function s(l){o(),r.value=Fe(function(){var u=n.value;switch(n.value){case"align":u="motion";break;case"motion":u="stable";break}i(u),l==null||l()})}return ve(t,function(){i("measure")},{immediate:!0,flush:"post"}),De(function(){ve(n,function(){switch(n.value){case"measure":e();break}n.value&&(r.value=Fe(hA(Hh.mark(function l(){var u,c;return Hh.wrap(function(v){for(;;)switch(v.prev=v.next){case 0:u=Zh.indexOf(n.value),c=Zh[u+1],c&&u!==-1&&i(c);case 3:case"end":return v.stop()}},l)}))))},{immediate:!0,flush:"post"})}),Ze(function(){a.value=!0,o()}),[n,s]},dM=function(t){var e=z({width:0,height:0});function n(a){e.value={width:a.offsetWidth,height:a.offsetHeight}}var r=G(function(){var a={};if(t.value){var i=e.value,o=i.width,s=i.height;t.value.indexOf("height")!==-1&&s?a.height="".concat(s,"px"):t.value.indexOf("minHeight")!==-1&&s&&(a.minHeight="".concat(s,"px")),t.value.indexOf("width")!==-1&&o?a.width="".concat(o,"px"):t.value.indexOf("minWidth")!==-1&&o&&(a.minWidth="".concat(o,"px"))}return a});return[r,n]};function Qh(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable})),n.push.apply(n,r)}return n}function em(t){for(var e=1;e=0&&n.left>=0&&n.bottom>n.top&&n.right>n.left?n:null}function $M(t,e,n,r){var a=Re.clone(t),i={width:e.width,height:e.height};return r.adjustX&&a.left=n.left&&a.left+i.width>n.right&&(i.width-=a.left+i.width-n.right),r.adjustX&&a.left+i.width>n.right&&(a.left=Math.max(n.right-i.width,n.left)),r.adjustY&&a.top=n.top&&a.top+i.height>n.bottom&&(i.height-=a.top+i.height-n.bottom),r.adjustY&&a.top+i.height>n.bottom&&(a.top=Math.max(n.bottom-i.height,n.top)),Re.mix(a,i)}function Zd(t){var e,n,r;if(!Re.isWindow(t)&&t.nodeType!==9)e=Re.offset(t),n=Re.outerWidth(t),r=Re.outerHeight(t);else{var a=Re.getWindow(t);e={left:Re.getWindowScrollLeft(a),top:Re.getWindowScrollTop(a)},n=Re.viewportWidth(a),r=Re.viewportHeight(a)}return e.width=n,e.height=r,e}function lm(t,e){var n=e.charAt(0),r=e.charAt(1),a=t.width,i=t.height,o=t.left,s=t.top;return n==="c"?s+=i/2:n==="b"&&(s+=i),r==="c"?o+=a/2:r==="r"&&(o+=a),{left:o,top:s}}function Es(t,e,n,r,a){var i=lm(e,n[1]),o=lm(t,n[0]),s=[o.left-i.left,o.top-i.top];return{left:Math.round(t.left-s[0]+r[0]-a[0]),top:Math.round(t.top-s[1]+r[1]-a[1])}}function um(t,e,n){return t.leftn.right}function cm(t,e,n){return t.topn.bottom}function LM(t,e,n){return t.left>n.right||t.left+e.widthn.bottom||t.top+e.height=n.right||r.top>=n.bottom}function Qd(t,e,n){var r=n.target||e,a=Zd(r),i=!FM(r,n.overflow&&n.overflow.alwaysByViewport);return x1(t,a,n,i)}Qd.__getOffsetParent=cf;Qd.__getVisibleRectForElement=Jd;function BM(t,e,n){var r,a,i=Re.getDocument(t),o=i.defaultView||i.parentWindow,s=Re.getWindowScrollLeft(o),l=Re.getWindowScrollTop(o),u=Re.viewportWidth(o),c=Re.viewportHeight(o);"pageX"in e?r=e.pageX:r=s+e.clientX,"pageY"in e?a=e.pageY:a=l+e.clientY;var d={left:r,top:a,width:0,height:0},v=r>=0&&r<=s+u&&a>=0&&a<=l+c,h=[n.points[0],"cc"];return x1(t,d,em(em({},n),{},{points:h}),v)}function yt(t){var e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!0,r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!1,a=t;if(Array.isArray(t)&&(a=Mi(t)[0]),!a)return null;var i=Gn(a,e,r);return i.props=n?M(M({},i.props),e):i.props,ru(He(i.props.class)!=="object","class must be string"),i}const jM=function(t){if(!t)return!1;if(t.offsetParent)return!0;if(t.getBBox){var e=t.getBBox();if(e.width||e.height)return!0}if(t.getBoundingClientRect){var n=t.getBoundingClientRect();if(n.width||n.height)return!0}return!1};function zM(t,e){return t===e?!0:!t||!e?!1:"pageX"in e&&"pageY"in e?t.pageX===e.pageX&&t.pageY===e.pageY:"clientX"in e&&"clientY"in e?t.clientX===e.clientX&&t.clientY===e.clientY:!1}function WM(t,e){t!==document.activeElement&&xa(e,t)&&typeof t.focus=="function"&&t.focus()}function vm(t,e){var n=null,r=null;function a(o){var s=Oe(o,1),l=s[0].target;if(document.documentElement.contains(l)){var u=l.getBoundingClientRect(),c=u.width,d=u.height,v=Math.floor(c),h=Math.floor(d);(n!==v||r!==h)&&Promise.resolve().then(function(){e({width:v,height:h})}),n=v,r=h}}var i=new aw(a);return t&&i.observe(t),function(){i.disconnect()}}const HM=function(t,e){var n=!1,r=null;function a(){clearTimeout(r)}function i(o){if(!n||o===!0){if(t()===!1)return;n=!0,a(),r=setTimeout(function(){n=!1},e.value)}else a(),r=setTimeout(function(){n=!1,i()},e.value)}return[i,function(){n=!1,a()}]};function VM(){this.__data__=[],this.size=0}function ev(t,e){return t===e||t!==t&&e!==e}function su(t,e){for(var n=t.length;n--;)if(ev(t[n][0],e))return n;return-1}var UM=Array.prototype,KM=UM.splice;function GM(t){var e=this.__data__,n=su(e,t);if(n<0)return!1;var r=e.length-1;return n==r?e.pop():KM.call(e,n,1),--this.size,!0}function qM(t){var e=this.__data__,n=su(e,t);return n<0?void 0:e[n][1]}function YM(t){return su(this.__data__,t)>-1}function XM(t,e){var n=this.__data__,r=su(n,t);return r<0?(++this.size,n.push([t,e])):n[r][1]=e,this}function Lr(t){var e=-1,n=t==null?0:t.length;for(this.clear();++es))return!1;var u=i.get(t),c=i.get(e);if(u&&c)return u==e&&c==t;var d=-1,v=!0,h=n&Vk?new Lo:void 0;for(i.set(t,e),i.set(e,t);++d-1&&t%1==0&&t-1&&t%1==0&&t<=SN}var xN="[object Arguments]",EN="[object Array]",ON="[object Boolean]",PN="[object Date]",TN="[object Error]",IN="[object Function]",AN="[object Map]",MN="[object Number]",kN="[object Object]",NN="[object RegExp]",RN="[object Set]",$N="[object String]",LN="[object WeakMap]",DN="[object ArrayBuffer]",FN="[object DataView]",BN="[object Float32Array]",jN="[object Float64Array]",zN="[object Int8Array]",WN="[object Int16Array]",HN="[object Int32Array]",VN="[object Uint8Array]",UN="[object Uint8ClampedArray]",KN="[object Uint16Array]",GN="[object Uint32Array]",lt={};lt[BN]=lt[jN]=lt[zN]=lt[WN]=lt[HN]=lt[VN]=lt[UN]=lt[KN]=lt[GN]=!0;lt[xN]=lt[EN]=lt[DN]=lt[ON]=lt[FN]=lt[PN]=lt[TN]=lt[IN]=lt[AN]=lt[MN]=lt[kN]=lt[NN]=lt[RN]=lt[$N]=lt[LN]=!1;function qN(t){return hr(t)&&ov(t.length)&&!!lt[sa(t)]}function sv(t){return function(e){return t(e)}}var k1=typeof exports=="object"&&exports&&!exports.nodeType&&exports,fo=k1&&typeof module=="object"&&module&&!module.nodeType&&module,YN=fo&&fo.exports===k1,ic=YN&&lw.process,XN=function(){try{var t=fo&&fo.require&&fo.require("util").types;return t||ic&&ic.binding&&ic.binding("util")}catch{}}();const wi=XN;var wm=wi&&wi.isTypedArray,JN=wm?sv(wm):qN;const N1=JN;var ZN=Object.prototype,QN=ZN.hasOwnProperty;function R1(t,e){var n=Xn(t),r=!n&&av(t),a=!n&&!r&&_l(t),i=!n&&!r&&!a&&N1(t),o=n||r||a||i,s=o?dN(t.length,String):[],l=s.length;for(var u in t)(e||QN.call(t,u))&&!(o&&(u=="length"||a&&(u=="offset"||u=="parent")||i&&(u=="buffer"||u=="byteLength"||u=="byteOffset")||iv(u,l)))&&s.push(u);return s}var eR=Object.prototype;function lv(t){var e=t&&t.constructor,n=typeof e=="function"&&e.prototype||eR;return t===n}var tR=cw(Object.keys,Object);const nR=tR;var rR=Object.prototype,aR=rR.hasOwnProperty;function iR(t){if(!lv(t))return nR(t);var e=[];for(var n in Object(t))aR.call(t,n)&&n!="constructor"&&e.push(n);return e}function $1(t){return t!=null&&ov(t.length)&&!E1(t)}function uu(t){return $1(t)?R1(t):iR(t)}function ff(t){return T1(t,uu,rv)}var oR=1,sR=Object.prototype,lR=sR.hasOwnProperty;function uR(t,e,n,r,a,i){var o=n&oR,s=ff(t),l=s.length,u=ff(e),c=u.length;if(l!=c&&!o)return!1;for(var d=l;d--;){var v=s[d];if(!(o?v in e:lR.call(e,v)))return!1}var h=i.get(t),f=i.get(e);if(h&&f)return h==e&&f==t;var p=!0;i.set(t,e),i.set(e,t);for(var g=o;++d1&&(J=x("div",{class:"".concat(j,"-content")},[J]));var Y=be(j,a.class,l.value),ne=f.value||!e.visible,se=ne?Zo(A.value.name,A.value):{};return x(Zn,M(M({ref:s},se),{},{onBeforeEnter:E}),{default:function(){return!$||e.visible?Jn(x(ER,{target:_(),key:"popup",ref:o,monitorWindowResize:!0,disabled:N.value,align:B,onAlign:O},{default:function(){return x("div",M(M({class:Y,onMouseenter:T,onMouseleave:P,onMousedown:Vn(K,["capture"])},te({},Zt?"onTouchstartPassive":"onTouchstart",Vn(L,["capture"]))),{},{style:ee}),[J])}}),[[Jo,f.value]]):null}})}}}),PR=de({compatConfig:{MODE:3},name:"Popup",inheritAttrs:!1,props:uM,setup:function(e,n){var r=n.attrs,a=n.slots,i=n.expose,o=z(!1),s=z(!1),l=z();return ve([function(){return e.visible},function(){return e.mobile}],function(){o.value=e.visible,e.visible&&e.mobile&&(s.value=!0)},{immediate:!0,flush:"post"}),i({forceAlign:function(){var c;(c=l.value)===null||c===void 0||c.forceAlign()},getElement:function(){var c;return(c=l.value)===null||c===void 0?void 0:c.getElement()}}),function(){var u=M(M(M({},e),r),{},{visible:o.value}),c=s.value?x(cM,M(M({},u),{},{mobile:e.mobile,ref:l}),{default:a.default}):x(OR,M(M({},u),{},{ref:l}),{default:a.default});return x("div",null,[x(m1,u,null),c])}}});function TR(t,e,n){return n?t[0]===e[0]:t[0]===e[0]&&t[1]===e[1]}function Mm(t,e,n){var r=t[e]||{};return M(M({},r),n)}function IR(t,e,n,r){for(var a=n.points,i=Object.keys(t),o=0;o0&&arguments[0]!==void 0?arguments[0]:{},n=arguments.length>1?arguments[1]:void 0,r=typeof e=="function"?e(this.$data,this.$props):e;if(this.getDerivedStateFromProps){var a=this.getDerivedStateFromProps(wT(this),M(M({},this.$data),r));if(a===null)return;r=M(M({},r),a||{})}Lt(this.$data,r),this._.isMounted&&this.$forceUpdate(),ze(function(){n&&n()})},__emit:function(){var e=[].slice.call(arguments,0),n=e[0];n="on".concat(n[0].toUpperCase()).concat(n.substring(1));var r=this.$props[n]||this.$attrs[n];if(e.length&&r)if(Array.isArray(r))for(var a=0,i=r.length;a1&&arguments[1]!==void 0?arguments[1]:{inTriggerContext:!0};pt(D1,{inTriggerContext:n.inTriggerContext,shouldRender:G(function(){var r=e||{},a=r.sPopupVisible,i=r.popupRef,o=r.forceRender,s=r.autoDestroy,l=!1;return(a||i||o)&&(l=!0),!a&&s&&(l=!1),l})})},kR=function(){uv({},{inTriggerContext:!1});var e=Je(D1,{shouldRender:G(function(){return!1}),inTriggerContext:!1});return{shouldRender:G(function(){return e.shouldRender.value||e.inTriggerContext===!1})}};const hf=de({compatConfig:{MODE:3},name:"Portal",inheritAttrs:!1,props:{getContainer:Q.func.isRequired,didUpdate:Function},setup:function(e,n){var r=n.slots,a=!0,i,o=kR(),s=o.shouldRender;gd(function(){a=!1,s.value&&(i=e.getContainer())});var l=ve(s,function(){s.value&&!i&&(i=e.getContainer()),i&&l()});return oa(function(){ze(function(){if(s.value){var u;(u=e.didUpdate)===null||u===void 0||u.call(e,e)}})}),Ze(function(){i&&i.parentNode&&i.parentNode.removeChild(i)}),function(){if(!s.value)return null;if(a){var u;return(u=r.default)===null||u===void 0?void 0:u.call(r)}return i?x(Sd,{to:i},r):null}}});function km(){}function NR(){return""}function RR(t){return t?t.ownerDocument:window.document}var $R=["onClick","onMousedown","onTouchstart","onMouseenter","onMouseleave","onFocus","onBlur","onContextmenu"];const fu=de({compatConfig:{MODE:3},name:"Trigger",mixins:[L1],inheritAttrs:!1,props:{action:Q.oneOfType([Q.string,Q.arrayOf(Q.string)]).def([]),showAction:Q.any.def([]),hideAction:Q.any.def([]),getPopupClassNameFromAlign:Q.any.def(NR),onPopupVisibleChange:Function,afterPopupVisibleChange:Q.func.def(km),popup:Q.any,popupStyle:{type:Object,default:void 0},prefixCls:Q.string.def("rc-trigger-popup"),popupClassName:Q.string.def(""),popupPlacement:String,builtinPlacements:Q.object,popupTransitionName:String,popupAnimation:Q.any,mouseEnterDelay:Q.number.def(0),mouseLeaveDelay:Q.number.def(.1),zIndex:Number,focusDelay:Q.number.def(0),blurDelay:Q.number.def(.15),getPopupContainer:Function,getDocument:Q.func.def(RR),forceRender:{type:Boolean,default:void 0},destroyPopupOnHide:{type:Boolean,default:!1},mask:{type:Boolean,default:!1},maskClosable:{type:Boolean,default:!0},popupAlign:Q.object.def(function(){return{}}),popupVisible:{type:Boolean,default:void 0},defaultPopupVisible:{type:Boolean,default:!1},maskTransitionName:String,maskAnimation:String,stretch:String,alignPoint:{type:Boolean,default:void 0},autoDestroy:{type:Boolean,default:!1},mobile:Object,getTriggerDOMNode:Function,tryPopPortal:Boolean},setup:function(e){var n=G(function(){var l=e.popupPlacement,u=e.popupAlign,c=e.builtinPlacements;return l&&c?Mm(c,l,u):u}),r=MR(e.tryPopPortal),a=r.setPortal,i=r.popPortal,o=z(null),s=function(u){o.value=u};return{popPortal:i,setPortal:a,vcTriggerContext:Je("vcTriggerContext",{}),popupRef:o,setPopupRef:s,triggerRef:z(null),align:n,focusTime:null,clickOutsideHandler:null,contextmenuOutsideHandler1:null,contextmenuOutsideHandler2:null,touchOutsideHandler:null,attachId:null,delayTimer:null,hasPopupMouseDown:!1,preClickTime:null,preTouchTime:null,mouseDownTimeout:null,childOriginEvents:{}}},data:function(){var e=this,n,r=this.$props,a;return this.popupVisible!==void 0?a=!!r.popupVisible:a=!!r.defaultPopupVisible,$R.forEach(function(i){e["fire".concat(i)]=function(o){e.fireEvents(i,o)}}),(n=this.setPortal)===null||n===void 0||n.call(this,x(hf,{key:"portal",getContainer:this.getContainer,didUpdate:this.handlePortalUpdate},{default:this.getComponent})),{prevPopupVisible:a,sPopupVisible:a,point:null}},watch:{popupVisible:function(e){e!==void 0&&(this.prevPopupVisible=this.sPopupVisible,this.sPopupVisible=e)}},created:function(){pt("vcTriggerContext",{onPopupMouseDown:this.onPopupMouseDown}),uv(this)},deactivated:function(){this.setPopupVisible(!1)},mounted:function(){var e=this;this.$nextTick(function(){e.updatedCal()})},updated:function(){var e=this;this.$nextTick(function(){e.updatedCal()})},beforeUnmount:function(){this.clearDelayTimer(),this.clearOutsideHandler(),clearTimeout(this.mouseDownTimeout),Fe.cancel(this.attachId)},methods:{updatedCal:function(){var e=this.$props,n=this.$data;if(n.sPopupVisible){var r;!this.clickOutsideHandler&&(this.isClickToHide()||this.isContextmenuToShow())&&(r=e.getDocument(this.getRootDomNode()),this.clickOutsideHandler=Tn(r,"mousedown",this.onDocumentClick)),this.touchOutsideHandler||(r=r||e.getDocument(this.getRootDomNode()),this.touchOutsideHandler=Tn(r,"touchstart",this.onDocumentClick,Zt?{passive:!1}:!1)),!this.contextmenuOutsideHandler1&&this.isContextmenuToShow()&&(r=r||e.getDocument(this.getRootDomNode()),this.contextmenuOutsideHandler1=Tn(r,"scroll",this.onContextmenuClose)),!this.contextmenuOutsideHandler2&&this.isContextmenuToShow()&&(this.contextmenuOutsideHandler2=Tn(window,"blur",this.onContextmenuClose))}else this.clearOutsideHandler()},onMouseenter:function(e){var n=this.$props.mouseEnterDelay;this.fireEvents("onMouseenter",e),this.delaySetPopupVisible(!0,n,n?null:e)},onMouseMove:function(e){this.fireEvents("onMousemove",e),this.setPoint(e)},onMouseleave:function(e){this.fireEvents("onMouseleave",e),this.delaySetPopupVisible(!1,this.$props.mouseLeaveDelay)},onPopupMouseenter:function(){this.clearDelayTimer()},onPopupMouseleave:function(e){var n;e&&e.relatedTarget&&!e.relatedTarget.setTimeout&&xa((n=this.popupRef)===null||n===void 0?void 0:n.getElement(),e.relatedTarget)||this.delaySetPopupVisible(!1,this.$props.mouseLeaveDelay)},onFocus:function(e){this.fireEvents("onFocus",e),this.clearDelayTimer(),this.isFocusToShow()&&(this.focusTime=Date.now(),this.delaySetPopupVisible(!0,this.$props.focusDelay))},onMousedown:function(e){this.fireEvents("onMousedown",e),this.preClickTime=Date.now()},onTouchstart:function(e){this.fireEvents("onTouchstart",e),this.preTouchTime=Date.now()},onBlur:function(e){xa(e.target,e.relatedTarget||document.activeElement)||(this.fireEvents("onBlur",e),this.clearDelayTimer(),this.isBlurToHide()&&this.delaySetPopupVisible(!1,this.$props.blurDelay))},onContextmenu:function(e){e.preventDefault(),this.fireEvents("onContextmenu",e),this.setPopupVisible(!0,e)},onContextmenuClose:function(){this.isContextmenuToShow()&&this.close()},onClick:function(e){if(this.fireEvents("onClick",e),this.focusTime){var n;if(this.preClickTime&&this.preTouchTime?n=Math.min(this.preClickTime,this.preTouchTime):this.preClickTime?n=this.preClickTime:this.preTouchTime&&(n=this.preTouchTime),Math.abs(n-this.focusTime)<20)return;this.focusTime=0}this.preClickTime=0,this.preTouchTime=0,this.isClickToShow()&&(this.isClickToHide()||this.isBlurToHide())&&e&&e.preventDefault&&e.preventDefault(),e&&e.domEvent&&e.domEvent.preventDefault();var r=!this.$data.sPopupVisible;(this.isClickToHide()&&!r||r&&this.isClickToShow())&&this.setPopupVisible(!this.$data.sPopupVisible,e)},onPopupMouseDown:function(){var e=this,n=this.vcTriggerContext,r=n===void 0?{}:n;this.hasPopupMouseDown=!0,clearTimeout(this.mouseDownTimeout),this.mouseDownTimeout=setTimeout(function(){e.hasPopupMouseDown=!1},0),r.onPopupMouseDown&&r.onPopupMouseDown.apply(r,arguments)},onDocumentClick:function(e){if(!(this.$props.mask&&!this.$props.maskClosable)){var n=e.target,r=this.getRootDomNode(),a=this.getPopupDomNode();(!xa(r,n)||this.isContextMenuOnly())&&!xa(a,n)&&!this.hasPopupMouseDown&&this.delaySetPopupVisible(!1,.1)}},getPopupDomNode:function(){var e;return((e=this.popupRef)===null||e===void 0?void 0:e.getElement())||null},getRootDomNode:function(){var e=this.$props.getTriggerDOMNode;if(e){var n=Pa(this.triggerRef);return Pa(e(n))}try{var r=Pa(this.triggerRef);if(r)return r}catch{}return Pa(this)},handleGetPopupClassFromAlign:function(e){var n=[],r=this.$props,a=r.popupPlacement,i=r.builtinPlacements,o=r.prefixCls,s=r.alignPoint,l=r.getPopupClassNameFromAlign;return a&&i&&n.push(IR(i,o,e,s)),l&&n.push(l(e)),n.join(" ")},getPopupAlign:function(){var e=this.$props,n=e.popupPlacement,r=e.popupAlign,a=e.builtinPlacements;return n&&a?Mm(a,n,r):r},getComponent:function(){var e=this,n={};this.isMouseEnterToShow()&&(n.onMouseenter=this.onPopupMouseenter),this.isMouseLeaveToHide()&&(n.onMouseleave=this.onPopupMouseleave),n.onMousedown=this.onPopupMouseDown,n[Zt?"onTouchstartPassive":"onTouchstart"]=this.onPopupMouseDown;var r=this.handleGetPopupClassFromAlign,a=this.getRootDomNode,i=this.getContainer,o=this.$attrs,s=this.$props,l=s.prefixCls,u=s.destroyPopupOnHide,c=s.popupClassName,d=s.popupAnimation,v=s.popupTransitionName,h=s.popupStyle,f=s.mask,p=s.maskAnimation,g=s.maskTransitionName,m=s.zIndex,y=s.stretch,b=s.alignPoint,w=s.mobile,_=s.forceRender,C=this.$data,O=C.sPopupVisible,A=C.point,E=M(M({prefixCls:l,destroyPopupOnHide:u,visible:O,point:b?A:null,align:this.align,animation:d,getClassNameFromAlign:r,stretch:y,getRootDomNode:a,mask:f,zIndex:m,transitionName:v,maskAnimation:p,maskTransitionName:g,getContainer:i,class:c,style:h,onAlign:o.onPopupAlign||km},n),{},{ref:this.setPopupRef,mobile:w,forceRender:_});return x(PR,E,{default:this.$slots.popup||function(){return vw(e,"popup")}})},attachParent:function(e){var n=this;Fe.cancel(this.attachId);var r=this.$props,a=r.getPopupContainer,i=r.getDocument,o=this.getRootDomNode(),s;a?(o||a.length===0)&&(s=a(o)):s=i(this.getRootDomNode()).body,s?s.appendChild(e):this.attachId=Fe(function(){n.attachParent(e)})},getContainer:function(){var e=this.$props,n=e.getDocument,r=n(this.getRootDomNode()).createElement("div");return r.style.position="absolute",r.style.top="0",r.style.left="0",r.style.width="100%",this.attachParent(r),r},setPopupVisible:function(e,n){var r=this.alignPoint,a=this.sPopupVisible,i=this.onPopupVisibleChange;this.clearDelayTimer(),a!==e&&(Za(this,"popupVisible")||this.setState({sPopupVisible:e,prevPopupVisible:a}),i&&i(e)),r&&n&&e&&this.setPoint(n)},setPoint:function(e){var n=this.$props.alignPoint;!n||!e||this.setState({point:{pageX:e.pageX,pageY:e.pageY}})},handlePortalUpdate:function(){this.prevPopupVisible!==this.sPopupVisible&&this.afterPopupVisibleChange(this.sPopupVisible)},delaySetPopupVisible:function(e,n,r){var a=this,i=n*1e3;if(this.clearDelayTimer(),i){var o=r?{pageX:r.pageX,pageY:r.pageY}:null;this.delayTimer=sM(function(){a.setPopupVisible(e,o),a.clearDelayTimer()},i)}else this.setPopupVisible(e,r)},clearDelayTimer:function(){this.delayTimer&&(oM(this.delayTimer),this.delayTimer=null)},clearOutsideHandler:function(){this.clickOutsideHandler&&(this.clickOutsideHandler.remove(),this.clickOutsideHandler=null),this.contextmenuOutsideHandler1&&(this.contextmenuOutsideHandler1.remove(),this.contextmenuOutsideHandler1=null),this.contextmenuOutsideHandler2&&(this.contextmenuOutsideHandler2.remove(),this.contextmenuOutsideHandler2=null),this.touchOutsideHandler&&(this.touchOutsideHandler.remove(),this.touchOutsideHandler=null)},createTwoChains:function(e){var n=function(){},r=dh(this);return this.childOriginEvents[e]&&r[e]?this["fire".concat(e)]:(n=this.childOriginEvents[e]||r[e]||n,n)},isClickToShow:function(){var e=this.$props,n=e.action,r=e.showAction;return n.indexOf("click")!==-1||r.indexOf("click")!==-1},isContextMenuOnly:function(){var e=this.$props.action;return e==="contextmenu"||e.length===1&&e[0]==="contextmenu"},isContextmenuToShow:function(){var e=this.$props,n=e.action,r=e.showAction;return n.indexOf("contextmenu")!==-1||r.indexOf("contextmenu")!==-1},isClickToHide:function(){var e=this.$props,n=e.action,r=e.hideAction;return n.indexOf("click")!==-1||r.indexOf("click")!==-1},isMouseEnterToShow:function(){var e=this.$props,n=e.action,r=e.showAction;return n.indexOf("hover")!==-1||r.indexOf("mouseenter")!==-1},isMouseLeaveToHide:function(){var e=this.$props,n=e.action,r=e.hideAction;return n.indexOf("hover")!==-1||r.indexOf("mouseleave")!==-1},isFocusToShow:function(){var e=this.$props,n=e.action,r=e.showAction;return n.indexOf("focus")!==-1||r.indexOf("focus")!==-1},isBlurToHide:function(){var e=this.$props,n=e.action,r=e.hideAction;return n.indexOf("focus")!==-1||r.indexOf("blur")!==-1},forcePopupAlign:function(){if(this.$data.sPopupVisible){var e;(e=this.popupRef)===null||e===void 0||e.forceAlign()}},fireEvents:function(e,n){this.childOriginEvents[e]&&this.childOriginEvents[e](n);var r=this.$props[e]||this.$attrs[e];r&&r(n)},close:function(){this.setPopupVisible(!1)}},render:function(){var e=this,n=this.$attrs,r=Mi(bT(this)),a=this.$props.alignPoint,i=r[0];this.childOriginEvents=dh(i);var o={key:"trigger"};this.isContextmenuToShow()?o.onContextmenu=this.onContextmenu:o.onContextmenu=this.createTwoChains("onContextmenu"),this.isClickToHide()||this.isClickToShow()?(o.onClick=this.onClick,o.onMousedown=this.onMousedown,o[Zt?"onTouchstartPassive":"onTouchstart"]=this.onTouchstart):(o.onClick=this.createTwoChains("onClick"),o.onMousedown=this.createTwoChains("onMousedown"),o[Zt?"onTouchstartPassive":"onTouchstart"]=this.createTwoChains("onTouchstart")),this.isMouseEnterToShow()?(o.onMouseenter=this.onMouseenter,a&&(o.onMousemove=this.onMouseMove)):o.onMouseenter=this.createTwoChains("onMouseenter"),this.isMouseLeaveToHide()?o.onMouseleave=this.onMouseleave:o.onMouseleave=this.createTwoChains("onMouseleave"),this.isFocusToShow()||this.isBlurToHide()?(o.onFocus=this.onFocus,o.onBlur=this.onBlur):(o.onFocus=this.createTwoChains("onFocus"),o.onBlur=function(c){c&&(!c.relatedTarget||!xa(c.target,c.relatedTarget))&&e.createTwoChains("onBlur")(c)});var s=be(i&&i.props&&i.props.class,n.class);s&&(o.class=s);var l=yt(i,M(M({},o),{},{ref:"triggerRef"}),!0,!0);if(this.popPortal)return l;var u=x(hf,{key:"portal",getContainer:this.getContainer,didUpdate:this.handlePortalUpdate},{default:this.getComponent});return x(Ae,null,[u,l])}});var LR=["empty"],DR=function(e){var n=e===!0?0:1;return{bottomLeft:{points:["tl","bl"],offset:[0,4],overflow:{adjustX:n,adjustY:1}},bottomRight:{points:["tr","br"],offset:[0,4],overflow:{adjustX:n,adjustY:1}},topLeft:{points:["bl","tl"],offset:[0,-4],overflow:{adjustX:n,adjustY:1}},topRight:{points:["br","tr"],offset:[0,-4],overflow:{adjustX:n,adjustY:1}}}},FR=de({name:"SelectTrigger",inheritAttrs:!1,props:{dropdownAlign:Object,visible:{type:Boolean,default:void 0},disabled:{type:Boolean,default:void 0},dropdownClassName:String,dropdownStyle:Q.object,placement:String,empty:{type:Boolean,default:void 0},prefixCls:String,popupClassName:String,animation:String,transitionName:String,getPopupContainer:Function,dropdownRender:Function,containerWidth:Number,dropdownMatchSelectWidth:Q.oneOfType([Number,Boolean]).def(!0),popupElement:Q.any,direction:String,getTriggerDOMNode:Function,onPopupVisibleChange:Function,onPopupMouseEnter:Function},setup:function(e,n){var r=n.slots,a=n.attrs,i=n.expose,o=G(function(){var l=e.dropdownMatchSelectWidth;return DR(l)}),s=z();return i({getPopupElement:function(){return s.value}}),function(){var l=M(M({},e),a),u=l.empty,c=u===void 0?!1:u,d=vt(l,LR),v=d.visible,h=d.dropdownAlign,f=d.prefixCls,p=d.popupElement,g=d.dropdownClassName,m=d.dropdownStyle,y=d.direction,b=y===void 0?"ltr":y,w=d.placement,_=d.dropdownMatchSelectWidth,C=d.containerWidth,O=d.dropdownRender,A=d.animation,E=d.transitionName,N=d.getPopupContainer,R=d.getTriggerDOMNode,D=d.onPopupVisibleChange,B=d.onPopupMouseEnter,j="".concat(f,"-dropdown"),$=p;O&&($=O({menuNode:p,props:e}));var T=A?"".concat(j,"-").concat(A):E,P=M({minWidth:"".concat(C,"px")},m);return typeof _=="number"?P.width="".concat(_,"px"):_&&(P.width="".concat(C,"px")),x(fu,M(M({},e),{},{showAction:D?["click"]:[],hideAction:D?["click"]:[],popupPlacement:w||(b==="rtl"?"bottomRight":"bottomLeft"),builtinPlacements:o.value,prefixCls:j,popupTransitionName:T,popupAlign:h,popupVisible:v,getPopupContainer:N,popupClassName:be(g,te({},"".concat(j,"-empty"),c)),popupStyle:P,getTriggerDOMNode:R,onPopupVisibleChange:D}),{default:r.default,popup:function(){return x("div",{ref:s,onMouseenter:B},[$])}})}}});const BR=FR;var Te={MAC_ENTER:3,BACKSPACE:8,TAB:9,NUM_CENTER:12,ENTER:13,SHIFT:16,CTRL:17,ALT:18,PAUSE:19,CAPS_LOCK:20,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,PRINT_SCREEN:44,INSERT:45,DELETE:46,ZERO:48,ONE:49,TWO:50,THREE:51,FOUR:52,FIVE:53,SIX:54,SEVEN:55,EIGHT:56,NINE:57,QUESTION_MARK:63,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90,META:91,WIN_KEY_RIGHT:92,CONTEXT_MENU:93,NUM_ZERO:96,NUM_ONE:97,NUM_TWO:98,NUM_THREE:99,NUM_FOUR:100,NUM_FIVE:101,NUM_SIX:102,NUM_SEVEN:103,NUM_EIGHT:104,NUM_NINE:105,NUM_MULTIPLY:106,NUM_PLUS:107,NUM_MINUS:109,NUM_PERIOD:110,NUM_DIVISION:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,NUMLOCK:144,SEMICOLON:186,DASH:189,EQUALS:187,COMMA:188,PERIOD:190,SLASH:191,APOSTROPHE:192,SINGLE_QUOTE:222,OPEN_SQUARE_BRACKET:219,BACKSLASH:220,CLOSE_SQUARE_BRACKET:221,WIN_KEY:224,MAC_FF_META:224,WIN_IME:229,isTextModifyingKeyEvent:function(e){var n=e.keyCode;if(e.altKey&&!e.ctrlKey||e.metaKey||n>=Te.F1&&n<=Te.F12)return!1;switch(n){case Te.ALT:case Te.CAPS_LOCK:case Te.CONTEXT_MENU:case Te.CTRL:case Te.DOWN:case Te.END:case Te.ESC:case Te.HOME:case Te.INSERT:case Te.LEFT:case Te.MAC_FF_META:case Te.META:case Te.NUMLOCK:case Te.NUM_CENTER:case Te.PAGE_DOWN:case Te.PAGE_UP:case Te.PAUSE:case Te.PRINT_SCREEN:case Te.RIGHT:case Te.SHIFT:case Te.UP:case Te.WIN_KEY:case Te.WIN_KEY_RIGHT:return!1;default:return!0}},isCharacterKey:function(e){if(e>=Te.ZERO&&e<=Te.NINE||e>=Te.NUM_ZERO&&e<=Te.NUM_MULTIPLY||e>=Te.A&&e<=Te.Z||window.navigator.userAgent.indexOf("WebKit")!==-1&&e===0)return!0;switch(e){case Te.SPACE:case Te.QUESTION_MARK:case Te.NUM_PLUS:case Te.NUM_MINUS:case Te.NUM_PERIOD:case Te.NUM_DIVISION:case Te.SEMICOLON:case Te.DASH:case Te.EQUALS:case Te.COMMA:case Te.PERIOD:case Te.SLASH:case Te.APOSTROPHE:case Te.SINGLE_QUOTE:case Te.OPEN_SQUARE_BRACKET:case Te.BACKSLASH:case Te.CLOSE_SQUARE_BRACKET:return!0;default:return!1}}};const Ee=Te;var du=function(e,n){var r,a=n.slots,i=e.class,o=e.customizeIcon,s=e.customizeIconProps,l=e.onMousedown,u=e.onClick,c;return typeof o=="function"?c=o(s):c=o,x("span",{class:i,onMousedown:function(v){v.preventDefault(),l&&l(v)},style:{userSelect:"none",WebkitUserSelect:"none"},unselectable:"on",onClick:u,"aria-hidden":!0},[c!==void 0?c:x("span",{class:i.split(/\s+/).map(function(d){return"".concat(d,"-icon")})},[(r=a.default)===null||r===void 0?void 0:r.call(a)])])};du.inheritAttrs=!1;du.displayName="TransBtn";du.props={class:String,customizeIcon:Q.any,customizeIconProps:Q.any,onMousedown:Function,onClick:Function};const Cl=du;function jR(t){t.target.composing=!0}function Nm(t){t.target.composing&&(t.target.composing=!1,zR(t.target,"input"))}function zR(t,e){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0),t.dispatchEvent(n)}function oc(t,e,n,r){t.addEventListener(e,n,r)}var WR={created:function(e,n){(!n.modifiers||!n.modifiers.lazy)&&(oc(e,"compositionstart",jR),oc(e,"compositionend",Nm),oc(e,"change",Nm))}};const ts=WR;var HR={inputRef:Q.any,prefixCls:String,id:String,inputElement:Q.VueNode,disabled:{type:Boolean,default:void 0},autofocus:{type:Boolean,default:void 0},autocomplete:String,editable:{type:Boolean,default:void 0},activeDescendantId:String,value:String,open:{type:Boolean,default:void 0},tabindex:Q.oneOfType([Q.number,Q.string]),attrs:Q.object,onKeydown:{type:Function},onMousedown:{type:Function},onChange:{type:Function},onPaste:{type:Function},onCompositionstart:{type:Function},onCompositionend:{type:Function},onFocus:{type:Function},onBlur:{type:Function}},VR=de({compatConfig:{MODE:3},name:"Input",inheritAttrs:!1,props:HR,setup:function(e){var n=null,r=Je("VCSelectContainerEvent");return function(){var a,i,o=e.prefixCls,s=e.id,l=e.inputElement,u=e.disabled,c=e.tabindex,d=e.autofocus,v=e.autocomplete,h=e.editable,f=e.activeDescendantId,p=e.value,g=e.onKeydown,m=e.onMousedown,y=e.onChange,b=e.onPaste,w=e.onCompositionstart,_=e.onCompositionend,C=e.onFocus,O=e.onBlur,A=e.open,E=e.inputRef,N=e.attrs,R=l||Jn(x("input",null,null),[[ts]]),D=R.props||{},B=D.onKeydown,j=D.onInput,$=D.onFocus,T=D.onBlur,P=D.onMousedown,k=D.onCompositionstart,L=D.onCompositionend,K=D.style;return R=yt(R,Lt(M(M(M({type:"search"},D),{},{id:s,ref:E,disabled:u,tabindex:c,autocomplete:v||"off",autofocus:d,class:be("".concat(o,"-selection-search-input"),(a=R)===null||a===void 0||(i=a.props)===null||i===void 0?void 0:i.class),role:"combobox","aria-expanded":A,"aria-haspopup":"listbox","aria-owns":"".concat(s,"_list"),"aria-autocomplete":"list","aria-controls":"".concat(s,"_list"),"aria-activedescendant":f},N),{},{value:h?p:"",readonly:!h,unselectable:h?null:"on",style:M(M({},K),{},{opacity:h?null:0}),onKeydown:function(ee){g(ee),B&&B(ee)},onMousedown:function(ee){m(ee),P&&P(ee)},onInput:function(ee){y(ee),j&&j(ee)},onCompositionstart:function(ee){w(ee),k&&k(ee)},onCompositionend:function(ee){_(ee),L&&L(ee)},onPaste:b,onFocus:function(){clearTimeout(n),$&&$(arguments.length<=0?void 0:arguments[0]),C&&C(arguments.length<=0?void 0:arguments[0]),r==null||r.focus(arguments.length<=0?void 0:arguments[0])},onBlur:function(){for(var ee=arguments.length,J=new Array(ee),Y=0;Y