From c3712b013fa55a7a90e62992cd79481d6e2cf98c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 19 Sep 2022 09:09:11 +1000 Subject: [PATCH 01/10] Fixes metadata implementation #686 --- ldm/dream/args.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/ldm/dream/args.py b/ldm/dream/args.py index 8a8a3a41b3..7d8f473b4a 100644 --- a/ldm/dream/args.py +++ b/ldm/dream/args.py @@ -602,6 +602,16 @@ def metadata_dumps(opt, This is intended to be turned into JSON and stored in the "sd ''' + + # top-level metadata minus `image` or `images` + metadata = { + 'model' : 'stable diffusion', + 'model_id' : opt.model, + 'model_hash' : model_hash, + 'app_id' : APP_ID, + 'app_version' : APP_VERSION, + } + # add some RFC266 fields that are generated internally, and not as # user args image_dict = opt.to_dict( @@ -647,22 +657,22 @@ def metadata_dumps(opt, else: rfc_dict['type'] = 'txt2img' - images = [] if len(seeds)==0 and opt.seed: seeds=[seed] - - for seed in seeds: - rfc_dict['seed'] = seed - images.append(copy.copy(rfc_dict)) - return { - 'model' : 'stable diffusion', - 'model_id' : opt.model, - 'model_hash' : model_hash, - 'app_id' : APP_ID, - 'app_version' : APP_VERSION, - 'images' : images, - } + if opt.grid: + images = [] + for seed in seeds: + rfc_dict['seed'] = seed + images.append(copy.copy(rfc_dict)) + metadata['images'] = images + else: + # there should only ever be a single seed if we did not generate a grid + assert len(seeds) == 1, 'Expected a single seed' + rfc_dict['seed'] = seeds[0] + metadata['image'] = rfc_dict + + return metadata def metadata_loads(metadata): ''' From f404c692ad23dfbbdb61e313d2adba78bec42eb6 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 19 Sep 2022 11:53:47 +1000 Subject: [PATCH 02/10] Fixes metadata_loads() #686 --- ldm/dream/args.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ldm/dream/args.py b/ldm/dream/args.py index 7d8f473b4a..db6d963645 100644 --- a/ldm/dream/args.py +++ b/ldm/dream/args.py @@ -681,7 +681,10 @@ def metadata_loads(metadata): ''' results = [] try: - images = metadata['sd-metadata']['images'] + if 'grid' in metadata['sd-metadata']: + images = metadata['sd-metadata']['images'] + else: + images = [metadata['sd-metadata']['image']] for image in images: # repack the prompt and variations image['prompt'] = ','.join([':'.join([x['prompt'], str(x['weight'])]) for x in image['prompt']]) From 94ca13c49431e74876005f0dfd79769d0d13f9c2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 19 Sep 2022 11:53:47 +1000 Subject: [PATCH 03/10] Fixes metadata_loads() #686 --- ldm/dream/args.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ldm/dream/args.py b/ldm/dream/args.py index 7d8f473b4a..db6d963645 100644 --- a/ldm/dream/args.py +++ b/ldm/dream/args.py @@ -681,7 +681,10 @@ def metadata_loads(metadata): ''' results = [] try: - images = metadata['sd-metadata']['images'] + if 'grid' in metadata['sd-metadata']: + images = metadata['sd-metadata']['images'] + else: + images = [metadata['sd-metadata']['image']] for image in images: # repack the prompt and variations image['prompt'] = ','.join([':'.join([x['prompt'], str(x['weight'])]) for x in image['prompt']]) From 2a6999d500d1d95a2fd454497255ca61467aeb21 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 19 Sep 2022 17:38:56 +1000 Subject: [PATCH 04/10] Fixes various issues with metadata handling --- ldm/dream/args.py | 57 ++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/ldm/dream/args.py b/ldm/dream/args.py index db6d963645..ada8975e96 100644 --- a/ldm/dream/args.py +++ b/ldm/dream/args.py @@ -174,31 +174,37 @@ class Args(object): switches.append(f'-W {a["width"]}') switches.append(f'-H {a["height"]}') switches.append(f'-C {a["cfg_scale"]}') - switches.append(f'-A {a["sampler_name"]}') if a['grid']: switches.append('--grid') if a['seamless']: switches.append('--seamless') + + # img2img generations have parameters relevant only to them and have special handling if a['init_img'] and len(a['init_img'])>0: switches.append(f'-I {a["init_img"]}') - if a['init_mask'] and len(a['init_mask'])>0: - switches.append(f'-M {a["init_mask"]}') - if a['init_color'] and len(a['init_color'])>0: - switches.append(f'--init_color {a["init_color"]}') - if a['fit']: - switches.append(f'--fit') - if a['init_img'] and a['strength'] and a['strength']>0: - switches.append(f'-f {a["strength"]}') + switches.append(f'-A ddim') # TODO: FIX ME WHEN IMG2IMG SUPPORTS ALL SAMPLERS + if a['fit']: + switches.append(f'--fit') + if a['init_mask'] and len(a['init_mask'])>0: + switches.append(f'-M {a["init_mask"]}') + if a['init_color'] and len(a['init_color'])>0: + switches.append(f'--init_color {a["init_color"]}') + if a['strength'] and a['strength']>0: + switches.append(f'-f {a["strength"]}') + else: + switches.append(f'-A {a["sampler_name"]}') + + # gfpgan-specific parameters if a['gfpgan_strength']: switches.append(f'-G {a["gfpgan_strength"]}') + + # esrgan-specific parameters if a['upscale']: switches.append(f'-U {" ".join([str(u) for u in a["upscale"]])}') if a['embiggen']: switches.append(f'--embiggen {" ".join([str(u) for u in a["embiggen"]])}') if a['embiggen_tiles']: switches.append(f'--embiggen_tiles {" ".join([str(u) for u in a["embiggen_tiles"]])}') - if a['variation_amount'] > 0: - switches.append(f'-v {a["variation_amount"]}') if a['with_variations']: formatted_variations = ','.join(f'{seed}:{weight}' for seed, weight in (a["with_variations"])) switches.append(f'-V {formatted_variations}') @@ -618,18 +624,24 @@ def metadata_dumps(opt, postprocessing=postprocessing ) - # TODO: This is just a hack until postprocessing pipeline work completed - image_dict['postprocessing'] = [] - if image_dict['gfpgan_strength'] and image_dict['gfpgan_strength'] > 0: - image_dict['postprocessing'].append('GFPGAN (not RFC compliant)') - if image_dict['upscale'] and image_dict['upscale'][0] > 0: - image_dict['postprocessing'].append('ESRGAN (not RFC compliant)') + # 'postprocessing' is either null or an array of postprocessing metadatal + if postprocessing: + # TODO: This is just a hack until postprocessing pipeline work completed + image_dict['postprocessing'] = [] + + if image_dict['gfpgan_strength'] and image_dict['gfpgan_strength'] > 0: + image_dict['postprocessing'].append('GFPGAN (not RFC compliant)') + if image_dict['upscale'] and image_dict['upscale'][0] > 0: + image_dict['postprocessing'].append('ESRGAN (not RFC compliant)') + else: + image_dict['postprocessing'] = None # remove any image keys not mentioned in RFC #266 rfc266_img_fields = ['type','postprocessing','sampler','prompt','seed','variations','steps', 'cfg_scale','step_number','width','height','extra','strength'] rfc_dict ={} + for item in image_dict.items(): key,value = item if key in rfc266_img_fields: @@ -637,25 +649,24 @@ def metadata_dumps(opt, # semantic drift rfc_dict['sampler'] = image_dict.get('sampler_name',None) - + # display weighted subprompts (liable to change) if opt.prompt: subprompts = split_weighted_subprompts(opt.prompt) subprompts = [{'prompt':x[0],'weight':x[1]} for x in subprompts] rfc_dict['prompt'] = subprompts - # variations - if opt.with_variations: - variations = [{'seed':x[0],'weight':x[1]} for x in opt.with_variations] - rfc_dict['variations'] = variations + # 'variations' should always exist and be an array, empty or consisting of {'seed': seed, 'weight': weight} pairs + rfc_dict['variations'] = [{'seed':x[0],'weight':x[1]} for x in opt.with_variations] if opt.with_variations else [] if opt.init_img: rfc_dict['type'] = 'img2img' rfc_dict['strength_steps'] = rfc_dict.pop('strength') rfc_dict['orig_hash'] = calculate_init_img_hash(opt.init_img) - rfc_dict['sampler'] = 'ddim' # FIX ME WHEN IMG2IMG SUPPORTS ALL SAMPLERS + rfc_dict['sampler'] = 'ddim' # TODO: FIX ME WHEN IMG2IMG SUPPORTS ALL SAMPLERS else: rfc_dict['type'] = 'txt2img' + rfc_dict.pop('strength') if len(seeds)==0 and opt.seed: seeds=[seed] From b970ec4ce9289e31ca9564d143cc390f87132c56 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 20 Sep 2022 23:17:31 +1000 Subject: [PATCH 05/10] Refactors metadata & types, bugfixes, organization --- backend/modules/metadata.py | 87 +++ backend/modules/parameters.py | 216 +----- backend/server.py | 568 +++++++++----- frontend/dist/assets/index.727a397b.js | 694 ++++++++++++++++++ frontend/dist/assets/index.de730902.js | 694 ------------------ frontend/dist/index.html | 2 +- frontend/package.json | 5 +- frontend/src/app/App.tsx | 13 +- frontend/src/app/invokeai.d.ts | 170 +++++ frontend/src/app/socketio/actions.ts | 10 +- frontend/src/app/socketio/emitters.ts | 17 +- frontend/src/app/socketio/listeners.ts | 100 +-- frontend/src/app/socketio/middleware.ts | 58 +- frontend/src/app/socketio/types.d.ts | 46 -- frontend/src/app/store.ts | 4 +- frontend/src/app/theme.ts | 15 + frontend/src/app/wip_types.ts | 171 ----- .../src/common/hooks/useCheckParameters.ts | 22 +- .../src/common/util/parameterTranslation.ts | 312 ++++---- frontend/src/common/util/promptToString.ts | 16 + frontend/src/common/util/seedWeightPairs.ts | 100 +-- .../features/gallery/CurrentImageButtons.tsx | 24 +- .../src/features/gallery/DeleteImageModal.tsx | 132 ++-- .../src/features/gallery/HoverableImage.tsx | 74 +- .../features/gallery/ImageMetadataViewer.tsx | 356 ++++++--- frontend/src/features/gallery/gallerySlice.ts | 48 +- .../{sd => options}/ESRGANOptions.tsx | 16 +- .../{sd => options}/GFPGANOptions.tsx | 12 +- .../{sd => options}/ImageToImageOptions.tsx | 16 +- .../{sd => options}/ImageUploader.tsx | 5 +- .../{sd => options}/InitAndMaskImage.css | 0 .../{sd => options}/InitAndMaskImage.tsx | 14 +- .../InitAndMaskUploadButtons.tsx | 52 +- .../{sd => options}/OptionsAccordion.tsx | 20 +- .../{sd => options}/OutputOptions.tsx | 16 +- .../{sd => options}/ProcessButtons.tsx | 0 .../features/{sd => options}/PromptInput.tsx | 4 +- .../{sd => options}/SamplerOptions.tsx | 16 +- .../{sd => options}/SeedVariationOptions.tsx | 24 +- .../sdSlice.ts => options/optionsSlice.ts} | 141 ++-- .../{header => system}/ProgressBar.tsx | 0 .../{header => system}/SiteHeader.tsx | 2 +- frontend/src/features/system/systemSlice.ts | 24 +- frontend/yarn.lock | 14 + 44 files changed, 2339 insertions(+), 1991 deletions(-) create mode 100644 backend/modules/metadata.py create mode 100644 frontend/dist/assets/index.727a397b.js delete mode 100644 frontend/dist/assets/index.de730902.js create mode 100644 frontend/src/app/invokeai.d.ts delete mode 100644 frontend/src/app/socketio/types.d.ts delete mode 100644 frontend/src/app/wip_types.ts create mode 100644 frontend/src/common/util/promptToString.ts rename frontend/src/features/{sd => options}/ESRGANOptions.tsx (84%) rename frontend/src/features/{sd => options}/GFPGANOptions.tsx (81%) rename frontend/src/features/{sd => options}/ImageToImageOptions.tsx (82%) rename frontend/src/features/{sd => options}/ImageUploader.tsx (94%) rename frontend/src/features/{sd => options}/InitAndMaskImage.css (100%) rename frontend/src/features/{sd => options}/InitAndMaskImage.tsx (80%) rename frontend/src/features/{sd => options}/InitAndMaskUploadButtons.tsx (75%) rename frontend/src/features/{sd => options}/OptionsAccordion.tsx (93%) rename frontend/src/features/{sd => options}/OutputOptions.tsx (82%) rename frontend/src/features/{sd => options}/ProcessButtons.tsx (100%) rename frontend/src/features/{sd => options}/PromptInput.tsx (94%) rename frontend/src/features/{sd => options}/SamplerOptions.tsx (80%) rename frontend/src/features/{sd => options}/SeedVariationOptions.tsx (89%) rename frontend/src/features/{sd/sdSlice.ts => options/optionsSlice.ts} (62%) rename frontend/src/features/{header => system}/ProgressBar.tsx (100%) rename frontend/src/features/{header => system}/SiteHeader.tsx (97%) diff --git a/backend/modules/metadata.py b/backend/modules/metadata.py new file mode 100644 index 0000000000..60c083ef8b --- /dev/null +++ b/backend/modules/metadata.py @@ -0,0 +1,87 @@ +def parameters_to_metadata(opt, + seeds=[], + model_hash=None, + postprocessing=None): + ''' + Given an Args object, returns a dict containing the keys and + structure of the proposed stable diffusion metadata standard + https://github.com/lstein/stable-diffusion/discussions/392 + This is intended to be turned into JSON and stored in the + "sd + ''' + + # top-level metadata minus `image` or `images` + metadata = { + 'model' : 'stable diffusion', + 'model_id' : opt.model, + 'model_hash' : model_hash, + 'app_id' : APP_ID, + 'app_version' : APP_VERSION, + } + + # add some RFC266 fields that are generated internally, and not as + # user args + image_dict = opt.to_dict( + postprocessing=postprocessing + ) + + # 'postprocessing' is either null or an array of postprocessing metadatal + if postprocessing: + # TODO: This is just a hack until postprocessing pipeline work completed + image_dict['postprocessing'] = [] + + if image_dict['gfpgan_strength'] and image_dict['gfpgan_strength'] > 0: + image_dict['postprocessing'].append('GFPGAN (not RFC compliant)') + if image_dict['upscale'] and image_dict['upscale'][0] > 0: + image_dict['postprocessing'].append('ESRGAN (not RFC compliant)') + else: + image_dict['postprocessing'] = None + + # remove any image keys not mentioned in RFC #266 + rfc266_img_fields = ['type','postprocessing','sampler','prompt','seed','variations','steps', + 'cfg_scale','step_number','width','height','extra','strength'] + + rfc_dict ={} + + for item in image_dict.items(): + key,value = item + if key in rfc266_img_fields: + rfc_dict[key] = value + + # semantic drift + rfc_dict['sampler'] = image_dict.get('sampler_name',None) + + # display weighted subprompts (liable to change) + if opt.prompt: + subprompts = split_weighted_subprompts(opt.prompt) + subprompts = [{'prompt':x[0],'weight':x[1]} for x in subprompts] + rfc_dict['prompt'] = subprompts + + # 'variations' should always exist and be an array, empty or consisting of {'seed': seed, 'weight': weight} pairs + rfc_dict['variations'] = [{'seed':x[0],'weight':x[1]} for x in opt.with_variations] if opt.with_variations else [] + + if opt.init_img: + rfc_dict['type'] = 'img2img' + rfc_dict['strength_steps'] = rfc_dict.pop('strength') + rfc_dict['orig_hash'] = calculate_init_img_hash(opt.init_img) + rfc_dict['sampler'] = 'ddim' # TODO: FIX ME WHEN IMG2IMG SUPPORTS ALL SAMPLERS + else: + rfc_dict['type'] = 'txt2img' + rfc_dict.pop('strength') + + if len(seeds)==0 and opt.seed: + seeds=[seed] + + if opt.grid: + images = [] + for seed in seeds: + rfc_dict['seed'] = seed + images.append(copy.copy(rfc_dict)) + metadata['images'] = images + else: + # there should only ever be a single seed if we did not generate a grid + assert len(seeds) == 1, 'Expected a single seed' + rfc_dict['seed'] = seeds[0] + metadata['image'] = rfc_dict + + return metadata diff --git a/backend/modules/parameters.py b/backend/modules/parameters.py index 95d07921ac..ec0cfe8272 100644 --- a/backend/modules/parameters.py +++ b/backend/modules/parameters.py @@ -2,14 +2,14 @@ from modules.parse_seed_weights import parse_seed_weights import argparse SAMPLER_CHOICES = [ - 'ddim', - 'k_dpm_2_a', - 'k_dpm_2', - 'k_euler_a', - 'k_euler', - 'k_heun', - 'k_lms', - 'plms', + "ddim", + "k_dpm_2_a", + "k_dpm_2", + "k_euler_a", + "k_euler", + "k_heun", + "k_lms", + "plms", ] @@ -20,194 +20,42 @@ def parameters_to_command(params): switches = list() - if 'prompt' in params: + if "prompt" in params: switches.append(f'"{params["prompt"]}"') - if 'steps' in params: + if "steps" in params: switches.append(f'-s {params["steps"]}') - if 'seed' in params: + if "seed" in params: switches.append(f'-S {params["seed"]}') - if 'width' in params: + if "width" in params: switches.append(f'-W {params["width"]}') - if 'height' in params: + if "height" in params: switches.append(f'-H {params["height"]}') - if 'cfg_scale' in params: + if "cfg_scale" in params: switches.append(f'-C {params["cfg_scale"]}') - if 'sampler_name' in params: + if "sampler_name" in params: switches.append(f'-A {params["sampler_name"]}') - if 'seamless' in params and params["seamless"] == True: - switches.append(f'--seamless') - if 'init_img' in params and len(params['init_img']) > 0: + if "seamless" in params and params["seamless"] == True: + switches.append(f"--seamless") + if "init_img" in params and len(params["init_img"]) > 0: switches.append(f'-I {params["init_img"]}') - if 'init_mask' in params and len(params['init_mask']) > 0: + if "init_mask" in params and len(params["init_mask"]) > 0: switches.append(f'-M {params["init_mask"]}') - if 'init_color' in params and len(params['init_color']) > 0: + if "init_color" in params and len(params["init_color"]) > 0: switches.append(f'--init_color {params["init_color"]}') - if 'strength' in params and 'init_img' in params: + if "strength" in params and "init_img" in params: switches.append(f'-f {params["strength"]}') - if 'fit' in params and params["fit"] == True: - switches.append(f'--fit') - if 'gfpgan_strength' in params and params["gfpgan_strength"]: + if "fit" in params and params["fit"] == True: + switches.append(f"--fit") + if "gfpgan_strength" in params and params["gfpgan_strength"]: switches.append(f'-G {params["gfpgan_strength"]}') - if 'upscale' in params and params["upscale"]: + if "upscale" in params and params["upscale"]: switches.append(f'-U {params["upscale"][0]} {params["upscale"][1]}') - if 'variation_amount' in params and params['variation_amount'] > 0: + if "variation_amount" in params and params["variation_amount"] > 0: switches.append(f'-v {params["variation_amount"]}') - if 'with_variations' in params: - seed_weight_pairs = ','.join(f'{seed}:{weight}' for seed, weight in params["with_variations"]) - switches.append(f'-V {seed_weight_pairs}') + if "with_variations" in params: + seed_weight_pairs = ",".join( + f"{seed}:{weight}" for seed, weight in params["with_variations"] + ) + switches.append(f"-V {seed_weight_pairs}") - return ' '.join(switches) - - - -def create_cmd_parser(): - """ - This is simply a copy of the parser from `dream.py` with a change to give - prompt a default value. This is a temporary hack pending merge of #587 which - provides a better way to do this. - """ - parser = argparse.ArgumentParser( - description='Example: dream> a fantastic alien landscape -W1024 -H960 -s100 -n12', - exit_on_error=True, - ) - parser.add_argument('prompt', nargs='?', default='') - parser.add_argument('-s', '--steps', type=int, help='Number of steps') - parser.add_argument( - '-S', - '--seed', - type=int, - help='Image seed; a +ve integer, or use -1 for the previous seed, -2 for the one before that, etc', - ) - parser.add_argument( - '-n', - '--iterations', - type=int, - default=1, - help='Number of samplings to perform (slower, but will provide seeds for individual images)', - ) - parser.add_argument( - '-W', '--width', type=int, help='Image width, multiple of 64' - ) - parser.add_argument( - '-H', '--height', type=int, help='Image height, multiple of 64' - ) - parser.add_argument( - '-C', - '--cfg_scale', - default=7.5, - type=float, - help='Classifier free guidance (CFG) scale - higher numbers cause generator to "try" harder.', - ) - parser.add_argument( - '-g', '--grid', action='store_true', help='generate a grid' - ) - parser.add_argument( - '--outdir', - '-o', - type=str, - default=None, - help='Directory to save generated images and a log of prompts and seeds', - ) - parser.add_argument( - '--seamless', - action='store_true', - help='Change the model to seamless tiling (circular) mode', - ) - parser.add_argument( - '-i', - '--individual', - action='store_true', - help='Generate individual files (default)', - ) - parser.add_argument( - '-I', - '--init_img', - type=str, - help='Path to input image for img2img mode (supersedes width and height)', - ) - parser.add_argument( - '-M', - '--init_mask', - type=str, - help='Path to input mask for inpainting mode (supersedes width and height)', - ) - parser.add_argument( - '--init_color', - type=str, - help='Path to reference image for color correction (used for repeated img2img and inpainting)' - ) - parser.add_argument( - '-T', - '-fit', - '--fit', - action='store_true', - help='If specified, will resize the input image to fit within the dimensions of width x height (512x512 default)', - ) - parser.add_argument( - '-f', - '--strength', - default=0.75, - type=float, - help='Strength for noising/unnoising. 0.0 preserves image exactly, 1.0 replaces it completely', - ) - parser.add_argument( - '-G', - '--gfpgan_strength', - default=0, - type=float, - help='The strength at which to apply the GFPGAN model to the result, in order to improve faces.', - ) - parser.add_argument( - '-U', - '--upscale', - nargs='+', - default=None, - type=float, - help='Scale factor (2, 4) for upscaling followed by upscaling strength (0-1.0). If strength not specified, defaults to 0.75' - ) - parser.add_argument( - '-save_orig', - '--save_original', - action='store_true', - help='Save original. Use it when upscaling to save both versions.', - ) - # variants is going to be superseded by a generalized "prompt-morph" function - # parser.add_argument('-v','--variants',type=int,help="in img2img mode, the first generated image will get passed back to img2img to generate the requested number of variants") - parser.add_argument( - '-x', - '--skip_normalize', - action='store_true', - help='Skip subprompt weight normalization', - ) - parser.add_argument( - '-A', - '-m', - '--sampler', - dest='sampler_name', - default=None, - type=str, - choices=SAMPLER_CHOICES, - metavar='SAMPLER_NAME', - help=f'Switch to a different sampler. Supported samplers: {", ".join(SAMPLER_CHOICES)}', - ) - parser.add_argument( - '-t', - '--log_tokenization', - action='store_true', - help='shows how the prompt is split into tokens' - ) - parser.add_argument( - '-v', - '--variation_amount', - default=0.0, - type=float, - help='If > 0, generates variations on the initial seed instead of random seeds per iteration. Must be between 0 and 1. Higher values will be more different.' - ) - parser.add_argument( - '-V', - '--with_variations', - default=None, - type=str, - help='list of variations to apply, in the format `seed:weight,seed:weight,...' - ) - return parser + return " ".join(switches) diff --git a/backend/server.py b/backend/server.py index b1346691e4..7518f41c5b 100644 --- a/backend/server.py +++ b/backend/server.py @@ -6,7 +6,6 @@ import traceback import eventlet import glob import shlex -import argparse import math import shutil @@ -23,8 +22,10 @@ from ldm.gfpgan.gfpgan_tools import real_esrgan_upscale from ldm.gfpgan.gfpgan_tools import run_gfpgan from ldm.generate import Generate from ldm.dream.pngwriter import PngWriter, retrieve_metadata +from ldm.dream.args import APP_ID, APP_VERSION, calculate_init_img_hash +from ldm.dream.conditioning import split_weighted_subprompts -from modules.parameters import parameters_to_command, create_cmd_parser +from modules.parameters import parameters_to_command """ @@ -32,12 +33,11 @@ USER CONFIG """ output_dir = "outputs/" # Base output directory for images -#host = 'localhost' # Web & socket.io host -host = '0.0.0.0' # Web & socket.io host +# host = 'localhost' # Web & socket.io host +host = "localhost" # Web & socket.io host port = 9090 # Web & socket.io port -verbose = False # enables copious socket.io logging -additional_allowed_origins = ['http://localhost:9090'] # additional CORS allowed origins +model = "stable-diffusion-1.4" """ END USER CONFIG @@ -50,26 +50,23 @@ SERVER SETUP # fix missing mimetypes on windows due to registry wonkiness -mimetypes.add_type('application/javascript', '.js') -mimetypes.add_type('text/css', '.css') +mimetypes.add_type("application/javascript", ".js") +mimetypes.add_type("text/css", ".css") -app = Flask(__name__, static_url_path='', static_folder='../frontend/dist/') +app = Flask(__name__, static_url_path="", static_folder="../frontend/dist/") -app.config['OUTPUTS_FOLDER'] = "../outputs" +app.config["OUTPUTS_FOLDER"] = "../outputs" -@app.route('/outputs/') +@app.route("/outputs/") def outputs(filename): - return send_from_directory( - app.config['OUTPUTS_FOLDER'], - filename - ) + return send_from_directory(app.config["OUTPUTS_FOLDER"], filename) -@app.route("/", defaults={'path': ''}) +@app.route("/", defaults={"path": ""}) def serve(path): - return send_from_directory(app.static_folder, 'index.html') + return send_from_directory(app.static_folder, "index.html") logger = True if verbose else False @@ -81,12 +78,12 @@ max_http_buffer_size = 10000000 cors_allowed_origins = [f"http://{host}:{port}"] + additional_allowed_origins socketio = SocketIO( - app, - logger=logger, - engineio_logger=engineio_logger, - max_http_buffer_size=max_http_buffer_size, - cors_allowed_origins=cors_allowed_origins, - ) + app, + logger=logger, + engineio_logger=engineio_logger, + max_http_buffer_size=max_http_buffer_size, + cors_allowed_origins=cors_allowed_origins, +) """ @@ -107,29 +104,31 @@ canceled = Event() # reduce logging outputs to error transformers.logging.set_verbosity_error() -logging.getLogger('pytorch_lightning').setLevel(logging.ERROR) +logging.getLogger("pytorch_lightning").setLevel(logging.ERROR) # Initialize and load model -model = Generate() -model.load_model() +generate = Generate(model) +generate.load_model() # location for "finished" images -result_path = os.path.join(output_dir, 'img-samples/') +result_path = os.path.join(output_dir, "img-samples/") # temporary path for intermediates -intermediate_path = os.path.join(result_path, 'intermediates/') +intermediate_path = os.path.join(result_path, "intermediates/") # path for user-uploaded init images and masks -init_image_path = os.path.join(result_path, 'init-images/') -mask_image_path = os.path.join(result_path, 'mask-images/') +init_image_path = os.path.join(result_path, "init-images/") +mask_image_path = os.path.join(result_path, "mask-images/") # txt log -log_path = os.path.join(result_path, 'dream_log.txt') +log_path = os.path.join(result_path, "dream_log.txt") # make all output paths -[os.makedirs(path, exist_ok=True) - for path in [result_path, intermediate_path, init_image_path, mask_image_path]] +[ + os.makedirs(path, exist_ok=True) + for path in [result_path, intermediate_path, init_image_path, mask_image_path] +] """ @@ -142,9 +141,16 @@ SOCKET.IO LISTENERS """ -@socketio.on('requestAllImages') +@socketio.on("requestSystemConfig") +def handle_request_capabilities(): + print(f">> System config requested") + config = get_system_config() + socketio.emit("systemConfig", config) + + +@socketio.on("requestAllImages") def handle_request_all_images(): - print(f'>> All images requested') + print(f">> All images requested") parser = create_cmd_parser() paths = list(filter(os.path.isfile, glob.glob(result_path + "*.png"))) paths.sort(key=lambda x: os.path.getmtime(x)) @@ -152,176 +158,208 @@ def handle_request_all_images(): for path in paths: # image = Image.open(path) all_metadata = retrieve_metadata(path) - if 'Dream' in all_metadata and not all_metadata['sd-metadata']: - metadata = vars(parser.parse_args(shlex.split(all_metadata['Dream']))) + if "Dream" in all_metadata and not all_metadata["sd-metadata"]: + metadata = vars(parser.parse_args(shlex.split(all_metadata["Dream"]))) else: - metadata = all_metadata['sd-metadata'] - image_array.append({'path': path, 'metadata': metadata}) - socketio.emit('galleryImages', {'images': image_array}) + metadata = all_metadata["sd-metadata"] + image_array.append({"url": path, "metadata": metadata}) + socketio.emit("galleryImages", {"images": image_array}) eventlet.sleep(0) -@socketio.on('generateImage') -def handle_generate_image_event(generation_parameters, esrgan_parameters, gfpgan_parameters): - print(f'>> Image generation requested: {generation_parameters}\nESRGAN parameters: {esrgan_parameters}\nGFPGAN parameters: {gfpgan_parameters}') - generate_images( - generation_parameters, - esrgan_parameters, - gfpgan_parameters +@socketio.on("generateImage") +def handle_generate_image_event( + generation_parameters, esrgan_parameters, gfpgan_parameters +): + print( + f">> Image generation requested: {generation_parameters}\nESRGAN parameters: {esrgan_parameters}\nGFPGAN parameters: {gfpgan_parameters}" ) + generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters) -@socketio.on('runESRGAN') +@socketio.on("runESRGAN") def handle_run_esrgan_event(original_image, esrgan_parameters): - print(f'>> ESRGAN upscale requested for "{original_image["url"]}": {esrgan_parameters}') + print( + f'>> ESRGAN upscale requested for "{original_image["url"]}": {esrgan_parameters}' + ) progress = { - 'currentStep': 1, - 'totalSteps': 1, - 'currentIteration': 1, - 'totalIterations': 1, - 'currentStatus': 'Preparing', - 'isProcessing': True, - 'currentStatusHasSteps': False + "currentStep": 1, + "totalSteps": 1, + "currentIteration": 1, + "totalIterations": 1, + "currentStatus": "Preparing", + "isProcessing": True, + "currentStatusHasSteps": False, } - socketio.emit('progressUpdate', progress) + socketio.emit("progressUpdate", progress) eventlet.sleep(0) image = Image.open(original_image["url"]) - seed = original_image['metadata']['seed'] if 'seed' in original_image['metadata'] else 'unknown_seed' + seed = ( + original_image["metadata"]["seed"] + if "seed" in original_image["metadata"] + else "unknown_seed" + ) - progress['currentStatus'] = 'Upscaling' - socketio.emit('progressUpdate', progress) + progress["currentStatus"] = "Upscaling" + socketio.emit("progressUpdate", progress) eventlet.sleep(0) image = real_esrgan_upscale( image=image, - upsampler_scale=esrgan_parameters['upscale'][0], - strength=esrgan_parameters['upscale'][1], - seed=seed + upsampler_scale=esrgan_parameters["upscale"][0], + strength=esrgan_parameters["upscale"][1], + seed=seed, ) - progress['currentStatus'] = 'Saving image' - socketio.emit('progressUpdate', progress) + progress["currentStatus"] = "Saving image" + socketio.emit("progressUpdate", progress) eventlet.sleep(0) - esrgan_parameters['seed'] = seed - path = save_image(image, esrgan_parameters, result_path, postprocessing='esrgan') + esrgan_parameters["seed"] = seed + metadata = parameters_to_post_processed_image_metadata( + parameters=esrgan_parameters, + original_image_path=original_image["url"], + type="esrgan", + ) command = parameters_to_command(esrgan_parameters) + path = save_image(image, command, metadata, result_path, postprocessing="esrgan") + write_log_message(f'[Upscaled] "{original_image["url"]}" > "{path}": {command}') - progress['currentStatus'] = 'Finished' - progress['currentStep'] = 0 - progress['totalSteps'] = 0 - progress['currentIteration'] = 0 - progress['totalIterations'] = 0 - progress['isProcessing'] = False - socketio.emit('progressUpdate', progress) + progress["currentStatus"] = "Finished" + progress["currentStep"] = 0 + progress["totalSteps"] = 0 + progress["currentIteration"] = 0 + progress["totalIterations"] = 0 + progress["isProcessing"] = False + socketio.emit("progressUpdate", progress) eventlet.sleep(0) socketio.emit( - 'esrganResult', {'url': os.path.relpath(path), 'uuid': original_image['uuid'], 'metadata': esrgan_parameters}) + "esrganResult", + { + "url": os.path.relpath(path), + "metadata": metadata, + }, + ) - -@socketio.on('runGFPGAN') +@socketio.on("runGFPGAN") def handle_run_gfpgan_event(original_image, gfpgan_parameters): - print(f'>> GFPGAN face fix requested for "{original_image["url"]}": {gfpgan_parameters}') + print( + f'>> GFPGAN face fix requested for "{original_image["url"]}": {gfpgan_parameters}' + ) progress = { - 'currentStep': 1, - 'totalSteps': 1, - 'currentIteration': 1, - 'totalIterations': 1, - 'currentStatus': 'Preparing', - 'isProcessing': True, - 'currentStatusHasSteps': False + "currentStep": 1, + "totalSteps": 1, + "currentIteration": 1, + "totalIterations": 1, + "currentStatus": "Preparing", + "isProcessing": True, + "currentStatusHasSteps": False, } - socketio.emit('progressUpdate', progress) + socketio.emit("progressUpdate", progress) eventlet.sleep(0) image = Image.open(original_image["url"]) - seed = original_image['metadata']['seed'] if 'seed' in original_image['metadata'] else 'unknown_seed' + seed = ( + original_image["metadata"]["seed"] + if "seed" in original_image["metadata"] + else "unknown_seed" + ) - progress['currentStatus'] = 'Fixing faces' - socketio.emit('progressUpdate', progress) + progress["currentStatus"] = "Fixing faces" + socketio.emit("progressUpdate", progress) eventlet.sleep(0) image = run_gfpgan( image=image, - strength=gfpgan_parameters['gfpgan_strength'], + strength=gfpgan_parameters["gfpgan_strength"], seed=seed, - upsampler_scale=1 + upsampler_scale=1, ) - progress['currentStatus'] = 'Saving image' - socketio.emit('progressUpdate', progress) + progress["currentStatus"] = "Saving image" + socketio.emit("progressUpdate", progress) eventlet.sleep(0) - gfpgan_parameters['seed'] = seed - path = save_image(image, gfpgan_parameters, result_path, postprocessing='gfpgan') + gfpgan_parameters["seed"] = seed + metadata = parameters_to_post_processed_image_metadata( + parameters=gfpgan_parameters, + original_image_path=original_image["url"], + type="gfpgan", + ) command = parameters_to_command(gfpgan_parameters) + path = save_image(image, command, metadata, result_path, postprocessing="gfpgan") + write_log_message(f'[Fixed faces] "{original_image["url"]}" > "{path}": {command}') - progress['currentStatus'] = 'Finished' - progress['currentStep'] = 0 - progress['totalSteps'] = 0 - progress['currentIteration'] = 0 - progress['totalIterations'] = 0 - progress['isProcessing'] = False - socketio.emit('progressUpdate', progress) + progress["currentStatus"] = "Finished" + progress["currentStep"] = 0 + progress["totalSteps"] = 0 + progress["currentIteration"] = 0 + progress["totalIterations"] = 0 + progress["isProcessing"] = False + socketio.emit("progressUpdate", progress) eventlet.sleep(0) socketio.emit( - 'gfpganResult', {'url': os.path.relpath(path), 'uuid': original_image['uuid'], 'metadata': gfpgan_parameters}) + "gfpganResult", + { + "url": os.path.relpath(path), + "metadata": metadata, + }, + ) -@socketio.on('cancel') +@socketio.on("cancel") def handle_cancel(): - print(f'>> Cancel processing requested') + print(f">> Cancel processing requested") canceled.set() - socketio.emit('processingCanceled') + socketio.emit("processingCanceled") # TODO: I think this needs a safety mechanism. -@socketio.on('deleteImage') +@socketio.on("deleteImage") def handle_delete_image(path, uuid): print(f'>> Delete requested "{path}"') send2trash(path) - socketio.emit('imageDeleted', {'url': path, 'uuid': uuid}) + socketio.emit("imageDeleted", {"url": path, "uuid": uuid}) # TODO: I think this needs a safety mechanism. -@socketio.on('uploadInitialImage') +@socketio.on("uploadInitialImage") def handle_upload_initial_image(bytes, name): print(f'>> Init image upload requested "{name}"') uuid = uuid4().hex split = os.path.splitext(name) - name = f'{split[0]}.{uuid}{split[1]}' + name = f"{split[0]}.{uuid}{split[1]}" file_path = os.path.join(init_image_path, name) os.makedirs(os.path.dirname(file_path), exist_ok=True) newFile = open(file_path, "wb") newFile.write(bytes) - socketio.emit('initialImageUploaded', {'url': file_path, 'uuid': ''}) + socketio.emit("initialImageUploaded", {"url": file_path, "uuid": ""}) # TODO: I think this needs a safety mechanism. -@socketio.on('uploadMaskImage') +@socketio.on("uploadMaskImage") def handle_upload_mask_image(bytes, name): print(f'>> Mask image upload requested "{name}"') uuid = uuid4().hex split = os.path.splitext(name) - name = f'{split[0]}.{uuid}{split[1]}' + name = f"{split[0]}.{uuid}{split[1]}" file_path = os.path.join(mask_image_path, name) os.makedirs(os.path.dirname(file_path), exist_ok=True) newFile = open(file_path, "wb") newFile.write(bytes) - socketio.emit('maskImageUploaded', {'url': file_path, 'uuid': ''}) - + socketio.emit("maskImageUploaded", {"url": file_path, "uuid": ""}) """ @@ -329,50 +367,171 @@ END SOCKET.IO LISTENERS """ - """ ADDITIONAL FUNCTIONS """ +def get_system_config(): + return { + "model": "stable diffusion", + "model_id": model, + "model_hash": generate.model_hash, + "app_id": APP_ID, + "app_version": APP_VERSION, + } + + +def parameters_to_post_processed_image_metadata(parameters, original_image_path, type): + # top-level metadata minus `image` or `images` + metadata = get_system_config() + + orig_hash = calculate_init_img_hash(original_image_path) + + image = {"orig_path": original_image_path, "orig_hash": orig_hash} + + if type == "esrgan": + image["type"] = "esrgan" + image["scale"] = parameters["upscale"][0] + image["strength"] = parameters["upscale"][1] + elif type == "gfpgan": + image["type"] = "gfpgan" + image["strength"] = parameters["gfpgan_strength"] + else: + raise TypeError(f"Invalid type: {type}") + + metadata["image"] = image + return metadata + + +def parameters_to_generated_image_metadata(parameters): + # top-level metadata minus `image` or `images` + + metadata = get_system_config() + # remove any image keys not mentioned in RFC #266 + rfc266_img_fields = [ + "type", + "postprocessing", + "sampler", + "prompt", + "seed", + "variations", + "steps", + "cfg_scale", + "step_number", + "width", + "height", + "extra", + "seamless", + ] + + rfc_dict = {} + + for item in parameters.items(): + key, value = item + if key in rfc266_img_fields: + rfc_dict[key] = value + + postprocessing = [] + + # 'postprocessing' is either null or an + if "gfpgan_strength" in parameters: + + postprocessing.append( + {"type": "gfpgan", "strength": float(parameters["gfpgan_strength"])} + ) + + if "upscale" in parameters: + postprocessing.append( + { + "type": "esrgan", + "scale": int(parameters["upscale"][0]), + "strength": float(parameters["upscale"][1]), + } + ) + + rfc_dict["postprocessing"] = postprocessing if len(postprocessing) > 0 else None + + # semantic drift + rfc_dict["sampler"] = parameters["sampler_name"] + + # display weighted subprompts (liable to change) + subprompts = split_weighted_subprompts(parameters["prompt"]) + subprompts = [{"prompt": x[0], "weight": x[1]} for x in subprompts] + rfc_dict["prompt"] = subprompts + + # 'variations' should always exist and be an array, empty or consisting of {'seed': seed, 'weight': weight} pairs + variations = [] + + if "with_variations" in parameters: + variations = [ + {"seed": x[0], "weight": x[1]} for x in parameters["with_variations"] + ] + + rfc_dict["variations"] = variations + + if "init_img" in parameters: + rfc_dict["type"] = "img2img" + rfc_dict["strength"] = parameters["strength"] + rfc_dict["fit"] = parameters["fit"] # TODO: Noncompliant + rfc_dict["orig_hash"] = calculate_init_img_hash(parameters["init_img"]) + rfc_dict["init_image_path"] = parameters["init_img"] # TODO: Noncompliant + rfc_dict["sampler"] = "ddim" # TODO: FIX ME WHEN IMG2IMG SUPPORTS ALL SAMPLERS + if "init_mask" in parameters: + rfc_dict["mask_hash"] = calculate_init_img_hash( + parameters["init_mask"] + ) # TODO: Noncompliant + rfc_dict["mask_image_path"] = parameters["init_mask"] # TODO: Noncompliant + else: + rfc_dict["type"] = "txt2img" + + metadata["image"] = rfc_dict + + return metadata + + def make_unique_init_image_filename(name): uuid = uuid4().hex split = os.path.splitext(name) - name = f'{split[0]}.{uuid}{split[1]}' + name = f"{split[0]}.{uuid}{split[1]}" return name def write_log_message(message, log_path=log_path): """Logs the filename and parameters used to generate or process that image to log file""" - message = f'{message}\n' - with open(log_path, 'a', encoding='utf-8') as file: + message = f"{message}\n" + with open(log_path, "a", encoding="utf-8") as file: file.writelines(message) -def save_image(image, parameters, output_dir, step_index=None, postprocessing=False): - seed = parameters['seed'] if 'seed' in parameters else 'unknown_seed' +def save_image( + image, command, metadata, output_dir, step_index=None, postprocessing=False +): + seed = metadata["seed"] if "seed" in metadata else "unknown_seed" pngwriter = PngWriter(output_dir) prefix = pngwriter.unique_prefix() - filename = f'{prefix}.{seed}' + filename = f"{prefix}.{seed}" if step_index: - filename += f'.{step_index}' + filename += f".{step_index}" if postprocessing: - filename += f'.postprocessed' + filename += f".postprocessed" - filename += '.png' + filename += ".png" - command = parameters_to_command(parameters) - - path = pngwriter.save_image_and_prompt_to_png(image, command, metadata=parameters, name=filename) + path = pngwriter.save_image_and_prompt_to_png( + image=image, dream_prompt=command, metadata=metadata, name=filename + ) return path + def calculate_real_steps(steps, strength, has_init_image): return math.floor(strength * steps) if has_init_image else steps + def generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters): canceled.clear() @@ -385,40 +544,40 @@ def generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters) If the init/mask image doesn't exist in the init_image_path/mask_image_path, make a unique filename for it and copy it there. """ - if ('init_img' in generation_parameters): - filename = os.path.basename(generation_parameters['init_img']) + if "init_img" in generation_parameters: + filename = os.path.basename(generation_parameters["init_img"]) if not os.path.exists(os.path.join(init_image_path, filename)): unique_filename = make_unique_init_image_filename(filename) new_path = os.path.join(init_image_path, unique_filename) - shutil.copy(generation_parameters['init_img'], new_path) - generation_parameters['init_img'] = new_path - if ('init_mask' in generation_parameters): - filename = os.path.basename(generation_parameters['init_mask']) + shutil.copy(generation_parameters["init_img"], new_path) + generation_parameters["init_img"] = new_path + if "init_mask" in generation_parameters: + filename = os.path.basename(generation_parameters["init_mask"]) if not os.path.exists(os.path.join(mask_image_path, filename)): unique_filename = make_unique_init_image_filename(filename) new_path = os.path.join(init_image_path, unique_filename) - shutil.copy(generation_parameters['init_img'], new_path) - generation_parameters['init_mask'] = new_path - - + shutil.copy(generation_parameters["init_img"], new_path) + generation_parameters["init_mask"] = new_path totalSteps = calculate_real_steps( - steps=generation_parameters['steps'], - strength=generation_parameters['strength'] if 'strength' in generation_parameters else None, - has_init_image='init_img' in generation_parameters - ) + steps=generation_parameters["steps"], + strength=generation_parameters["strength"] + if "strength" in generation_parameters + else None, + has_init_image="init_img" in generation_parameters, + ) progress = { - 'currentStep': 1, - 'totalSteps': totalSteps, - 'currentIteration': 1, - 'totalIterations': generation_parameters['iterations'], - 'currentStatus': 'Preparing', - 'isProcessing': True, - 'currentStatusHasSteps': False + "currentStep": 1, + "totalSteps": totalSteps, + "currentIteration": 1, + "totalIterations": generation_parameters["iterations"], + "currentStatus": "Preparing", + "isProcessing": True, + "currentStatusHasSteps": False, } - socketio.emit('progressUpdate', progress) + socketio.emit("progressUpdate", progress) eventlet.sleep(0) def image_progress(sample, step): @@ -429,18 +588,26 @@ def generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters) nonlocal generation_parameters nonlocal progress - progress['currentStep'] = step + 1 - progress['currentStatus'] = 'Generating' - progress['currentStatusHasSteps'] = True + progress["currentStep"] = step + 1 + progress["currentStatus"] = "Generating" + progress["currentStatusHasSteps"] = True - if generation_parameters["progress_images"] and step % 5 == 0 and step < generation_parameters['steps'] - 1: - image = model.sample_to_image(sample) - path = save_image(image, generation_parameters, intermediate_path, step_index) + if ( + generation_parameters["progress_images"] + and step % 5 == 0 + and step < generation_parameters["steps"] - 1 + ): + image = generate.sample_to_image(sample) + path = save_image( + image, generation_parameters, intermediate_path, step_index + ) step_index += 1 - socketio.emit('intermediateResult', { - 'url': os.path.relpath(path), 'metadata': generation_parameters}) - socketio.emit('progressUpdate', progress) + socketio.emit( + "intermediateResult", + {"url": os.path.relpath(path), "metadata": generation_parameters}, + ) + socketio.emit("progressUpdate", progress) eventlet.sleep(0) def image_done(image, seed): @@ -451,79 +618,88 @@ def generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters) step_index = 1 - progress['currentStatus'] = 'Generation complete' - socketio.emit('progressUpdate', progress) + progress["currentStatus"] = "Generation complete" + socketio.emit("progressUpdate", progress) eventlet.sleep(0) all_parameters = generation_parameters postprocessing = False if esrgan_parameters: - progress['currentStatus'] = 'Upscaling' - progress['currentStatusHasSteps'] = False - socketio.emit('progressUpdate', progress) + progress["currentStatus"] = "Upscaling" + progress["currentStatusHasSteps"] = False + socketio.emit("progressUpdate", progress) eventlet.sleep(0) image = real_esrgan_upscale( image=image, - strength=esrgan_parameters['strength'], - upsampler_scale=esrgan_parameters['level'], - seed=seed + strength=esrgan_parameters["strength"], + upsampler_scale=esrgan_parameters["level"], + seed=seed, ) postprocessing = True - all_parameters["upscale"] = [esrgan_parameters['level'], esrgan_parameters['strength']] + all_parameters["upscale"] = [ + esrgan_parameters["level"], + esrgan_parameters["strength"], + ] if gfpgan_parameters: - progress['currentStatus'] = 'Fixing faces' - progress['currentStatusHasSteps'] = False - socketio.emit('progressUpdate', progress) + progress["currentStatus"] = "Fixing faces" + progress["currentStatusHasSteps"] = False + socketio.emit("progressUpdate", progress) eventlet.sleep(0) image = run_gfpgan( image=image, - strength=gfpgan_parameters['strength'], + strength=gfpgan_parameters["strength"], seed=seed, upsampler_scale=1, ) postprocessing = True - all_parameters["gfpgan_strength"] = gfpgan_parameters['strength'] + all_parameters["gfpgan_strength"] = gfpgan_parameters["strength"] - all_parameters['seed'] = seed - progress['currentStatus'] = 'Saving image' - socketio.emit('progressUpdate', progress) + all_parameters["seed"] = seed + progress["currentStatus"] = "Saving image" + socketio.emit("progressUpdate", progress) eventlet.sleep(0) - path = save_image(image, all_parameters, result_path, postprocessing=postprocessing) + metadata = parameters_to_generated_image_metadata(all_parameters) command = parameters_to_command(all_parameters) - print(f'Image generated: "{path}"') + path = save_image( + image, command, metadata, result_path, postprocessing=postprocessing + ) + + print(f'>> Image generated: "{path}"') write_log_message(f'[Generated] "{path}": {command}') - if (progress['totalIterations'] > progress['currentIteration']): - progress['currentStep'] = 1 - progress['currentIteration'] +=1 - progress['currentStatus'] = 'Iteration finished' - progress['currentStatusHasSteps'] = False + if progress["totalIterations"] > progress["currentIteration"]: + progress["currentStep"] = 1 + progress["currentIteration"] += 1 + progress["currentStatus"] = "Iteration finished" + progress["currentStatusHasSteps"] = False else: - progress['currentStep'] = 0 - progress['totalSteps'] = 0 - progress['currentIteration'] = 0 - progress['totalIterations'] = 0 - progress['currentStatus'] = 'Finished' - progress['isProcessing'] = False + progress["currentStep"] = 0 + progress["totalSteps"] = 0 + progress["currentIteration"] = 0 + progress["totalIterations"] = 0 + progress["currentStatus"] = "Finished" + progress["isProcessing"] = False - socketio.emit('progressUpdate', progress) + socketio.emit("progressUpdate", progress) eventlet.sleep(0) socketio.emit( - 'generationResult', {'url': os.path.relpath(path), 'metadata': all_parameters}) + "generationResult", + {"url": os.path.relpath(path), "metadata": metadata}, + ) eventlet.sleep(0) try: - model.prompt2image( + generate.prompt2image( **generation_parameters, step_callback=image_progress, - image_callback=image_done + image_callback=image_done, ) except KeyboardInterrupt: @@ -531,7 +707,7 @@ def generate_images(generation_parameters, esrgan_parameters, gfpgan_parameters) except CanceledException: pass except Exception as e: - socketio.emit('error', {'message': (str(e))}) + socketio.emit("error", {"message": (str(e))}) print("\n") traceback.print_exc() print("\n") @@ -542,6 +718,6 @@ END ADDITIONAL FUNCTIONS """ -if __name__ == '__main__': - print(f'Starting server at http://{host}:{port}') +if __name__ == "__main__": + print(f">> Starting server at http://{host}:{port}") socketio.run(app, host=host, port=port) diff --git a/frontend/dist/assets/index.727a397b.js b/frontend/dist/assets/index.727a397b.js new file mode 100644 index 0000000000..cc3b6e6fca --- /dev/null +++ b/frontend/dist/assets/index.727a397b.js @@ -0,0 +1,694 @@ +function p$(e,t){for(var r=0;ri[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))i(o);new MutationObserver(o=>{for(const c of o)if(c.type==="childList")for(const u of c.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&i(u)}).observe(document,{childList:!0,subtree:!0});function r(o){const c={};return o.integrity&&(c.integrity=o.integrity),o.referrerpolicy&&(c.referrerPolicy=o.referrerpolicy),o.crossorigin==="use-credentials"?c.credentials="include":o.crossorigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function i(o){if(o.ep)return;o.ep=!0;const c=r(o);fetch(o.href,c)}})();var hc=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function h$(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var D={exports:{}},LR={exports:{}};/** + * @license React + * react.development.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */(function(e,t){(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var r="18.2.0",i=Symbol.for("react.element"),o=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),u=Symbol.for("react.strict_mode"),h=Symbol.for("react.profiler"),m=Symbol.for("react.provider"),v=Symbol.for("react.context"),S=Symbol.for("react.forward_ref"),x=Symbol.for("react.suspense"),w=Symbol.for("react.suspense_list"),_=Symbol.for("react.memo"),R=Symbol.for("react.lazy"),k=Symbol.for("react.offscreen"),P=Symbol.iterator,U="@@iterator";function L(b){if(b===null||typeof b!="object")return null;var A=P&&b[P]||b[U];return typeof A=="function"?A:null}var F={current:null},B={transition:null},$={current:null,isBatchingLegacy:!1,didScheduleLegacyUpdate:!1},K={current:null},Z={},fe=null;function me(b){fe=b}Z.setExtraStackFrame=function(b){fe=b},Z.getCurrentStack=null,Z.getStackAddendum=function(){var b="";fe&&(b+=fe);var A=Z.getCurrentStack;return A&&(b+=A()||""),b};var se=!1,Se=!1,Ke=!1,ae=!1,ce=!1,ge={ReactCurrentDispatcher:F,ReactCurrentBatchConfig:B,ReactCurrentOwner:K};ge.ReactDebugCurrentFrame=Z,ge.ReactCurrentActQueue=$;function _e(b){{for(var A=arguments.length,V=new Array(A>1?A-1:0),G=1;G1?A-1:0),G=1;G1){for(var At=Array(yt),ht=0;ht1){for(var zt=Array(ht),_t=0;_t is not supported and will be removed in a future major release. Did you mean to render instead?")),A.Provider},set:function(Ce){A.Provider=Ce}},_currentValue:{get:function(){return A._currentValue},set:function(Ce){A._currentValue=Ce}},_currentValue2:{get:function(){return A._currentValue2},set:function(Ce){A._currentValue2=Ce}},_threadCount:{get:function(){return A._threadCount},set:function(Ce){A._threadCount=Ce}},Consumer:{get:function(){return V||(V=!0,oe("Rendering is not supported and will be removed in a future major release. Did you mean to render instead?")),A.Consumer}},displayName:{get:function(){return A.displayName},set:function(Ce){te||(_e("Setting `displayName` on Context.Consumer has no effect. You should set it directly on the context with Context.displayName = '%s'.",Ce),te=!0)}}}),A.Consumer=Fe}return A._currentRenderer=null,A._currentRenderer2=null,A}var pr=-1,Ma=0,Ii=1,Pa=2;function q(b){if(b._status===pr){var A=b._result,V=A();if(V.then(function(Fe){if(b._status===Ma||b._status===pr){var Ce=b;Ce._status=Ii,Ce._result=Fe}},function(Fe){if(b._status===Ma||b._status===pr){var Ce=b;Ce._status=Pa,Ce._result=Fe}}),b._status===pr){var G=b;G._status=Ma,G._result=V}}if(b._status===Ii){var te=b._result;return te===void 0&&oe(`lazy: Expected the result of a dynamic import() call. Instead received: %s + +Your code should look like: + const MyComponent = lazy(() => import('./MyComponent')) + +Did you accidentally put curly braces around the import?`,te),"default"in te||oe(`lazy: Expected the result of a dynamic import() call. Instead received: %s + +Your code should look like: + const MyComponent = lazy(() => import('./MyComponent'))`,te),te.default}else throw b._result}function Ue(b){var A={_status:pr,_result:b},V={$$typeof:R,_payload:A,_init:q};{var G,te;Object.defineProperties(V,{defaultProps:{configurable:!0,get:function(){return G},set:function(Fe){oe("React.lazy(...): It is not supported to assign `defaultProps` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),G=Fe,Object.defineProperty(V,"defaultProps",{enumerable:!0})}},propTypes:{configurable:!0,get:function(){return te},set:function(Fe){oe("React.lazy(...): It is not supported to assign `propTypes` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),te=Fe,Object.defineProperty(V,"propTypes",{enumerable:!0})}}})}return V}function qe(b){b!=null&&b.$$typeof===_?oe("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."):typeof b!="function"?oe("forwardRef requires a render function but was given %s.",b===null?"null":typeof b):b.length!==0&&b.length!==2&&oe("forwardRef render functions accept exactly two parameters: props and ref. %s",b.length===1?"Did you forget to use the ref parameter?":"Any additional parameter will be undefined."),b!=null&&(b.defaultProps!=null||b.propTypes!=null)&&oe("forwardRef render functions do not support propTypes or defaultProps. Did you accidentally pass a React component?");var A={$$typeof:S,render:b};{var V;Object.defineProperty(A,"displayName",{enumerable:!1,configurable:!0,get:function(){return V},set:function(G){V=G,!b.name&&!b.displayName&&(b.displayName=G)}})}return A}var St;St=Symbol.for("react.module.reference");function an(b){return!!(typeof b=="string"||typeof b=="function"||b===c||b===h||ce||b===u||b===x||b===w||ae||b===k||se||Se||Ke||typeof b=="object"&&b!==null&&(b.$$typeof===R||b.$$typeof===_||b.$$typeof===m||b.$$typeof===v||b.$$typeof===S||b.$$typeof===St||b.getModuleId!==void 0))}function Sn(b,A){an(b)||oe("memo: The first argument must be a component. Instead received: %s",b===null?"null":typeof b);var V={$$typeof:_,type:b,compare:A===void 0?null:A};{var G;Object.defineProperty(V,"displayName",{enumerable:!1,configurable:!0,get:function(){return G},set:function(te){G=te,!b.name&&!b.displayName&&(b.displayName=te)}})}return V}function tt(){var b=F.current;return b===null&&oe(`Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: +1. You might have mismatching versions of React and the renderer (such as React DOM) +2. You might be breaking the Rules of Hooks +3. You might have more than one copy of React in the same app +See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.`),b}function Ht(b){var A=tt();if(b._context!==void 0){var V=b._context;V.Consumer===b?oe("Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be removed in a future major release. Did you mean to call useContext(Context) instead?"):V.Provider===b&&oe("Calling useContext(Context.Provider) is not supported. Did you mean to call useContext(Context) instead?")}return A.useContext(b)}function jn(b){var A=tt();return A.useState(b)}function zn(b,A,V){var G=tt();return G.useReducer(b,A,V)}function sn(b){var A=tt();return A.useRef(b)}function zr(b,A){var V=tt();return V.useEffect(b,A)}function hi(b,A){var V=tt();return V.useInsertionEffect(b,A)}function Do(b,A){var V=tt();return V.useLayoutEffect(b,A)}function va(b,A){var V=tt();return V.useCallback(b,A)}function io(b,A){var V=tt();return V.useMemo(b,A)}function wu(b,A,V){var G=tt();return G.useImperativeHandle(b,A,V)}function mi(b,A){{var V=tt();return V.useDebugValue(b,A)}}function $s(){var b=tt();return b.useTransition()}function Fi(b){var A=tt();return A.useDeferredValue(b)}function Jt(){var b=tt();return b.useId()}function zi(b,A,V){var G=tt();return G.useSyncExternalStore(b,A,V)}var ga=0,Mo,os,Po,ss,ls,Lo,Io;function us(){}us.__reactDisabledLog=!0;function Vs(){{if(ga===0){Mo=console.log,os=console.info,Po=console.warn,ss=console.error,ls=console.group,Lo=console.groupCollapsed,Io=console.groupEnd;var b={configurable:!0,enumerable:!0,value:us,writable:!0};Object.defineProperties(console,{info:b,log:b,warn:b,error:b,group:b,groupCollapsed:b,groupEnd:b})}ga++}}function Hs(){{if(ga--,ga===0){var b={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:Le({},b,{value:Mo}),info:Le({},b,{value:os}),warn:Le({},b,{value:Po}),error:Le({},b,{value:ss}),group:Le({},b,{value:ls}),groupCollapsed:Le({},b,{value:Lo}),groupEnd:Le({},b,{value:Io})})}ga<0&&oe("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var vi=ge.ReactCurrentDispatcher,Pr;function La(b,A,V){{if(Pr===void 0)try{throw Error()}catch(te){var G=te.stack.trim().match(/\n( *(at )?)/);Pr=G&&G[1]||""}return` +`+Pr+b}}var ya=!1,Ia;{var cs=typeof WeakMap=="function"?WeakMap:Map;Ia=new cs}function Fo(b,A){if(!b||ya)return"";{var V=Ia.get(b);if(V!==void 0)return V}var G;ya=!0;var te=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var Fe;Fe=vi.current,vi.current=null,Vs();try{if(A){var Ce=function(){throw Error()};if(Object.defineProperty(Ce.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(Ce,[])}catch(Pt){G=Pt}Reflect.construct(b,[],Ce)}else{try{Ce.call()}catch(Pt){G=Pt}b.call(Ce.prototype)}}else{try{throw Error()}catch(Pt){G=Pt}b()}}catch(Pt){if(Pt&&G&&typeof Pt.stack=="string"){for(var $e=Pt.stack.split(` +`),it=G.stack.split(` +`),yt=$e.length-1,At=it.length-1;yt>=1&&At>=0&&$e[yt]!==it[At];)At--;for(;yt>=1&&At>=0;yt--,At--)if($e[yt]!==it[At]){if(yt!==1||At!==1)do if(yt--,At--,At<0||$e[yt]!==it[At]){var ht=` +`+$e[yt].replace(" at new "," at ");return b.displayName&&ht.includes("")&&(ht=ht.replace("",b.displayName)),typeof b=="function"&&Ia.set(b,ht),ht}while(yt>=1&&At>=0);break}}}finally{ya=!1,vi.current=Fe,Hs(),Error.prepareStackTrace=te}var zt=b?b.displayName||b.name:"",_t=zt?La(zt):"";return typeof b=="function"&&Ia.set(b,_t),_t}function fs(b,A,V){return Fo(b,!1)}function Dl(b){var A=b.prototype;return!!(A&&A.isReactComponent)}function ba(b,A,V){if(b==null)return"";if(typeof b=="function")return Fo(b,Dl(b));if(typeof b=="string")return La(b);switch(b){case x:return La("Suspense");case w:return La("SuspenseList")}if(typeof b=="object")switch(b.$$typeof){case S:return fs(b.render);case _:return ba(b.type,A,V);case R:{var G=b,te=G._payload,Fe=G._init;try{return ba(Fe(te),A,V)}catch{}}}return""}var zo={},Fa=ge.ReactDebugCurrentFrame;function gi(b){if(b){var A=b._owner,V=ba(b.type,b._source,A?A.type:null);Fa.setExtraStackFrame(V)}else Fa.setExtraStackFrame(null)}function Ws(b,A,V,G,te){{var Fe=Function.call.bind(hn);for(var Ce in b)if(Fe(b,Ce)){var $e=void 0;try{if(typeof b[Ce]!="function"){var it=Error((G||"React class")+": "+V+" type `"+Ce+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof b[Ce]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw it.name="Invariant Violation",it}$e=b[Ce](A,Ce,G,V,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(yt){$e=yt}$e&&!($e instanceof Error)&&(gi(te),oe("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",G||"React class",V,Ce,typeof $e),gi(null)),$e instanceof Error&&!($e.message in zo)&&(zo[$e.message]=!0,gi(te),oe("Failed %s type: %s",V,$e.message),gi(null))}}}function ln(b){if(b){var A=b._owner,V=ba(b.type,b._source,A?A.type:null);me(V)}else me(null)}var yi;yi=!1;function Bo(){if(K.current){var b=jt(K.current.type);if(b)return` + +Check the render method of \``+b+"`."}return""}function Ft(b){if(b!==void 0){var A=b.fileName.replace(/^.*[\\\/]/,""),V=b.lineNumber;return` + +Check your code at `+A+":"+V+"."}return""}function Gs(b){return b!=null?Ft(b.__source):""}var xr={};function Bi(b){var A=Bo();if(!A){var V=typeof b=="string"?b:b.displayName||b.name;V&&(A=` + +Check the top-level render call using <`+V+">.")}return A}function Ha(b,A){if(!(!b._store||b._store.validated||b.key!=null)){b._store.validated=!0;var V=Bi(A);if(!xr[V]){xr[V]=!0;var G="";b&&b._owner&&b._owner!==K.current&&(G=" It was passed a child from "+jt(b._owner.type)+"."),ln(b),oe('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',V,G),ln(null)}}}function oo(b,A){if(typeof b=="object"){if(Kt(b))for(var V=0;V",te=" Did you accidentally export a JSX literal instead of a component?"):Ce=typeof b,oe("React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",Ce,te)}var $e=rt.apply(this,arguments);if($e==null)return $e;if(G)for(var it=2;it10&&_e("Detected a large number of updates inside startTransition. If this is due to a subscription please re-write it to use React provided hooks. Otherwise concurrent mode guarantees are off the table."),G._updatedFibers.clear()}}}var so=!1,bi=null;function Ys(b){if(bi===null)try{var A=("require"+Math.random()).slice(0,7),V=e&&e[A];bi=V.call(e,"timers").setImmediate}catch{bi=function(te){so===!1&&(so=!0,typeof MessageChannel>"u"&&oe("This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning."));var Fe=new MessageChannel;Fe.port1.onmessage=te,Fe.port2.postMessage(void 0)}}return bi(b)}var vn=0,In=!1;function Ml(b){{var A=vn;vn++,$.current===null&&($.current=[]);var V=$.isBatchingLegacy,G;try{if($.isBatchingLegacy=!0,G=b(),!V&&$.didScheduleLegacyUpdate){var te=$.current;te!==null&&($.didScheduleLegacyUpdate=!1,de(te))}}catch(zt){throw za(A),zt}finally{$.isBatchingLegacy=V}if(G!==null&&typeof G=="object"&&typeof G.then=="function"){var Fe=G,Ce=!1,$e={then:function(zt,_t){Ce=!0,Fe.then(function(Pt){za(A),vn===0?W(Pt,zt,_t):zt(Pt)},function(Pt){za(A),_t(Pt)})}};return!In&&typeof Promise<"u"&&Promise.resolve().then(function(){}).then(function(){Ce||(In=!0,oe("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"))}),$e}else{var it=G;if(za(A),vn===0){var yt=$.current;yt!==null&&(de(yt),$.current=null);var At={then:function(zt,_t){$.current===null?($.current=[],W(it,zt,_t)):zt(it)}};return At}else{var ht={then:function(zt,_t){zt(it)}};return ht}}}}function za(b){b!==vn-1&&oe("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. "),vn=b}function W(b,A,V){{var G=$.current;if(G!==null)try{de(G),Ys(function(){G.length===0?($.current=null,A(b)):W(b,A,V)})}catch(te){V(te)}else A(b)}}var Q=!1;function de(b){if(!Q){Q=!0;var A=0;try{for(;A0;){var Qt=mn-1>>>1,Nn=je[Qt];if(v(Nn,rt)>0)je[Qt]=rt,je[mn]=Nn,mn=Qt;else return}}function m(je,rt,wt){for(var mn=wt,Qt=je.length,Nn=Qt>>>1;mnwt&&(!je||lr()));){var mn=ae.callback;if(typeof mn=="function"){ae.callback=null,ce=ae.priorityLevel;var Qt=ae.expirationTime<=wt,Nn=mn(Qt);wt=e.unstable_now(),typeof Nn=="function"?ae.callback=Nn:ae===c(se)&&u(se),we(wt)}else u(se);ae=c(se)}if(ae!==null)return!0;var Ln=c(Se);return Ln!==null&&Rt(Le,Ln.startTime-wt),!1}function st(je,rt){switch(je){case S:case x:case w:case _:case R:break;default:je=w}var wt=ce;ce=je;try{return rt()}finally{ce=wt}}function vt(je){var rt;switch(ce){case S:case x:case w:rt=w;break;default:rt=ce;break}var wt=ce;ce=rt;try{return je()}finally{ce=wt}}function qt(je){var rt=ce;return function(){var wt=ce;ce=rt;try{return je.apply(this,arguments)}finally{ce=wt}}}function Qe(je,rt,wt){var mn=e.unstable_now(),Qt;if(typeof wt=="object"&&wt!==null){var Nn=wt.delay;typeof Nn=="number"&&Nn>0?Qt=mn+Nn:Qt=mn}else Qt=mn;var Ln;switch(je){case S:Ln=$;break;case x:Ln=K;break;case R:Ln=me;break;case _:Ln=fe;break;case w:default:Ln=Z;break}var kr=Qt+Ln,kn={id:Ke++,callback:rt,priorityLevel:je,startTime:Qt,expirationTime:kr,sortIndex:-1};return Qt>mn?(kn.sortIndex=Qt,o(Se,kn),c(se)===null&&kn===c(Se)&&(oe?Oe():oe=!0,Rt(Le,Qt-mn))):(kn.sortIndex=kr,o(se,kn),!_e&&!ge&&(_e=!0,tn(Ie))),kn}function gt(){}function Tt(){!_e&&!ge&&(_e=!0,tn(Ie))}function Ut(){return c(se)}function We(je){je.callback=null}function Kt(){return ce}var be=!1,It=null,Xt=-1,Ct=i,sr=-1;function lr(){var je=e.unstable_now()-sr;return!(je125){console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported");return}je>0?Ct=Math.floor(1e3/je):Ct=i}var Pn=function(){if(It!==null){var je=e.unstable_now();sr=je;var rt=!0,wt=!0;try{wt=It(rt,je)}finally{wt?gn():(be=!1,It=null)}}else be=!1},gn;if(typeof pe=="function")gn=function(){pe(Pn)};else if(typeof MessageChannel<"u"){var Ve=new MessageChannel,Xe=Ve.port2;Ve.port1.onmessage=Pn,gn=function(){Xe.postMessage(null)}}else gn=function(){xe(Pn,0)};function tn(je){It=je,be||(be=!0,gn())}function Rt(je,rt){Xt=xe(function(){je(e.unstable_now())},rt)}function Oe(){Te(Xt),Xt=-1}var Vt=jt,_n=null;e.unstable_IdlePriority=R,e.unstable_ImmediatePriority=S,e.unstable_LowPriority=_,e.unstable_NormalPriority=w,e.unstable_Profiling=_n,e.unstable_UserBlockingPriority=x,e.unstable_cancelCallback=We,e.unstable_continueExecution=Tt,e.unstable_forceFrameRate=hn,e.unstable_getCurrentPriorityLevel=Kt,e.unstable_getFirstCallbackNode=Ut,e.unstable_next=vt,e.unstable_pauseExecution=gt,e.unstable_requestPaint=Vt,e.unstable_runWithPriority=st,e.unstable_scheduleCallback=Qe,e.unstable_shouldYield=lr,e.unstable_wrapCallback=qt,typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error)})()})(AL);(function(e){e.exports=AL})(RL);/** + * @license React + * react-dom.development.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var e=D.exports,t=RL.exports,r=e.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,i=!1;function o(n){i=n}function c(n){if(!i){for(var a=arguments.length,s=new Array(a>1?a-1:0),f=1;f1?a-1:0),f=1;f2&&(n[0]==="o"||n[0]==="O")&&(n[1]==="n"||n[1]==="N")}function kr(n,a,s,f){if(s!==null&&s.type===Ve)return!1;switch(typeof a){case"function":case"symbol":return!0;case"boolean":{if(f)return!1;if(s!==null)return!s.acceptsBooleans;var p=n.toLowerCase().slice(0,5);return p!=="data-"&&p!=="aria-"}default:return!1}}function kn(n,a,s,f){if(a===null||typeof a>"u"||kr(n,a,s,f))return!0;if(f)return!1;if(s!==null)switch(s.type){case Rt:return!a;case Oe:return a===!1;case Vt:return isNaN(a);case _n:return isNaN(a)||a<1}return!1}function ha(n){return En.hasOwnProperty(n)?En[n]:null}function Un(n,a,s,f,p,y,C){this.acceptsBooleans=a===tn||a===Rt||a===Oe,this.attributeName=f,this.attributeNamespace=p,this.mustUseProperty=s,this.propertyName=n,this.type=a,this.sanitizeURL=y,this.removeEmptyString=C}var En={},ma=["children","dangerouslySetInnerHTML","defaultValue","defaultChecked","innerHTML","suppressContentEditableWarning","suppressHydrationWarning","style"];ma.forEach(function(n){En[n]=new Un(n,Ve,!1,n,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(n){var a=n[0],s=n[1];En[a]=new Un(a,Xe,!1,s,null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(n){En[n]=new Un(n,tn,!1,n.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(n){En[n]=new Un(n,tn,!1,n,null,!1,!1)}),["allowFullScreen","async","autoFocus","autoPlay","controls","default","defer","disabled","disablePictureInPicture","disableRemotePlayback","formNoValidate","hidden","loop","noModule","noValidate","open","playsInline","readOnly","required","reversed","scoped","seamless","itemScope"].forEach(function(n){En[n]=new Un(n,Rt,!1,n.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(n){En[n]=new Un(n,Rt,!0,n,null,!1,!1)}),["capture","download"].forEach(function(n){En[n]=new Un(n,Oe,!1,n,null,!1,!1)}),["cols","rows","size","span"].forEach(function(n){En[n]=new Un(n,_n,!1,n,null,!1,!1)}),["rowSpan","start"].forEach(function(n){En[n]=new Un(n,Vt,!1,n.toLowerCase(),null,!1,!1)});var Sr=/[\-\:]([a-z])/g,Ao=function(n){return n[1].toUpperCase()};["accent-height","alignment-baseline","arabic-form","baseline-shift","cap-height","clip-path","clip-rule","color-interpolation","color-interpolation-filters","color-profile","color-rendering","dominant-baseline","enable-background","fill-opacity","fill-rule","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","glyph-name","glyph-orientation-horizontal","glyph-orientation-vertical","horiz-adv-x","horiz-origin-x","image-rendering","letter-spacing","lighting-color","marker-end","marker-mid","marker-start","overline-position","overline-thickness","paint-order","panose-1","pointer-events","rendering-intent","shape-rendering","stop-color","stop-opacity","strikethrough-position","strikethrough-thickness","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-anchor","text-decoration","text-rendering","underline-position","underline-thickness","unicode-bidi","unicode-range","units-per-em","v-alphabetic","v-hanging","v-ideographic","v-mathematical","vector-effect","vert-adv-y","vert-origin-x","vert-origin-y","word-spacing","writing-mode","xmlns:xlink","x-height"].forEach(function(n){var a=n.replace(Sr,Ao);En[a]=new Un(a,Xe,!1,n,null,!1,!1)}),["xlink:actuate","xlink:arcrole","xlink:role","xlink:show","xlink:title","xlink:type"].forEach(function(n){var a=n.replace(Sr,Ao);En[a]=new Un(a,Xe,!1,n,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(n){var a=n.replace(Sr,Ao);En[a]=new Un(a,Xe,!1,n,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(n){En[n]=new Un(n,Xe,!1,n.toLowerCase(),null,!1,!1)});var as="xlinkHref";En[as]=new Un("xlinkHref",Xe,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(n){En[n]=new Un(n,Xe,!1,n.toLowerCase(),null,!0,!0)});var is=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i,ko=!1;function Oo(n){!ko&&is.test(n)&&(ko=!0,u("A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead. React was passed %s.",JSON.stringify(n)))}function pr(n,a,s,f){if(f.mustUseProperty){var p=f.propertyName;return n[p]}else{sr(s,a),f.sanitizeURL&&Oo(""+s);var y=f.attributeName,C=null;if(f.type===Oe){if(n.hasAttribute(y)){var T=n.getAttribute(y);return T===""?!0:kn(a,s,f,!1)?T:T===""+s?s:T}}else if(n.hasAttribute(y)){if(kn(a,s,f,!1))return n.getAttribute(y);if(f.type===Rt)return s;C=n.getAttribute(y)}return kn(a,s,f,!1)?C===null?s:C:C===""+s?s:C}}function Ma(n,a,s,f){{if(!Nn(a))return;if(!n.hasAttribute(a))return s===void 0?void 0:null;var p=n.getAttribute(a);return sr(s,a),p===""+s?s:p}}function Ii(n,a,s,f){var p=ha(a);if(!Ln(a,p,f)){if(kn(a,s,p,f)&&(s=null),f||p===null){if(Nn(a)){var y=a;s===null?n.removeAttribute(y):(sr(s,a),n.setAttribute(y,""+s))}return}var C=p.mustUseProperty;if(C){var T=p.propertyName;if(s===null){var O=p.type;n[T]=O===Rt?!1:""}else n[T]=s;return}var z=p.attributeName,H=p.attributeNamespace;if(s===null)n.removeAttribute(z);else{var ee=p.type,J;ee===Rt||ee===Oe&&s===!0?J="":(sr(s,z),J=""+s,p.sanitizeURL&&Oo(J.toString())),H?n.setAttributeNS(H,z,J):n.setAttribute(z,J)}}}var Pa=Symbol.for("react.element"),q=Symbol.for("react.portal"),Ue=Symbol.for("react.fragment"),qe=Symbol.for("react.strict_mode"),St=Symbol.for("react.profiler"),an=Symbol.for("react.provider"),Sn=Symbol.for("react.context"),tt=Symbol.for("react.forward_ref"),Ht=Symbol.for("react.suspense"),jn=Symbol.for("react.suspense_list"),zn=Symbol.for("react.memo"),sn=Symbol.for("react.lazy"),zr=Symbol.for("react.scope"),hi=Symbol.for("react.debug_trace_mode"),Do=Symbol.for("react.offscreen"),va=Symbol.for("react.legacy_hidden"),io=Symbol.for("react.cache"),wu=Symbol.for("react.tracing_marker"),mi=Symbol.iterator,$s="@@iterator";function Fi(n){if(n===null||typeof n!="object")return null;var a=mi&&n[mi]||n[$s];return typeof a=="function"?a:null}var Jt=Object.assign,zi=0,ga,Mo,os,Po,ss,ls,Lo;function Io(){}Io.__reactDisabledLog=!0;function us(){{if(zi===0){ga=console.log,Mo=console.info,os=console.warn,Po=console.error,ss=console.group,ls=console.groupCollapsed,Lo=console.groupEnd;var n={configurable:!0,enumerable:!0,value:Io,writable:!0};Object.defineProperties(console,{info:n,log:n,warn:n,error:n,group:n,groupCollapsed:n,groupEnd:n})}zi++}}function Vs(){{if(zi--,zi===0){var n={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:Jt({},n,{value:ga}),info:Jt({},n,{value:Mo}),warn:Jt({},n,{value:os}),error:Jt({},n,{value:Po}),group:Jt({},n,{value:ss}),groupCollapsed:Jt({},n,{value:ls}),groupEnd:Jt({},n,{value:Lo})})}zi<0&&u("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var Hs=r.ReactCurrentDispatcher,vi;function Pr(n,a,s){{if(vi===void 0)try{throw Error()}catch(p){var f=p.stack.trim().match(/\n( *(at )?)/);vi=f&&f[1]||""}return` +`+vi+n}}var La=!1,ya;{var Ia=typeof WeakMap=="function"?WeakMap:Map;ya=new Ia}function cs(n,a){if(!n||La)return"";{var s=ya.get(n);if(s!==void 0)return s}var f;La=!0;var p=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var y;y=Hs.current,Hs.current=null,us();try{if(a){var C=function(){throw Error()};if(Object.defineProperty(C.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(C,[])}catch(ve){f=ve}Reflect.construct(n,[],C)}else{try{C.call()}catch(ve){f=ve}n.call(C.prototype)}}else{try{throw Error()}catch(ve){f=ve}n()}}catch(ve){if(ve&&f&&typeof ve.stack=="string"){for(var T=ve.stack.split(` +`),O=f.stack.split(` +`),z=T.length-1,H=O.length-1;z>=1&&H>=0&&T[z]!==O[H];)H--;for(;z>=1&&H>=0;z--,H--)if(T[z]!==O[H]){if(z!==1||H!==1)do if(z--,H--,H<0||T[z]!==O[H]){var ee=` +`+T[z].replace(" at new "," at ");return n.displayName&&ee.includes("")&&(ee=ee.replace("",n.displayName)),typeof n=="function"&&ya.set(n,ee),ee}while(z>=1&&H>=0);break}}}finally{La=!1,Hs.current=y,Vs(),Error.prepareStackTrace=p}var J=n?n.displayName||n.name:"",he=J?Pr(J):"";return typeof n=="function"&&ya.set(n,he),he}function Fo(n,a,s){return cs(n,!0)}function fs(n,a,s){return cs(n,!1)}function Dl(n){var a=n.prototype;return!!(a&&a.isReactComponent)}function ba(n,a,s){if(n==null)return"";if(typeof n=="function")return cs(n,Dl(n));if(typeof n=="string")return Pr(n);switch(n){case Ht:return Pr("Suspense");case jn:return Pr("SuspenseList")}if(typeof n=="object")switch(n.$$typeof){case tt:return fs(n.render);case zn:return ba(n.type,a,s);case sn:{var f=n,p=f._payload,y=f._init;try{return ba(y(p),a,s)}catch{}}}return""}function zo(n){switch(n._debugOwner&&n._debugOwner.type,n._debugSource,n.tag){case _:return Pr(n.type);case fe:return Pr("Lazy");case $:return Pr("Suspense");case Se:return Pr("SuspenseList");case m:case S:case Z:return fs(n.type);case F:return fs(n.type.render);case v:return Fo(n.type);default:return""}}function Fa(n){try{var a="",s=n;do a+=zo(s),s=s.return;while(s);return a}catch(f){return` +Error generating stack: `+f.message+` +`+f.stack}}function gi(n,a,s){var f=n.displayName;if(f)return f;var p=a.displayName||a.name||"";return p!==""?s+"("+p+")":s}function Ws(n){return n.displayName||"Context"}function ln(n){if(n==null)return null;if(typeof n.tag=="number"&&u("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),typeof n=="function")return n.displayName||n.name||null;if(typeof n=="string")return n;switch(n){case Ue:return"Fragment";case q:return"Portal";case St:return"Profiler";case qe:return"StrictMode";case Ht:return"Suspense";case jn:return"SuspenseList"}if(typeof n=="object")switch(n.$$typeof){case Sn:var a=n;return Ws(a)+".Consumer";case an:var s=n;return Ws(s._context)+".Provider";case tt:return gi(n,n.render,"ForwardRef");case zn:var f=n.displayName||null;return f!==null?f:ln(n.type)||"Memo";case sn:{var p=n,y=p._payload,C=p._init;try{return ln(C(y))}catch{return null}}}return null}function yi(n,a,s){var f=a.displayName||a.name||"";return n.displayName||(f!==""?s+"("+f+")":s)}function Bo(n){return n.displayName||"Context"}function Ft(n){var a=n.tag,s=n.type;switch(a){case ge:return"Cache";case U:var f=s;return Bo(f)+".Consumer";case L:var p=s;return Bo(p._context)+".Provider";case se:return"DehydratedFragment";case F:return yi(s,s.render,"ForwardRef");case k:return"Fragment";case _:return s;case w:return"Portal";case x:return"Root";case R:return"Text";case fe:return ln(s);case P:return s===qe?"StrictMode":"Mode";case ae:return"Offscreen";case B:return"Profiler";case Ke:return"Scope";case $:return"Suspense";case Se:return"SuspenseList";case _e:return"TracingMarker";case v:case m:case me:case S:case K:case Z:if(typeof s=="function")return s.displayName||s.name||null;if(typeof s=="string")return s;break}return null}var Gs=r.ReactDebugCurrentFrame,xr=null,Bi=!1;function Ha(){{if(xr===null)return null;var n=xr._debugOwner;if(n!==null&&typeof n<"u")return Ft(n)}return null}function oo(){return xr===null?"":Fa(xr)}function Er(){Gs.getCurrentStack=null,xr=null,Bi=!1}function rr(n){Gs.getCurrentStack=n===null?null:oo,xr=n,Bi=!1}function Uo(){return xr}function qr(n){Bi=n}function dr(n){return""+n}function aa(n){switch(typeof n){case"boolean":case"number":case"string":case"undefined":return n;case"object":return gn(n),n;default:return""}}var _u={button:!0,checkbox:!0,image:!0,hidden:!0,radio:!0,reset:!0,submit:!0};function so(n,a){_u[a.type]||a.onChange||a.onInput||a.readOnly||a.disabled||a.value==null||u("You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`."),a.onChange||a.readOnly||a.disabled||a.checked==null||u("You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.")}function bi(n){var a=n.type,s=n.nodeName;return s&&s.toLowerCase()==="input"&&(a==="checkbox"||a==="radio")}function Ys(n){return n._valueTracker}function vn(n){n._valueTracker=null}function In(n){var a="";return n&&(bi(n)?a=n.checked?"true":"false":a=n.value),a}function Ml(n){var a=bi(n)?"checked":"value",s=Object.getOwnPropertyDescriptor(n.constructor.prototype,a);gn(n[a]);var f=""+n[a];if(!(n.hasOwnProperty(a)||typeof s>"u"||typeof s.get!="function"||typeof s.set!="function")){var p=s.get,y=s.set;Object.defineProperty(n,a,{configurable:!0,get:function(){return p.call(this)},set:function(T){gn(T),f=""+T,y.call(this,T)}}),Object.defineProperty(n,a,{enumerable:s.enumerable});var C={getValue:function(){return f},setValue:function(T){gn(T),f=""+T},stopTracking:function(){vn(n),delete n[a]}};return C}}function za(n){Ys(n)||(n._valueTracker=Ml(n))}function W(n){if(!n)return!1;var a=Ys(n);if(!a)return!0;var s=a.getValue(),f=In(n);return f!==s?(a.setValue(f),!0):!1}function Q(n){if(n=n||(typeof document<"u"?document:void 0),typeof n>"u")return null;try{return n.activeElement||n.body}catch{return n.body}}var de=!1,at=!1,un=!1,Fn=!1;function Wt(n){var a=n.type==="checkbox"||n.type==="radio";return a?n.checked!=null:n.value!=null}function b(n,a){var s=n,f=a.checked,p=Jt({},a,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:f??s._wrapperState.initialChecked});return p}function A(n,a){so("input",a),a.checked!==void 0&&a.defaultChecked!==void 0&&!at&&(u("%s contains an input of type %s with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components",Ha()||"A component",a.type),at=!0),a.value!==void 0&&a.defaultValue!==void 0&&!de&&(u("%s contains an input of type %s with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components",Ha()||"A component",a.type),de=!0);var s=n,f=a.defaultValue==null?"":a.defaultValue;s._wrapperState={initialChecked:a.checked!=null?a.checked:a.defaultChecked,initialValue:aa(a.value!=null?a.value:f),controlled:Wt(a)}}function V(n,a){var s=n,f=a.checked;f!=null&&Ii(s,"checked",f,!1)}function G(n,a){var s=n;{var f=Wt(a);!s._wrapperState.controlled&&f&&!Fn&&(u("A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"),Fn=!0),s._wrapperState.controlled&&!f&&!un&&(u("A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"),un=!0)}V(n,a);var p=aa(a.value),y=a.type;if(p!=null)y==="number"?(p===0&&s.value===""||s.value!=p)&&(s.value=dr(p)):s.value!==dr(p)&&(s.value=dr(p));else if(y==="submit"||y==="reset"){s.removeAttribute("value");return}a.hasOwnProperty("value")?$e(s,a.type,p):a.hasOwnProperty("defaultValue")&&$e(s,a.type,aa(a.defaultValue)),a.checked==null&&a.defaultChecked!=null&&(s.defaultChecked=!!a.defaultChecked)}function te(n,a,s){var f=n;if(a.hasOwnProperty("value")||a.hasOwnProperty("defaultValue")){var p=a.type,y=p==="submit"||p==="reset";if(y&&(a.value===void 0||a.value===null))return;var C=dr(f._wrapperState.initialValue);s||C!==f.value&&(f.value=C),f.defaultValue=C}var T=f.name;T!==""&&(f.name=""),f.defaultChecked=!f.defaultChecked,f.defaultChecked=!!f._wrapperState.initialChecked,T!==""&&(f.name=T)}function Fe(n,a){var s=n;G(s,a),Ce(s,a)}function Ce(n,a){var s=a.name;if(a.type==="radio"&&s!=null){for(var f=n;f.parentNode;)f=f.parentNode;sr(s,"name");for(var p=f.querySelectorAll("input[name="+JSON.stringify(""+s)+'][type="radio"]'),y=0;y.")))}):a.dangerouslySetInnerHTML!=null&&(At||(At=!0,u("Pass a `value` prop if you set dangerouslyInnerHTML so React knows which value should be selected.")))),a.selected!=null&&!it&&(u("Use the `defaultValue` or `value` props on must be a scalar value if `multiple` is false.%s",s,Sa())}}}}function On(n,a,s,f){var p=n.options;if(a){for(var y=s,C={},T=0;T.");var f=Jt({},a,{value:void 0,defaultValue:void 0,children:dr(s._wrapperState.initialValue)});return f}function gv(n,a){var s=n;so("textarea",a),a.value!==void 0&&a.defaultValue!==void 0&&!Zy&&(u("%s contains a textarea with both value and defaultValue props. Textarea elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled textarea and remove one of these props. More info: https://reactjs.org/link/controlled-components",Ha()||"A component"),Zy=!0);var f=a.value;if(f==null){var p=a.children,y=a.defaultValue;if(p!=null){u("Use the `defaultValue` or `value` props instead of setting children on