diff --git a/backend/invoke_ai_web_server.py b/backend/invoke_ai_web_server.py index 89d67a6e70..d4055137da 100644 --- a/backend/invoke_ai_web_server.py +++ b/backend/invoke_ai_web_server.py @@ -3,6 +3,8 @@ import glob import os import shutil import mimetypes +import traceback +import math from flask import Flask, redirect, send_from_directory from flask_socketio import SocketIO @@ -16,6 +18,7 @@ from ldm.dream.conditioning import split_weighted_subprompts from backend.modules.parameters import parameters_to_command + # Loading Arguments opt = Args() args = opt.parse_args() @@ -39,15 +42,18 @@ class InvokeAIWebServer: def setup_flask(self): # Fix missing mimetypes on Windows - mimetypes.add_type("application/javascript", ".js") - mimetypes.add_type("text/css", ".css") + mimetypes.add_type('application/javascript', '.js') + mimetypes.add_type('text/css', '.css') # Socket IO logger = True if args.web_verbose else False engineio_logger = True if args.web_verbose else False max_http_buffer_size = 10000000 # CORS Allowed Setup - cors_allowed_origins = ['http://127.0.0.1:5173', 'http://localhost:5173'] + cors_allowed_origins = [ + 'http://127.0.0.1:5173', + 'http://localhost:5173', + ] additional_allowed_origins = ( opt.cors if opt.cors else [] ) # additional CORS allowed origins @@ -76,6 +82,10 @@ class InvokeAIWebServer: ping_timeout=60, ) + # Keep Server Alive Route + @self.app.route('/flaskwebgui-keep-server-alive') + def keep_alive(): + return {'message': 'Server Running'} # Outputs Route self.app.config['OUTPUTS_FOLDER'] = os.path.abspath(args.outdir) @@ -86,7 +96,6 @@ class InvokeAIWebServer: self.app.config['OUTPUTS_FOLDER'], file_path ) - # Base Route @self.app.route('/') def serve(): @@ -99,18 +108,42 @@ class InvokeAIWebServer: self.load_socketio_listeners(self.socketio) - print('>> Started Invoke AI Web Server!') - if self.host == '0.0.0.0': - print( - f"Point your browser at http://localhost:{self.port} or use the host's DNS name or IP address." - ) - else: - print( - '>> Default host address now 127.0.0.1 (localhost). Use --host 0.0.0.0 to bind any address.' - ) - print(f'>> Point your browser at http://{self.host}:{self.port}') + if args.gui: + print('>> Launching Invoke AI GUI') + close_server_on_exit = True + if args.web_develop: + close_server_on_exit = False + try: + from flaskwebgui import FlaskUI + FlaskUI( + app=self.app, + socketio=self.socketio, + start_server='flask-socketio', + host=self.host, + port=self.port, + width=1600, + height=1000, + idle_interval=10, + close_server_on_exit=close_server_on_exit, + ).run() + except KeyboardInterrupt: + import sys - self.socketio.run(app=self.app, host=self.host, port=self.port) + sys.exit(0) + else: + print('>> Started Invoke AI Web Server!') + if self.host == '0.0.0.0': + print( + f"Point your browser at http://localhost:{self.port} or use the host's DNS name or IP address." + ) + else: + print( + '>> Default host address now 127.0.0.1 (localhost). Use --host 0.0.0.0 to bind any address.' + ) + print( + f'>> Point your browser at http://{self.host}:{self.port}' + ) + self.socketio.run(app=self.app, host=self.host, port=self.port) def setup_app(self): self.result_url = 'outputs/' @@ -146,276 +179,276 @@ class InvokeAIWebServer: config = self.get_system_config() socketio.emit('systemConfig', config) - @socketio.on('requestImages') - def handle_request_images(page=1, offset=0, last_mtime=None): - chunk_size = 50 + @socketio.on('requestLatestImages') + def handle_request_latest_images(latest_mtime): + try: + paths = glob.glob(os.path.join(self.result_path, '*.png')) - if last_mtime: - print(f'>> Latest images requested') - else: - print( - f'>> Page {page} of images requested (page size {chunk_size} offset {offset})' + image_paths = sorted( + paths, key=lambda x: os.path.getmtime(x), reverse=True ) - paths = glob.glob(os.path.join(self.result_path, '*.png')) - sorted_paths = sorted( - paths, key=lambda x: os.path.getmtime(x), reverse=True - ) - - if last_mtime: - image_paths = filter( - lambda x: os.path.getmtime(x) > last_mtime, sorted_paths - ) - else: - - image_paths = sorted_paths[ - slice( - chunk_size * (page - 1) + offset, - chunk_size * page + offset, + image_paths = list( + filter( + lambda x: os.path.getmtime(x) > latest_mtime, + image_paths, ) - ] - page = page + 1 - - image_array = [] - - for path in image_paths: - metadata = retrieve_metadata(path) - image_array.append( - { - 'url': self.get_url_from_image_path(path), - 'mtime': os.path.getmtime(path), - 'metadata': metadata['sd-metadata'], - } ) - socketio.emit( - 'galleryImages', - { - 'images': image_array, - 'nextPage': page, - 'offset': offset, - 'onlyNewImages': True if last_mtime else False, - }, - ) + image_array = [] + + for path in image_paths: + metadata = retrieve_metadata(path) + image_array.append( + { + 'url': self.get_url_from_image_path(path), + 'mtime': os.path.getmtime(path), + 'metadata': metadata['sd-metadata'], + } + ) + + socketio.emit( + 'galleryImages', + { + 'images': image_array, + }, + ) + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') + + @socketio.on('requestImages') + def handle_request_images(earliest_mtime=None): + try: + page_size = 50 + + paths = glob.glob(os.path.join(self.result_path, '*.png')) + + image_paths = sorted( + paths, key=lambda x: os.path.getmtime(x), reverse=True + ) + + if earliest_mtime: + image_paths = list( + filter( + lambda x: os.path.getmtime(x) < earliest_mtime, + image_paths, + ) + ) + + areMoreImagesAvailable = len(image_paths) >= page_size + image_paths = image_paths[slice(0, page_size)] + + image_array = [] + + for path in image_paths: + metadata = retrieve_metadata(path) + image_array.append( + { + 'url': self.get_url_from_image_path(path), + 'mtime': os.path.getmtime(path), + 'metadata': metadata['sd-metadata'], + } + ) + + socketio.emit( + 'galleryImages', + { + 'images': image_array, + 'areMoreImagesAvailable': areMoreImagesAvailable, + }, + ) + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') @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}' - ) - self.generate_images( - generation_parameters, esrgan_parameters, gfpgan_parameters - ) + try: + print( + f'>> Image generation requested: {generation_parameters}\nESRGAN parameters: {esrgan_parameters}\nGFPGAN parameters: {gfpgan_parameters}' + ) + self.generate_images( + generation_parameters, esrgan_parameters, gfpgan_parameters + ) + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') - @socketio.on('runESRGAN') - def handle_run_esrgan_event(original_image, 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, - } + traceback.print_exc() + print('\n') - socketio.emit('progressUpdate', progress) - eventlet.sleep(0) + @socketio.on('runPostprocessing') + def handle_run_postprocessing( + original_image, postprocessing_parameters + ): + try: + print( + f'>> Postprocessing requested for "{original_image["url"]}": {postprocessing_parameters}' + ) - original_image_path = self.get_image_path_from_url(original_image['url']) - # os.path.join(self.result_path, os.path.basename(original_image['url'])) + progress = Progress() - image = Image.open(original_image_path) + socketio.emit('progressUpdate', progress.to_formatted_dict()) + eventlet.sleep(0) - seed = ( - original_image['metadata']['seed'] - if 'seed' in original_image['metadata'] - else 'unknown_seed' - ) + original_image_path = self.get_image_path_from_url( + original_image['url'] + ) - progress['currentStatus'] = 'Upscaling' - socketio.emit('progressUpdate', progress) - eventlet.sleep(0) + image = Image.open(original_image_path) - image = self.esrgan.process( - image=image, - upsampler_scale=esrgan_parameters['upscale'][0], - strength=esrgan_parameters['upscale'][1], - seed=seed, - ) + seed = ( + original_image['metadata']['seed'] + if 'seed' in original_image['metadata'] + else 'unknown_seed' + ) - progress['currentStatus'] = 'Saving image' - socketio.emit('progressUpdate', progress) - eventlet.sleep(0) + if postprocessing_parameters['type'] == 'esrgan': + progress.set_current_status('Upscaling') + elif postprocessing_parameters['type'] == 'gfpgan': + progress.set_current_status('Restoring Faces') - esrgan_parameters['seed'] = seed - metadata = self.parameters_to_post_processed_image_metadata( - parameters=esrgan_parameters, - original_image_path=original_image_path, - type='esrgan', - ) - command = parameters_to_command(esrgan_parameters) + socketio.emit('progressUpdate', progress.to_formatted_dict()) + eventlet.sleep(0) - path = self.save_image( - image, - command, - metadata, - self.result_path, - postprocessing='esrgan', - ) + if postprocessing_parameters['type'] == 'esrgan': + image = self.esrgan.process( + image=image, + upsampler_scale=postprocessing_parameters['upscale'][ + 0 + ], + strength=postprocessing_parameters['upscale'][1], + seed=seed, + ) + elif postprocessing_parameters['type'] == 'gfpgan': + image = self.gfpgan.process( + image=image, + strength=postprocessing_parameters['gfpgan_strength'], + seed=seed, + ) + else: + raise TypeError( + f'{postprocessing_parameters["type"]} is not a valid postprocessing type' + ) - self.write_log_message( - f'[Upscaled] "{original_image_path}" > "{path}": {command}' - ) + progress.set_current_status('Saving Image') + socketio.emit('progressUpdate', progress.to_formatted_dict()) + eventlet.sleep(0) - 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) + postprocessing_parameters['seed'] = seed + metadata = self.parameters_to_post_processed_image_metadata( + parameters=postprocessing_parameters, + original_image_path=original_image_path, + ) - socketio.emit( - 'esrganResult', - { - 'url': self.get_url_from_image_path(path), - 'mtime': os.path.getmtime(path), - 'metadata': metadata, - }, - ) + command = parameters_to_command(postprocessing_parameters) - @socketio.on('runGFPGAN') - def handle_run_gfpgan_event(original_image, 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, - } + path = self.save_result_image( + image, + command, + metadata, + self.result_path, + postprocessing=postprocessing_parameters['type'], + ) - socketio.emit('progressUpdate', progress) - eventlet.sleep(0) + self.write_log_message( + f'[Postprocessed] "{original_image_path}" > "{path}": {postprocessing_parameters}' + ) - original_image_path = self.get_image_path_from_url(original_image['url']) + progress.mark_complete() + socketio.emit('progressUpdate', progress.to_formatted_dict()) + eventlet.sleep(0) - image = Image.open(original_image_path) + socketio.emit( + 'postprocessingResult', + { + 'url': self.get_url_from_image_path(path), + 'mtime': os.path.getmtime(path), + 'metadata': metadata, + }, + ) + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') - seed = ( - original_image['metadata']['seed'] - if 'seed' in original_image['metadata'] - else 'unknown_seed' - ) - - progress['currentStatus'] = 'Fixing faces' - socketio.emit('progressUpdate', progress) - eventlet.sleep(0) - - image = self.gfpgan.process( - image=image, - strength=gfpgan_parameters['gfpgan_strength'], - seed=seed, - ) - - progress['currentStatus'] = 'Saving image' - socketio.emit('progressUpdate', progress) - eventlet.sleep(0) - - gfpgan_parameters['seed'] = seed - metadata = self.parameters_to_post_processed_image_metadata( - parameters=gfpgan_parameters, - original_image_path=original_image_path, - type='gfpgan', - ) - command = parameters_to_command(gfpgan_parameters) - - path = self.save_image( - image, - command, - metadata, - self.result_path, - postprocessing='gfpgan', - ) - - self.write_log_message( - f'[Fixed faces] "{original_image_path}" > "{path}": {command}' - ) - - 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': self.get_url_from_image_path(path), - 'mtime': os.path.getmtime(path), - 'metadata': metadata, - }, - ) + traceback.print_exc() + print('\n') @socketio.on('cancel') def handle_cancel(): print(f'>> Cancel processing requested') self.canceled.set() - socketio.emit('processingCanceled') # TODO: I think this needs a safety mechanism. @socketio.on('deleteImage') - def handle_delete_image(path, uuid): - print(f'>> Delete requested "{path}"') - from send2trash import send2trash + def handle_delete_image(url, uuid): + try: + print(f'>> Delete requested "{url}"') + from send2trash import send2trash - path = self.get_image_path_from_url(path) - send2trash(path) - socketio.emit('imageDeleted', {'url': path, 'uuid': uuid}) + path = self.get_image_path_from_url(url) + send2trash(path) + socketio.emit('imageDeleted', {'url': url, 'uuid': uuid}) + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') # TODO: I think this needs a safety mechanism. @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]}' - file_path = os.path.join(self.init_image_path, name) - os.makedirs(os.path.dirname(file_path), exist_ok=True) - newFile = open(file_path, 'wb') - newFile.write(bytes) + try: + print(f'>> Init image upload requested "{name}"') + file_path = self.save_file_unique_uuid_name( + bytes=bytes, name=name, path=self.init_image_path + ) - socketio.emit( - 'initialImageUploaded', {'url': self.get_url_from_image_path(file_path), 'uuid': ''} - ) + socketio.emit( + 'initialImageUploaded', + { + 'url': self.get_url_from_image_path(file_path), + }, + ) + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') # TODO: I think this needs a safety mechanism. @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]}' - file_path = os.path.join(self.mask_image_path, name) - os.makedirs(os.path.dirname(file_path), exist_ok=True) - newFile = open(file_path, 'wb') - newFile.write(bytes) + try: + print(f'>> Mask image upload requested "{name}"') - socketio.emit('maskImageUploaded', {'url': self.get_url_from_image_path(file_path), 'uuid': ''}) + file_path = self.save_file_unique_uuid_name( + bytes=bytes, name=name, path=self.mask_image_path + ) + + socketio.emit( + 'maskImageUploaded', + { + 'url': self.get_url_from_image_path(file_path), + }, + ) + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') # App Functions def get_system_config(self): @@ -430,244 +463,239 @@ class InvokeAIWebServer: def generate_images( self, generation_parameters, esrgan_parameters, gfpgan_parameters ): - self.canceled.clear() + try: + self.canceled.clear() - step_index = 1 - prior_variations = ( - generation_parameters['with_variations'] - if 'with_variations' in generation_parameters - else [] - ) + step_index = 1 + prior_variations = ( + generation_parameters['with_variations'] + if 'with_variations' in generation_parameters + else [] + ) - """ - TODO: RE-IMPLEMENT THE COMMENTED-OUT CODE - If a result image is used as an init image, and then deleted, we will want to be - able to use it as an init image in the future. Need to copy it. + """ + TODO: + If a result image is used as an init image, and then deleted, we will want to be + able to use it as an init image in the future. Need to handle this case. + """ - 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']) - # abs_init_image_path = os.path.join(self.init_image_path, filename) - # if not os.path.exists( - # abs_init_image_path - # ): - # unique_filename = self.make_unique_init_image_filename( - # filename - # ) - # new_path = os.path.join(self.init_image_path, unique_filename) - # shutil.copy(abs_init_image_path, new_path) - # generation_parameters['init_img'] = os.path.abspath(new_path) - # else: - # generation_parameters['init_img'] = os.path.abspath(os.path.join(self.init_image_path, filename)) + # We need to give absolute paths to the generator, stash the URLs for later + init_img_url = None + mask_img_url = None - # if 'init_mask' in generation_parameters: - # filename = os.path.basename(generation_parameters['init_mask']) - # if not os.path.exists( - # os.path.join(self.mask_image_path, filename) - # ): - # unique_filename = self.make_unique_init_image_filename( - # filename - # ) - # new_path = os.path.join( - # self.init_image_path, unique_filename - # ) - # shutil.copy(generation_parameters['init_img'], new_path) - # generation_parameters['init_mask'] = os.path.abspath(new_path) - # else: - # generation_parameters['init_mas'] = os.path.abspath(os.path.join(self.mask_image_path, filename)) + if 'init_img' in generation_parameters: + init_img_url = generation_parameters['init_img'] + generation_parameters[ + 'init_img' + ] = self.get_image_path_from_url( + generation_parameters['init_img'] + ) + if 'init_mask' in generation_parameters: + mask_img_url = generation_parameters['init_mask'] + generation_parameters[ + 'init_mask' + ] = self.get_image_path_from_url( + generation_parameters['init_mask'] + ) - # We need to give absolute paths to the generator, stash the URLs for later - init_img_url = None; - mask_img_url = None; + totalSteps = self.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, + ) - if 'init_img' in generation_parameters: - init_img_url = generation_parameters['init_img'] - generation_parameters['init_img'] = self.get_image_path_from_url(generation_parameters['init_img']) + progress = Progress(generation_parameters=generation_parameters) - if 'init_mask' in generation_parameters: - mask_img_url = generation_parameters['init_mask'] - generation_parameters['init_mask'] = self.get_image_path_from_url(generation_parameters['init_mask']) + self.socketio.emit('progressUpdate', progress.to_formatted_dict()) + eventlet.sleep(0) - totalSteps = self.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, - ) + def image_progress(sample, step): + if self.canceled.is_set(): + raise CanceledException - progress = { - 'currentStep': 1, - 'totalSteps': totalSteps, - 'currentIteration': 1, - 'totalIterations': generation_parameters['iterations'], - 'currentStatus': 'Preparing', - 'isProcessing': True, - 'currentStatusHasSteps': False, - } + nonlocal step_index + nonlocal generation_parameters + nonlocal progress - self.socketio.emit('progressUpdate', progress) - eventlet.sleep(0) + progress.set_current_step(step + 1) + progress.set_current_status('Generating') + progress.set_current_status_has_steps(True) - def image_progress(sample, step): - if self.canceled.is_set(): - raise CanceledException + if ( + generation_parameters['progress_images'] + and step % 5 == 0 + and step < generation_parameters['steps'] - 1 + ): + image = self.generate.sample_to_image(sample) + metadata = self.parameters_to_generated_image_metadata( + generation_parameters + ) + command = parameters_to_command(generation_parameters) - nonlocal step_index - nonlocal generation_parameters - nonlocal progress + path = self.save_result_image( + image, + command, + metadata, + self.intermediate_path, + step_index=step_index, + postprocessing=False, + ) - 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 = self.generate.sample_to_image(sample) - metadata = self.parameters_to_generated_image_metadata(generation_parameters) - command = parameters_to_command(generation_parameters) - - path = self.save_image(image, command, metadata, self.intermediate_path, step_index=step_index, postprocessing=False) - - step_index += 1 + step_index += 1 + self.socketio.emit( + 'intermediateResult', + { + 'url': self.get_url_from_image_path(path), + 'mtime': os.path.getmtime(path), + 'metadata': metadata, + }, + ) self.socketio.emit( - 'intermediateResult', + 'progressUpdate', progress.to_formatted_dict() + ) + eventlet.sleep(0) + + def image_done(image, seed, first_seed): + if self.canceled.is_set(): + raise CanceledException + + nonlocal generation_parameters + nonlocal esrgan_parameters + nonlocal gfpgan_parameters + nonlocal progress + + step_index = 1 + nonlocal prior_variations + + progress.set_current_status('Generation Complete') + + self.socketio.emit( + 'progressUpdate', progress.to_formatted_dict() + ) + eventlet.sleep(0) + + all_parameters = generation_parameters + postprocessing = False + + if ( + 'variation_amount' in all_parameters + and all_parameters['variation_amount'] > 0 + ): + first_seed = first_seed or seed + this_variation = [ + [seed, all_parameters['variation_amount']] + ] + all_parameters['with_variations'] = ( + prior_variations + this_variation + ) + all_parameters['seed'] = first_seed + elif 'with_variations' in all_parameters: + all_parameters['seed'] = first_seed + else: + all_parameters['seed'] = seed + + if self.canceled.is_set(): + raise CanceledException + + if esrgan_parameters: + progress.set_current_status('Upscaling') + progress.set_current_status_has_steps(False) + self.socketio.emit( + 'progressUpdate', progress.to_formatted_dict() + ) + eventlet.sleep(0) + + image = self.esrgan.process( + image=image, + upsampler_scale=esrgan_parameters['level'], + strength=esrgan_parameters['strength'], + seed=seed, + ) + + postprocessing = True + all_parameters['upscale'] = [ + esrgan_parameters['level'], + esrgan_parameters['strength'], + ] + + if self.canceled.is_set(): + raise CanceledException + + if gfpgan_parameters: + progress.set_current_status('Restoring Faces') + progress.set_current_status_has_steps(False) + self.socketio.emit( + 'progressUpdate', progress.to_formatted_dict() + ) + eventlet.sleep(0) + + image = self.gfpgan.process( + image=image, + strength=gfpgan_parameters['strength'], + seed=seed, + ) + postprocessing = True + all_parameters['gfpgan_strength'] = gfpgan_parameters[ + 'strength' + ] + + progress.set_current_status('Saving Image') + self.socketio.emit( + 'progressUpdate', progress.to_formatted_dict() + ) + eventlet.sleep(0) + + # restore the stashed URLS and discard the paths, we are about to send the result to client + if 'init_img' in all_parameters: + all_parameters['init_img'] = init_img_url + + if 'init_mask' in all_parameters: + all_parameters['init_mask'] = mask_img_url + + metadata = self.parameters_to_generated_image_metadata( + all_parameters + ) + + command = parameters_to_command(all_parameters) + + path = self.save_result_image( + image, + command, + metadata, + self.result_path, + postprocessing=postprocessing, + ) + + print(f'>> Image generated: "{path}"') + self.write_log_message(f'[Generated] "{path}": {command}') + + if progress.total_iterations > progress.current_iteration: + progress.set_current_step(1) + progress.set_current_status('Iteration complete') + progress.set_current_status_has_steps(False) + else: + progress.mark_complete() + + self.socketio.emit( + 'progressUpdate', progress.to_formatted_dict() + ) + eventlet.sleep(0) + + self.socketio.emit( + 'generationResult', { 'url': self.get_url_from_image_path(path), 'mtime': os.path.getmtime(path), 'metadata': metadata, }, ) - self.socketio.emit('progressUpdate', progress) - eventlet.sleep(0) - - def image_done(image, seed, first_seed): - nonlocal generation_parameters - nonlocal esrgan_parameters - nonlocal gfpgan_parameters - nonlocal progress - - step_index = 1 - nonlocal prior_variations - - progress['currentStatus'] = 'Generation complete' - self.socketio.emit('progressUpdate', progress) - eventlet.sleep(0) - - all_parameters = generation_parameters - postprocessing = False - - if ( - 'variation_amount' in all_parameters - and all_parameters['variation_amount'] > 0 - ): - first_seed = first_seed or seed - this_variation = [[seed, all_parameters['variation_amount']]] - all_parameters['with_variations'] = ( - prior_variations + this_variation - ) - all_parameters['seed'] = first_seed - elif 'with_variations' in all_parameters: - all_parameters['seed'] = first_seed - else: - all_parameters['seed'] = seed - - if esrgan_parameters: - progress['currentStatus'] = 'Upscaling' - progress['currentStatusHasSteps'] = False - self.socketio.emit('progressUpdate', progress) eventlet.sleep(0) - image = self.esrgan.process( - image=image, - upsampler_scale=esrgan_parameters['level'], - strength=esrgan_parameters['strength'], - seed=seed, - ) + progress.set_current_iteration(progress.current_iteration + 1) - postprocessing = True - all_parameters['upscale'] = [ - esrgan_parameters['level'], - esrgan_parameters['strength'], - ] - - if gfpgan_parameters: - progress['currentStatus'] = 'Fixing faces' - progress['currentStatusHasSteps'] = False - self.socketio.emit('progressUpdate', progress) - eventlet.sleep(0) - - image = self.gfpgan.process( - image=image, - strength=gfpgan_parameters['strength'], - seed=seed, - ) - postprocessing = True - all_parameters['gfpgan_strength'] = gfpgan_parameters[ - 'strength' - ] - - progress['currentStatus'] = 'Saving image' - self.socketio.emit('progressUpdate', progress) - eventlet.sleep(0) - - # restore the stashed URLS and discard the paths, we are about to send the result to client - if 'init_img' in all_parameters: - all_parameters['init_img'] = init_img_url - - if 'init_mask' in all_parameters: - all_parameters['init_mask'] = mask_img_url - - metadata = self.parameters_to_generated_image_metadata( - all_parameters - ) - - command = parameters_to_command(all_parameters) - - path = self.save_image( - image, - command, - metadata, - self.result_path, - postprocessing=postprocessing, - ) - - print(f'>> Image generated: "{path}"') - self.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 - else: - progress['currentStep'] = 0 - progress['totalSteps'] = 0 - progress['currentIteration'] = 0 - progress['totalIterations'] = 0 - progress['currentStatus'] = 'Finished' - progress['isProcessing'] = False - - self.socketio.emit('progressUpdate', progress) - eventlet.sleep(0) - - self.socketio.emit( - 'generationResult', - { - 'url': self.get_url_from_image_path(path), - 'mtime': os.path.getmtime(path), - 'metadata': metadata, - }, - ) - eventlet.sleep(0) - - try: self.generate.prompt2image( **generation_parameters, step_callback=image_progress, @@ -677,16 +705,18 @@ class InvokeAIWebServer: except KeyboardInterrupt: raise except CanceledException: + self.socketio.emit('processingCanceled') pass except Exception as e: + print(e) self.socketio.emit('error', {'message': (str(e))}) print('\n') - import traceback traceback.print_exc() print('\n') def parameters_to_generated_image_metadata(self, parameters): + try: # top-level metadata minus `image` or `images` metadata = self.get_system_config() # remove any image keys not mentioned in RFC #266 @@ -708,104 +738,147 @@ class InvokeAIWebServer: 'seamless', ] - rfc_dict = {} + rfc_dict = {} - for item in parameters.items(): - key, value = item - if key in rfc266_img_fields: - rfc_dict[key] = value + for item in parameters.items(): + key, value = item + if key in rfc266_img_fields: + rfc_dict[key] = value - postprocessing = [] + postprocessing = [] - # 'postprocessing' is either null or an - if 'gfpgan_strength' in parameters: + # 'postprocessing' is either null or an + if 'gfpgan_strength' in parameters: - postprocessing.append( - { - 'type': 'gfpgan', - 'strength': float(parameters['gfpgan_strength']), - } + 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 ) - if 'upscale' in parameters: - postprocessing.append( - { - 'type': 'esrgan', - 'scale': int(parameters['upscale'][0]), - 'strength': float(parameters['upscale'][1]), - } - ) + # semantic drift + rfc_dict['sampler'] = parameters['sampler_name'] - rfc_dict['postprocessing'] = ( - postprocessing if len(postprocessing) > 0 else None - ) + # 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 - # semantic drift - rfc_dict['sampler'] = parameters['sampler_name'] + # 'variations' should always exist and be an array, empty or consisting of {'seed': seed, 'weight': weight} pairs + variations = [] - # 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 + if 'with_variations' in parameters: + variations = [ + {'seed': x[0], 'weight': x[1]} + for x in parameters['with_variations'] + ] - # 'variations' should always exist and be an array, empty or consisting of {'seed': seed, 'weight': weight} pairs - variations = [] + rfc_dict['variations'] = 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(self.get_image_path_from_url(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(self.get_image_path_from_url(parameters['init_mask'])) # TODO: Noncompliant - rfc_dict['mask_image_path'] = parameters[ - 'init_mask' + 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( + self.get_image_path_from_url(parameters['init_img']) + ) + rfc_dict['init_image_path'] = parameters[ + 'init_img' ] # TODO: Noncompliant - else: - rfc_dict['type'] = 'txt2img' + 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( + self.get_image_path_from_url(parameters['init_mask']) + ) # TODO: Noncompliant + rfc_dict['mask_image_path'] = parameters[ + 'init_mask' + ] # TODO: Noncompliant + else: + rfc_dict['type'] = 'txt2img' - metadata['image'] = rfc_dict + metadata['image'] = rfc_dict - return metadata + return metadata + + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') def parameters_to_post_processed_image_metadata( - self, parameters, original_image_path, type + self, parameters, original_image_path ): - # top-level metadata minus `image` or `images` - metadata = self.get_system_config() + try: + current_metadata = retrieve_metadata(original_image_path)[ + 'sd-metadata' + ] + postprocessing_metadata = {} - orig_hash = calculate_init_img_hash(self.get_image_path_from_url(original_image_path)) + """ + if we don't have an original image metadata to reconstruct, + need to record the original image and its hash + """ + if 'image' not in current_metadata: + current_metadata['image'] = {} - image = {'orig_path': original_image_path, 'orig_hash': orig_hash} + orig_hash = calculate_init_img_hash( + self.get_image_path_from_url(original_image_path) + ) - 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}') + postprocessing_metadata['orig_path'] = (original_image_path,) + postprocessing_metadata['orig_hash'] = orig_hash - metadata['image'] = image - return metadata + if parameters['type'] == 'esrgan': + postprocessing_metadata['type'] = 'esrgan' + postprocessing_metadata['scale'] = parameters['upscale'][0] + postprocessing_metadata['strength'] = parameters['upscale'][1] + elif parameters['type'] == 'gfpgan': + postprocessing_metadata['type'] = 'gfpgan' + postprocessing_metadata['strength'] = parameters[ + 'gfpgan_strength' + ] + else: + raise TypeError(f"Invalid type: {parameters['type']}") - def save_image( + if 'postprocessing' in current_metadata['image'] and isinstance( + current_metadata['image']['postprocessing'], list + ): + current_metadata['image']['postprocessing'].append( + postprocessing_metadata + ) + else: + current_metadata['image']['postprocessing'] = [ + postprocessing_metadata + ] + + return current_metadata + + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') + + def save_result_image( self, image, command, @@ -814,69 +887,213 @@ class InvokeAIWebServer: step_index=None, postprocessing=False, ): - pngwriter = PngWriter(output_dir) - prefix = pngwriter.unique_prefix() + try: + pngwriter = PngWriter(output_dir) + prefix = pngwriter.unique_prefix() - seed = 'unknown_seed' + seed = 'unknown_seed' - if 'image' in metadata: - if 'seed' in metadata['image']: - seed = metadata['image']['seed'] + if 'image' in metadata: + if 'seed' in metadata['image']: + seed = metadata['image']['seed'] - filename = f'{prefix}.{seed}' + filename = f'{prefix}.{seed}' - if step_index: - filename += f'.{step_index}' - if postprocessing: - filename += f'.postprocessed' + if step_index: + filename += f'.{step_index}' + if postprocessing: + filename += f'.postprocessed' - filename += '.png' + filename += '.png' - path = pngwriter.save_image_and_prompt_to_png( - image=image, dream_prompt=command, metadata=metadata, name=filename - ) + path = pngwriter.save_image_and_prompt_to_png( + image=image, + dream_prompt=command, + metadata=metadata, + name=filename, + ) - return os.path.abspath(path) + return os.path.abspath(path) + + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') def make_unique_init_image_filename(self, name): - uuid = uuid4().hex - split = os.path.splitext(name) - name = f'{split[0]}.{uuid}{split[1]}' - return name + try: + uuid = uuid4().hex + split = os.path.splitext(name) + name = f'{split[0]}.{uuid}{split[1]}' + return name + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') def calculate_real_steps(self, steps, strength, has_init_image): import math + return math.floor(strength * steps) if has_init_image else steps def write_log_message(self, message): """Logs the filename and parameters used to generate or process that image to log file""" - message = f'{message}\n' - with open(self.log_path, 'a', encoding='utf-8') as file: - file.writelines(message) + try: + message = f'{message}\n' + with open(self.log_path, 'a', encoding='utf-8') as file: + file.writelines(message) + + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') def get_image_path_from_url(self, url): """Given a url to an image used by the client, returns the absolute file path to that image""" - if 'init-images' in url: - return os.path.abspath(os.path.join(self.init_image_path, os.path.basename(url))) - elif 'mask-images' in url: - return os.path.abspath(os.path.join(self.mask_image_path, os.path.basename(url))) - elif 'intermediates' in url: - return os.path.abspath(os.path.join(self.intermediate_path, os.path.basename(url))) - else: - return os.path.abspath(os.path.join(self.result_path, os.path.basename(url))) + try: + if 'init-images' in url: + return os.path.abspath( + os.path.join(self.init_image_path, os.path.basename(url)) + ) + elif 'mask-images' in url: + return os.path.abspath( + os.path.join(self.mask_image_path, os.path.basename(url)) + ) + elif 'intermediates' in url: + return os.path.abspath( + os.path.join(self.intermediate_path, os.path.basename(url)) + ) + else: + return os.path.abspath( + os.path.join(self.result_path, os.path.basename(url)) + ) + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') def get_url_from_image_path(self, path): """Given an absolute file path to an image, returns the URL that the client can use to load the image""" - if 'init-images' in path: - return os.path.join(self.init_image_url, os.path.basename(path)) - elif 'mask-images' in path: - return os.path.join(self.mask_image_url, os.path.basename(path)) - elif 'intermediates' in path: - return os.path.join(self.intermediate_url, os.path.basename(path)) - else: - return os.path.join(self.result_url, os.path.basename(path)) + try: + if 'init-images' in path: + return os.path.join( + self.init_image_url, os.path.basename(path) + ) + elif 'mask-images' in path: + return os.path.join( + self.mask_image_url, os.path.basename(path) + ) + elif 'intermediates' in path: + return os.path.join( + self.intermediate_url, os.path.basename(path) + ) + else: + return os.path.join(self.result_url, os.path.basename(path)) + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + traceback.print_exc() + print('\n') + + def save_file_unique_uuid_name(self, bytes, name, path): + try: + uuid = uuid4().hex + split = os.path.splitext(name) + name = f'{split[0]}.{uuid}{split[1]}' + file_path = os.path.join(path, name) + os.makedirs(os.path.dirname(file_path), exist_ok=True) + newFile = open(file_path, 'wb') + newFile.write(bytes) + return file_path + except Exception as e: + self.socketio.emit('error', {'message': (str(e))}) + print('\n') + + traceback.print_exc() + print('\n') + + +class Progress: + def __init__(self, generation_parameters=None): + self.current_step = 1 + self.total_steps = ( + self._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, + ) + if generation_parameters + else 1 + ) + self.current_iteration = 1 + self.total_iterations = ( + generation_parameters['iterations'] if generation_parameters else 1 + ) + self.current_status = 'Preparing' + self.is_processing = True + self.current_status_has_steps = False + self.has_error = False + + def set_current_step(self, current_step): + self.current_step = current_step + + def set_total_steps(self, total_steps): + self.total_steps = total_steps + + def set_current_iteration(self, current_iteration): + self.current_iteration = current_iteration + + def set_total_iterations(self, total_iterations): + self.total_iterations = total_iterations + + def set_current_status(self, current_status): + self.current_status = current_status + + def set_is_processing(self, is_processing): + self.is_processing = is_processing + + def set_current_status_has_steps(self, current_status_has_steps): + self.current_status_has_steps = current_status_has_steps + + def set_has_error(self, has_error): + self.has_error = has_error + + def mark_complete(self): + self.current_status = 'Processing Complete' + self.current_step = 0 + self.total_steps = 0 + self.current_iteration = 0 + self.total_iterations = 0 + self.is_processing = False + + def to_formatted_dict( + self, + ): + return { + 'currentStep': self.current_step, + 'totalSteps': self.total_steps, + 'currentIteration': self.current_iteration, + 'totalIterations': self.total_iterations, + 'currentStatus': self.current_status, + 'isProcessing': self.is_processing, + 'currentStatusHasSteps': self.current_status_has_steps, + 'hasError': self.has_error, + } + + def _calculate_real_steps(self, steps, strength, has_init_image): + return math.floor(strength * steps) if has_init_image else steps class CanceledException(Exception): - pass \ No newline at end of file + pass diff --git a/configs/stable-diffusion/v1-finetune.yaml b/configs/stable-diffusion/v1-finetune.yaml index 7bc31168e7..df22987fa5 100644 --- a/configs/stable-diffusion/v1-finetune.yaml +++ b/configs/stable-diffusion/v1-finetune.yaml @@ -107,4 +107,4 @@ lightning: benchmark: True max_steps: 4000000 # max_steps: 4000 - \ No newline at end of file + diff --git a/configs/stable-diffusion/v1-inference.yaml b/configs/stable-diffusion/v1-inference.yaml index 59d8f33125..da4770ffc7 100644 --- a/configs/stable-diffusion/v1-inference.yaml +++ b/configs/stable-diffusion/v1-inference.yaml @@ -30,9 +30,9 @@ model: target: ldm.modules.embedding_manager.EmbeddingManager params: placeholder_strings: ["*"] - initializer_words: ["sculpture"] + initializer_words: ['face', 'man', 'photo', 'africanmale'] per_image_tokens: false - num_vectors_per_token: 1 + num_vectors_per_token: 6 progressive_words: False unet_config: diff --git a/configs/stable-diffusion/v1-m1-finetune.yaml b/configs/stable-diffusion/v1-m1-finetune.yaml new file mode 100644 index 0000000000..af37f1ec7e --- /dev/null +++ b/configs/stable-diffusion/v1-m1-finetune.yaml @@ -0,0 +1,110 @@ +model: + base_learning_rate: 5.0e-03 + target: ldm.models.diffusion.ddpm.LatentDiffusion + params: + linear_start: 0.00085 + linear_end: 0.0120 + num_timesteps_cond: 1 + log_every_t: 200 + timesteps: 1000 + first_stage_key: image + cond_stage_key: caption + image_size: 64 + channels: 4 + cond_stage_trainable: true # Note: different from the one we trained before + conditioning_key: crossattn + monitor: val/loss_simple_ema + scale_factor: 0.18215 + use_ema: False + embedding_reg_weight: 0.0 + + personalization_config: + target: ldm.modules.embedding_manager.EmbeddingManager + params: + placeholder_strings: ["*"] + initializer_words: ['face', 'man', 'photo', 'africanmale'] + per_image_tokens: false + num_vectors_per_token: 6 + progressive_words: False + + unet_config: + target: ldm.modules.diffusionmodules.openaimodel.UNetModel + params: + image_size: 32 # unused + in_channels: 4 + out_channels: 4 + model_channels: 320 + attention_resolutions: [ 4, 2, 1 ] + num_res_blocks: 2 + channel_mult: [ 1, 2, 4, 4 ] + num_heads: 8 + use_spatial_transformer: True + transformer_depth: 1 + context_dim: 768 + use_checkpoint: True + legacy: False + + first_stage_config: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + double_z: true + z_channels: 4 + resolution: 256 + in_channels: 3 + out_ch: 3 + ch: 128 + ch_mult: + - 1 + - 2 + - 4 + - 4 + num_res_blocks: 2 + attn_resolutions: [] + dropout: 0.0 + lossconfig: + target: torch.nn.Identity + + cond_stage_config: + target: ldm.modules.encoders.modules.FrozenCLIPEmbedder + +data: + target: main.DataModuleFromConfig + params: + batch_size: 1 + num_workers: 2 + wrap: false + train: + target: ldm.data.personalized.PersonalizedBase + params: + size: 512 + set: train + per_image_tokens: false + repeats: 100 + validation: + target: ldm.data.personalized.PersonalizedBase + params: + size: 512 + set: val + per_image_tokens: false + repeats: 10 + +lightning: + modelcheckpoint: + params: + every_n_train_steps: 500 + callbacks: + image_logger: + target: main.ImageLogger + params: + batch_frequency: 500 + max_images: 5 + increase_log_steps: False + + trainer: + benchmark: False + max_steps: 6200 +# max_steps: 4000 + diff --git a/docs/features/UPSCALE.md b/docs/features/UPSCALE.md index 6f720826ac..10f7c375d7 100644 --- a/docs/features/UPSCALE.md +++ b/docs/features/UPSCALE.md @@ -31,7 +31,7 @@ into **src/gfpgan/experiments/pretrained_models**. On Mac and Linux systems, here's how you'd do it using **wget**: ```bash -wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth src/gfpgan/experiments/pretrained_models/ +wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth -P src/gfpgan/experiments/pretrained_models/ ``` Make sure that you're in the InvokeAI directory when you do this. diff --git a/environment-mac.yml b/environment-mac.yml index dcaed6c88d..70df3b8865 100644 --- a/environment-mac.yml +++ b/environment-mac.yml @@ -33,13 +33,13 @@ dependencies: - openh264==2.3.0 - onnx==1.12.0 - onnxruntime==1.12.1 - - protobuf==3.20.1 + - protobuf==3.19.4 - pudb==2022.1 - - pytorch-lightning==1.6.5 + - pytorch-lightning==1.7.5 - scipy==1.9.1 - streamlit==1.12.2 - sympy==1.10.1 - - tensorboard==2.9.0 + - tensorboard==2.10.0 - torchmetrics==0.9.3 - pip: - flask==2.1.3 diff --git a/frontend/dist/assets/Inter-Bold.790c108b.ttf b/frontend/dist/assets/Inter-Bold.790c108b.ttf new file mode 100644 index 0000000000..8e82c70d10 Binary files /dev/null and b/frontend/dist/assets/Inter-Bold.790c108b.ttf differ diff --git a/frontend/dist/assets/Inter.b9a8e5e2.ttf b/frontend/dist/assets/Inter.b9a8e5e2.ttf new file mode 100644 index 0000000000..ec3164efa8 Binary files /dev/null and b/frontend/dist/assets/Inter.b9a8e5e2.ttf differ diff --git a/frontend/dist/assets/favicon.0d253ced.ico b/frontend/dist/assets/favicon.0d253ced.ico new file mode 100644 index 0000000000..413340efb2 Binary files /dev/null and b/frontend/dist/assets/favicon.0d253ced.ico differ diff --git a/frontend/dist/assets/image2img.dde6a9f1.png b/frontend/dist/assets/image2img.dde6a9f1.png new file mode 100644 index 0000000000..bacc938ea6 Binary files /dev/null and b/frontend/dist/assets/image2img.dde6a9f1.png differ diff --git a/frontend/dist/assets/index.447eb2a9.css b/frontend/dist/assets/index.447eb2a9.css deleted file mode 100644 index 5d9f34f029..0000000000 --- a/frontend/dist/assets/index.447eb2a9.css +++ /dev/null @@ -1 +0,0 @@ -.checkerboard{background-position:0px 0px,10px 10px;background-size:20px 20px;background-image:linear-gradient(45deg,#eee 25%,transparent 25%,transparent 75%,#eee 75%,#eee 100%),linear-gradient(45deg,#eee 25%,white 25%,white 75%,#eee 75%,#eee 100%)} diff --git a/frontend/dist/assets/index.73bc96d2.js b/frontend/dist/assets/index.73bc96d2.js new file mode 100644 index 0000000000..5276bfaeae --- /dev/null +++ b/frontend/dist/assets/index.73bc96d2.js @@ -0,0 +1,690 @@ +function oH(e,t){for(var n=0;na[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"]'))a(o);new MutationObserver(o=>{for(const u of o)if(u.type==="childList")for(const c of u.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&a(c)}).observe(document,{childList:!0,subtree:!0});function n(o){const u={};return o.integrity&&(u.integrity=o.integrity),o.referrerpolicy&&(u.referrerPolicy=o.referrerpolicy),o.crossorigin==="use-credentials"?u.credentials="include":o.crossorigin==="anonymous"?u.credentials="omit":u.credentials="same-origin",u}function a(o){if(o.ep)return;o.ep=!0;const u=n(o);fetch(o.href,u)}})();var bc=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function sH(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var L={exports:{}},L6={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 n="18.2.0",a=Symbol.for("react.element"),o=Symbol.for("react.portal"),u=Symbol.for("react.fragment"),c=Symbol.for("react.strict_mode"),m=Symbol.for("react.profiler"),h=Symbol.for("react.provider"),v=Symbol.for("react.context"),y=Symbol.for("react.forward_ref"),x=Symbol.for("react.suspense"),N=Symbol.for("react.suspense_list"),E=Symbol.for("react.memo"),T=Symbol.for("react.lazy"),k=Symbol.for("react.offscreen"),D=Symbol.iterator,z="@@iterator";function P(S){if(S===null||typeof S!="object")return null;var A=D&&S[D]||S[z];return typeof A=="function"?A:null}var I={current:null},F={transition:null},$={current:null,isBatchingLegacy:!1,didScheduleLegacyUpdate:!1},Y={current:null},Z={},ue=null;function de(S){ue=S}Z.setExtraStackFrame=function(S){ue=S},Z.getCurrentStack=null,Z.getStackAddendum=function(){var S="";ue&&(S+=ue);var A=Z.getCurrentStack;return A&&(S+=A()||""),S};var se=!1,ye=!1,Ye=!1,re=!1,le=!1,ge={ReactCurrentDispatcher:I,ReactCurrentBatchConfig:F,ReactCurrentOwner:Y};ge.ReactDebugCurrentFrame=Z,ge.ReactCurrentActQueue=$;function Ce(S){{for(var A=arguments.length,V=new Array(A>1?A-1:0),G=1;G1?A-1:0),G=1;G1){for(var kt=Array(St),yt=0;yt1){for(var jt=Array(yt),Tt=0;Tt is not supported and will be removed in a future major release. Did you mean to render instead?")),A.Provider},set:function(Ee){A.Provider=Ee}},_currentValue:{get:function(){return A._currentValue},set:function(Ee){A._currentValue=Ee}},_currentValue2:{get:function(){return A._currentValue2},set:function(Ee){A._currentValue2=Ee}},_threadCount:{get:function(){return A._threadCount},set:function(Ee){A._threadCount=Ee}},Consumer:{get:function(){return V||(V=!0,X("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(Ee){ne||(Ce("Setting `displayName` on Context.Consumer has no effect. You should set it directly on the context with Context.displayName = '%s'.",Ee),ne=!0)}}}),A.Consumer=ze}return A._currentRenderer=null,A._currentRenderer2=null,A}var hr=-1,Fi=0,Ba=1,zi=2;function K(S){if(S._status===hr){var A=S._result,V=A();if(V.then(function(ze){if(S._status===Fi||S._status===hr){var Ee=S;Ee._status=Ba,Ee._result=ze}},function(ze){if(S._status===Fi||S._status===hr){var Ee=S;Ee._status=zi,Ee._result=ze}}),S._status===hr){var G=S;G._status=Fi,G._result=V}}if(S._status===Ba){var ne=S._result;return ne===void 0&&X(`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?`,ne),"default"in ne||X(`lazy: Expected the result of a dynamic import() call. Instead received: %s + +Your code should look like: + const MyComponent = lazy(() => import('./MyComponent'))`,ne),ne.default}else throw S._result}function Ue(S){var A={_status:hr,_result:S},V={$$typeof:T,_payload:A,_init:K};{var G,ne;Object.defineProperties(V,{defaultProps:{configurable:!0,get:function(){return G},set:function(ze){X("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=ze,Object.defineProperty(V,"defaultProps",{enumerable:!0})}},propTypes:{configurable:!0,get:function(){return ne},set:function(ze){X("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."),ne=ze,Object.defineProperty(V,"propTypes",{enumerable:!0})}}})}return V}function Ke(S){S!=null&&S.$$typeof===E?X("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."):typeof S!="function"?X("forwardRef requires a render function but was given %s.",S===null?"null":typeof S):S.length!==0&&S.length!==2&&X("forwardRef render functions accept exactly two parameters: props and ref. %s",S.length===1?"Did you forget to use the ref parameter?":"Any additional parameter will be undefined."),S!=null&&(S.defaultProps!=null||S.propTypes!=null)&&X("forwardRef render functions do not support propTypes or defaultProps. Did you accidentally pass a React component?");var A={$$typeof:y,render:S};{var V;Object.defineProperty(A,"displayName",{enumerable:!1,configurable:!0,get:function(){return V},set:function(G){V=G,!S.name&&!S.displayName&&(S.displayName=G)}})}return A}var Ct;Ct=Symbol.for("react.module.reference");function sn(S){return!!(typeof S=="string"||typeof S=="function"||S===u||S===m||le||S===c||S===x||S===N||re||S===k||se||ye||Ye||typeof S=="object"&&S!==null&&(S.$$typeof===T||S.$$typeof===E||S.$$typeof===h||S.$$typeof===v||S.$$typeof===y||S.$$typeof===Ct||S.getModuleId!==void 0))}function xn(S,A){sn(S)||X("memo: The first argument must be a component. Instead received: %s",S===null?"null":typeof S);var V={$$typeof:E,type:S,compare:A===void 0?null:A};{var G;Object.defineProperty(V,"displayName",{enumerable:!1,configurable:!0,get:function(){return G},set:function(ne){G=ne,!S.name&&!S.displayName&&(S.displayName=ne)}})}return V}function nt(){var S=I.current;return S===null&&X(`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.`),S}function Zt(S){var A=nt();if(S._context!==void 0){var V=S._context;V.Consumer===S?X("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===S&&X("Calling useContext(Context.Provider) is not supported. Did you mean to call useContext(Context) instead?")}return A.useContext(S)}function Vn(S){var A=nt();return A.useState(S)}function Bn(S,A,V){var G=nt();return G.useReducer(S,A,V)}function un(S){var A=nt();return A.useRef(S)}function Ur(S,A){var V=nt();return V.useEffect(S,A)}function ba(S,A){var V=nt();return V.useInsertionEffect(S,A)}function Mo(S,A){var V=nt();return V.useLayoutEffect(S,A)}function yi(S,A){var V=nt();return V.useCallback(S,A)}function uo(S,A){var V=nt();return V.useMemo(S,A)}function Au(S,A,V){var G=nt();return G.useImperativeHandle(S,A,V)}function Sa(S,A){{var V=nt();return V.useDebugValue(S,A)}}function Ys(){var S=nt();return S.useTransition()}function Ua(S){var A=nt();return A.useDeferredValue(S)}function en(){var S=nt();return S.useId()}function $a(S,A,V){var G=nt();return G.useSyncExternalStore(S,A,V)}var bi=0,Do,ls,Po,us,cs,Io,Fo;function fs(){}fs.__reactDisabledLog=!0;function qs(){{if(bi===0){Do=console.log,ls=console.info,Po=console.warn,us=console.error,cs=console.group,Io=console.groupCollapsed,Fo=console.groupEnd;var S={configurable:!0,enumerable:!0,value:fs,writable:!0};Object.defineProperties(console,{info:S,log:S,warn:S,error:S,group:S,groupCollapsed:S,groupEnd:S})}bi++}}function Zs(){{if(bi--,bi===0){var S={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:De({},S,{value:Do}),info:De({},S,{value:ls}),warn:De({},S,{value:Po}),error:De({},S,{value:us}),group:De({},S,{value:cs}),groupCollapsed:De({},S,{value:Io}),groupEnd:De({},S,{value:Fo})})}bi<0&&X("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var xa=ge.ReactCurrentDispatcher,Pr;function Bi(S,A,V){{if(Pr===void 0)try{throw Error()}catch(ne){var G=ne.stack.trim().match(/\n( *(at )?)/);Pr=G&&G[1]||""}return` +`+Pr+S}}var Si=!1,Ui;{var ds=typeof WeakMap=="function"?WeakMap:Map;Ui=new ds}function zo(S,A){if(!S||Si)return"";{var V=Ui.get(S);if(V!==void 0)return V}var G;Si=!0;var ne=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var ze;ze=xa.current,xa.current=null,qs();try{if(A){var Ee=function(){throw Error()};if(Object.defineProperty(Ee.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(Ee,[])}catch(zt){G=zt}Reflect.construct(S,[],Ee)}else{try{Ee.call()}catch(zt){G=zt}S.call(Ee.prototype)}}else{try{throw Error()}catch(zt){G=zt}S()}}catch(zt){if(zt&&G&&typeof zt.stack=="string"){for(var Ve=zt.stack.split(` +`),st=G.stack.split(` +`),St=Ve.length-1,kt=st.length-1;St>=1&&kt>=0&&Ve[St]!==st[kt];)kt--;for(;St>=1&&kt>=0;St--,kt--)if(Ve[St]!==st[kt]){if(St!==1||kt!==1)do if(St--,kt--,kt<0||Ve[St]!==st[kt]){var yt=` +`+Ve[St].replace(" at new "," at ");return S.displayName&&yt.includes("")&&(yt=yt.replace("",S.displayName)),typeof S=="function"&&Ui.set(S,yt),yt}while(St>=1&&kt>=0);break}}}finally{Si=!1,xa.current=ze,Zs(),Error.prepareStackTrace=ne}var jt=S?S.displayName||S.name:"",Tt=jt?Bi(jt):"";return typeof S=="function"&&Ui.set(S,Tt),Tt}function ps(S,A,V){return zo(S,!1)}function Il(S){var A=S.prototype;return!!(A&&A.isReactComponent)}function xi(S,A,V){if(S==null)return"";if(typeof S=="function")return zo(S,Il(S));if(typeof S=="string")return Bi(S);switch(S){case x:return Bi("Suspense");case N:return Bi("SuspenseList")}if(typeof S=="object")switch(S.$$typeof){case y:return ps(S.render);case E:return xi(S.type,A,V);case T:{var G=S,ne=G._payload,ze=G._init;try{return xi(ze(ne),A,V)}catch{}}}return""}var Bo={},$i=ge.ReactDebugCurrentFrame;function Ca(S){if(S){var A=S._owner,V=xi(S.type,S._source,A?A.type:null);$i.setExtraStackFrame(V)}else $i.setExtraStackFrame(null)}function Ks(S,A,V,G,ne){{var ze=Function.call.bind(on);for(var Ee in S)if(ze(S,Ee)){var Ve=void 0;try{if(typeof S[Ee]!="function"){var st=Error((G||"React class")+": "+V+" type `"+Ee+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof S[Ee]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw st.name="Invariant Violation",st}Ve=S[Ee](A,Ee,G,V,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(St){Ve=St}Ve&&!(Ve instanceof Error)&&(Ca(ne),X("%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,Ee,typeof Ve),Ca(null)),Ve instanceof Error&&!(Ve.message in Bo)&&(Bo[Ve.message]=!0,Ca(ne),X("Failed %s type: %s",V,Ve.message),Ca(null))}}}function cn(S){if(S){var A=S._owner,V=xi(S.type,S._source,A?A.type:null);de(V)}else de(null)}var wa;wa=!1;function Uo(){if(Y.current){var S=Pt(Y.current.type);if(S)return` + +Check the render method of \``+S+"`."}return""}function $t(S){if(S!==void 0){var A=S.fileName.replace(/^.*[\\\/]/,""),V=S.lineNumber;return` + +Check your code at `+A+":"+V+"."}return""}function Xs(S){return S!=null?$t(S.__source):""}var Sr={};function ja(S){var A=Uo();if(!A){var V=typeof S=="string"?S:S.displayName||S.name;V&&(A=` + +Check the top-level render call using <`+V+">.")}return A}function Ki(S,A){if(!(!S._store||S._store.validated||S.key!=null)){S._store.validated=!0;var V=ja(A);if(!Sr[V]){Sr[V]=!0;var G="";S&&S._owner&&S._owner!==Y.current&&(G=" It was passed a child from "+Pt(S._owner.type)+"."),cn(S),X('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),cn(null)}}}function co(S,A){if(typeof S=="object"){if(Wt(S))for(var V=0;V",ne=" Did you accidentally export a JSX literal instead of a component?"):Ee=typeof S,X("React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",Ee,ne)}var Ve=at.apply(this,arguments);if(Ve==null)return Ve;if(G)for(var st=2;st10&&Ce("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 fo=!1,Na=null;function Qs(S){if(Na===null)try{var A=("require"+Math.random()).slice(0,7),V=e&&e[A];Na=V.call(e,"timers").setImmediate}catch{Na=function(ne){fo===!1&&(fo=!0,typeof MessageChannel>"u"&&X("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 ze=new MessageChannel;ze.port1.onmessage=ne,ze.port2.postMessage(void 0)}}return Na(S)}var vn=0,In=!1;function Fl(S){{var A=vn;vn++,$.current===null&&($.current=[]);var V=$.isBatchingLegacy,G;try{if($.isBatchingLegacy=!0,G=S(),!V&&$.didScheduleLegacyUpdate){var ne=$.current;ne!==null&&($.didScheduleLegacyUpdate=!1,pe(ne))}}catch(jt){throw ji(A),jt}finally{$.isBatchingLegacy=V}if(G!==null&&typeof G=="object"&&typeof G.then=="function"){var ze=G,Ee=!1,Ve={then:function(jt,Tt){Ee=!0,ze.then(function(zt){ji(A),vn===0?W(zt,jt,Tt):jt(zt)},function(zt){ji(A),Tt(zt)})}};return!In&&typeof Promise<"u"&&Promise.resolve().then(function(){}).then(function(){Ee||(In=!0,X("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 () => ...);"))}),Ve}else{var st=G;if(ji(A),vn===0){var St=$.current;St!==null&&(pe(St),$.current=null);var kt={then:function(jt,Tt){$.current===null?($.current=[],W(st,jt,Tt)):jt(st)}};return kt}else{var yt={then:function(jt,Tt){jt(st)}};return yt}}}}function ji(S){S!==vn-1&&X("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. "),vn=S}function W(S,A,V){{var G=$.current;if(G!==null)try{pe(G),Qs(function(){G.length===0?($.current=null,A(S)):W(S,A,V)})}catch(ne){V(ne)}else A(S)}}var J=!1;function pe(S){if(!J){J=!0;var A=0;try{for(;A0;){var Jt=hn-1>>>1,_n=je[Jt];if(v(_n,at)>0)je[Jt]=at,je[hn]=_n,hn=Jt;else return}}function h(je,at,_t){for(var hn=_t,Jt=je.length,_n=Jt>>>1;hn<_n;){var Pn=(hn+1)*2-1,Lr=je[Pn],kn=Pn+1,vi=je[kn];if(v(Lr,at)<0)kn_t&&(!je||$n()));){var hn=re.callback;if(typeof hn=="function"){re.callback=null,le=re.priorityLevel;var Jt=re.expirationTime<=_t,_n=hn(Jt);_t=e.unstable_now(),typeof _n=="function"?re.callback=_n:re===u(se)&&c(se),Ne(_t)}else c(se);re=u(se)}if(re!==null)return!0;var Pn=u(ye);return Pn!==null&&Lt(De,Pn.startTime-_t),!1}function it(je,at){switch(je){case y:case x:case N:case E:case T:break;default:je=N}var _t=le;le=je;try{return at()}finally{le=_t}}function gt(je){var at;switch(le){case y:case x:case N:at=N;break;default:at=le;break}var _t=le;le=at;try{return je()}finally{le=_t}}function Ht(je){var at=le;return function(){var _t=le;le=at;try{return je.apply(this,arguments)}finally{le=_t}}}function Xe(je,at,_t){var hn=e.unstable_now(),Jt;if(typeof _t=="object"&&_t!==null){var _n=_t.delay;typeof _n=="number"&&_n>0?Jt=hn+_n:Jt=hn}else Jt=hn;var Pn;switch(je){case y:Pn=$;break;case x:Pn=Y;break;case T:Pn=de;break;case E:Pn=ue;break;case N:default:Pn=Z;break}var Lr=Jt+Pn,kn={id:Ye++,callback:at,priorityLevel:je,startTime:Jt,expirationTime:Lr,sortIndex:-1};return Jt>hn?(kn.sortIndex=Jt,o(ye,kn),u(se)===null&&kn===u(ye)&&(X?Oe():X=!0,Lt(De,Jt-hn))):(kn.sortIndex=Lr,o(se,kn),!Ce&&!ge&&(Ce=!0,nn(Pe))),kn}function ct(){}function wt(){!Ce&&!ge&&(Ce=!0,nn(Pe))}function Ft(){return u(se)}function Ge(je){je.callback=null}function Wt(){return le}var Se=!1,et=null,Nt=-1,lt=a,Sn=-1;function $n(){var je=e.unstable_now()-Sn;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?lt=Math.floor(1e3/je):lt=a}var Ln=function(){if(et!==null){var je=e.unstable_now();Sn=je;var at=!0,_t=!0;try{_t=et(at,je)}finally{_t?gn():(Se=!1,et=null)}}else Se=!1},gn;if(typeof me=="function")gn=function(){me(Ln)};else if(typeof MessageChannel<"u"){var He=new MessageChannel,Je=He.port2;He.port1.onmessage=Ln,gn=function(){Je.postMessage(null)}}else gn=function(){be(Ln,0)};function nn(je){et=je,Se||(Se=!0,gn())}function Lt(je,at){Nt=be(function(){je(e.unstable_now())},at)}function Oe(){we(Nt),Nt=-1}var qt=Pt,En=null;e.unstable_IdlePriority=T,e.unstable_ImmediatePriority=y,e.unstable_LowPriority=E,e.unstable_NormalPriority=N,e.unstable_Profiling=En,e.unstable_UserBlockingPriority=x,e.unstable_cancelCallback=Ge,e.unstable_continueExecution=wt,e.unstable_forceFrameRate=on,e.unstable_getCurrentPriorityLevel=Wt,e.unstable_getFirstCallbackNode=Ft,e.unstable_next=gt,e.unstable_pauseExecution=ct,e.unstable_requestPaint=qt,e.unstable_runWithPriority=it,e.unstable_scheduleCallback=Xe,e.unstable_shouldYield=$n,e.unstable_wrapCallback=Ht,typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error)})()})(qM);(function(e){e.exports=qM})(YM);/** + * @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=L.exports,t=YM.exports,n=e.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,a=!1;function o(r){a=r}function u(r){if(!a){for(var i=arguments.length,s=new Array(i>1?i-1:0),f=1;f1?i-1:0),f=1;f2&&(r[0]==="o"||r[0]==="O")&&(r[1]==="n"||r[1]==="N")}function Lr(r,i,s,f){if(s!==null&&s.type===He)return!1;switch(typeof i){case"function":case"symbol":return!0;case"boolean":{if(f)return!1;if(s!==null)return!s.acceptsBooleans;var p=r.toLowerCase().slice(0,5);return p!=="data-"&&p!=="aria-"}default:return!1}}function kn(r,i,s,f){if(i===null||typeof i>"u"||Lr(r,i,s,f))return!0;if(f)return!1;if(s!==null)switch(s.type){case Lt:return!i;case Oe:return i===!1;case qt:return isNaN(i);case En:return isNaN(i)||i<1}return!1}function vi(r){return Tn.hasOwnProperty(r)?Tn[r]:null}function jn(r,i,s,f,p,b,C){this.acceptsBooleans=i===nn||i===Lt||i===Oe,this.attributeName=f,this.attributeNamespace=p,this.mustUseProperty=s,this.propertyName=r,this.type=i,this.sanitizeURL=b,this.removeEmptyString=C}var Tn={},gi=["children","dangerouslySetInnerHTML","defaultValue","defaultChecked","innerHTML","suppressContentEditableWarning","suppressHydrationWarning","style"];gi.forEach(function(r){Tn[r]=new jn(r,He,!1,r,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(r){var i=r[0],s=r[1];Tn[i]=new jn(i,Je,!1,s,null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(r){Tn[r]=new jn(r,nn,!1,r.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(r){Tn[r]=new jn(r,nn,!1,r,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(r){Tn[r]=new jn(r,Lt,!1,r.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(r){Tn[r]=new jn(r,Lt,!0,r,null,!1,!1)}),["capture","download"].forEach(function(r){Tn[r]=new jn(r,Oe,!1,r,null,!1,!1)}),["cols","rows","size","span"].forEach(function(r){Tn[r]=new jn(r,En,!1,r,null,!1,!1)}),["rowSpan","start"].forEach(function(r){Tn[r]=new jn(r,qt,!1,r.toLowerCase(),null,!1,!1)});var br=/[\-\:]([a-z])/g,Lo=function(r){return r[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(r){var i=r.replace(br,Lo);Tn[i]=new jn(i,Je,!1,r,null,!1,!1)}),["xlink:actuate","xlink:arcrole","xlink:role","xlink:show","xlink:title","xlink:type"].forEach(function(r){var i=r.replace(br,Lo);Tn[i]=new jn(i,Je,!1,r,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(r){var i=r.replace(br,Lo);Tn[i]=new jn(i,Je,!1,r,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(r){Tn[r]=new jn(r,Je,!1,r.toLowerCase(),null,!1,!1)});var os="xlinkHref";Tn[os]=new jn("xlinkHref",Je,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(r){Tn[r]=new jn(r,Je,!1,r.toLowerCase(),null,!0,!0)});var ss=/^[\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(r){!ko&&ss.test(r)&&(ko=!0,c("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(r)))}function hr(r,i,s,f){if(f.mustUseProperty){var p=f.propertyName;return r[p]}else{Sn(s,i),f.sanitizeURL&&Oo(""+s);var b=f.attributeName,C=null;if(f.type===Oe){if(r.hasAttribute(b)){var R=r.getAttribute(b);return R===""?!0:kn(i,s,f,!1)?R:R===""+s?s:R}}else if(r.hasAttribute(b)){if(kn(i,s,f,!1))return r.getAttribute(b);if(f.type===Lt)return s;C=r.getAttribute(b)}return kn(i,s,f,!1)?C===null?s:C:C===""+s?s:C}}function Fi(r,i,s,f){{if(!_n(i))return;if(!r.hasAttribute(i))return s===void 0?void 0:null;var p=r.getAttribute(i);return Sn(s,i),p===""+s?s:p}}function Ba(r,i,s,f){var p=vi(i);if(!Pn(i,p,f)){if(kn(i,s,p,f)&&(s=null),f||p===null){if(_n(i)){var b=i;s===null?r.removeAttribute(b):(Sn(s,i),r.setAttribute(b,""+s))}return}var C=p.mustUseProperty;if(C){var R=p.propertyName;if(s===null){var O=p.type;r[R]=O===Lt?!1:""}else r[R]=s;return}var U=p.attributeName,H=p.attributeNamespace;if(s===null)r.removeAttribute(U);else{var te=p.type,ee;te===Lt||te===Oe&&s===!0?ee="":(Sn(s,U),ee=""+s,p.sanitizeURL&&Oo(ee.toString())),H?r.setAttributeNS(H,U,ee):r.setAttribute(U,ee)}}}var zi=Symbol.for("react.element"),K=Symbol.for("react.portal"),Ue=Symbol.for("react.fragment"),Ke=Symbol.for("react.strict_mode"),Ct=Symbol.for("react.profiler"),sn=Symbol.for("react.provider"),xn=Symbol.for("react.context"),nt=Symbol.for("react.forward_ref"),Zt=Symbol.for("react.suspense"),Vn=Symbol.for("react.suspense_list"),Bn=Symbol.for("react.memo"),un=Symbol.for("react.lazy"),Ur=Symbol.for("react.scope"),ba=Symbol.for("react.debug_trace_mode"),Mo=Symbol.for("react.offscreen"),yi=Symbol.for("react.legacy_hidden"),uo=Symbol.for("react.cache"),Au=Symbol.for("react.tracing_marker"),Sa=Symbol.iterator,Ys="@@iterator";function Ua(r){if(r===null||typeof r!="object")return null;var i=Sa&&r[Sa]||r[Ys];return typeof i=="function"?i:null}var en=Object.assign,$a=0,bi,Do,ls,Po,us,cs,Io;function Fo(){}Fo.__reactDisabledLog=!0;function fs(){{if($a===0){bi=console.log,Do=console.info,ls=console.warn,Po=console.error,us=console.group,cs=console.groupCollapsed,Io=console.groupEnd;var r={configurable:!0,enumerable:!0,value:Fo,writable:!0};Object.defineProperties(console,{info:r,log:r,warn:r,error:r,group:r,groupCollapsed:r,groupEnd:r})}$a++}}function qs(){{if($a--,$a===0){var r={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:en({},r,{value:bi}),info:en({},r,{value:Do}),warn:en({},r,{value:ls}),error:en({},r,{value:Po}),group:en({},r,{value:us}),groupCollapsed:en({},r,{value:cs}),groupEnd:en({},r,{value:Io})})}$a<0&&c("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var Zs=n.ReactCurrentDispatcher,xa;function Pr(r,i,s){{if(xa===void 0)try{throw Error()}catch(p){var f=p.stack.trim().match(/\n( *(at )?)/);xa=f&&f[1]||""}return` +`+xa+r}}var Bi=!1,Si;{var Ui=typeof WeakMap=="function"?WeakMap:Map;Si=new Ui}function ds(r,i){if(!r||Bi)return"";{var s=Si.get(r);if(s!==void 0)return s}var f;Bi=!0;var p=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var b;b=Zs.current,Zs.current=null,fs();try{if(i){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(r,[],C)}else{try{C.call()}catch(ve){f=ve}r.call(C.prototype)}}else{try{throw Error()}catch(ve){f=ve}r()}}catch(ve){if(ve&&f&&typeof ve.stack=="string"){for(var R=ve.stack.split(` +`),O=f.stack.split(` +`),U=R.length-1,H=O.length-1;U>=1&&H>=0&&R[U]!==O[H];)H--;for(;U>=1&&H>=0;U--,H--)if(R[U]!==O[H]){if(U!==1||H!==1)do if(U--,H--,H<0||R[U]!==O[H]){var te=` +`+R[U].replace(" at new "," at ");return r.displayName&&te.includes("")&&(te=te.replace("",r.displayName)),typeof r=="function"&&Si.set(r,te),te}while(U>=1&&H>=0);break}}}finally{Bi=!1,Zs.current=b,qs(),Error.prepareStackTrace=p}var ee=r?r.displayName||r.name:"",he=ee?Pr(ee):"";return typeof r=="function"&&Si.set(r,he),he}function zo(r,i,s){return ds(r,!0)}function ps(r,i,s){return ds(r,!1)}function Il(r){var i=r.prototype;return!!(i&&i.isReactComponent)}function xi(r,i,s){if(r==null)return"";if(typeof r=="function")return ds(r,Il(r));if(typeof r=="string")return Pr(r);switch(r){case Zt:return Pr("Suspense");case Vn:return Pr("SuspenseList")}if(typeof r=="object")switch(r.$$typeof){case nt:return ps(r.render);case Bn:return xi(r.type,i,s);case un:{var f=r,p=f._payload,b=f._init;try{return xi(b(p),i,s)}catch{}}}return""}function Bo(r){switch(r._debugOwner&&r._debugOwner.type,r._debugSource,r.tag){case E:return Pr(r.type);case ue:return Pr("Lazy");case $:return Pr("Suspense");case ye:return Pr("SuspenseList");case h:case y:case Z:return ps(r.type);case I:return ps(r.type.render);case v:return zo(r.type);default:return""}}function $i(r){try{var i="",s=r;do i+=Bo(s),s=s.return;while(s);return i}catch(f){return` +Error generating stack: `+f.message+` +`+f.stack}}function Ca(r,i,s){var f=r.displayName;if(f)return f;var p=i.displayName||i.name||"";return p!==""?s+"("+p+")":s}function Ks(r){return r.displayName||"Context"}function cn(r){if(r==null)return null;if(typeof r.tag=="number"&&c("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),typeof r=="function")return r.displayName||r.name||null;if(typeof r=="string")return r;switch(r){case Ue:return"Fragment";case K:return"Portal";case Ct:return"Profiler";case Ke:return"StrictMode";case Zt:return"Suspense";case Vn:return"SuspenseList"}if(typeof r=="object")switch(r.$$typeof){case xn:var i=r;return Ks(i)+".Consumer";case sn:var s=r;return Ks(s._context)+".Provider";case nt:return Ca(r,r.render,"ForwardRef");case Bn:var f=r.displayName||null;return f!==null?f:cn(r.type)||"Memo";case un:{var p=r,b=p._payload,C=p._init;try{return cn(C(b))}catch{return null}}}return null}function wa(r,i,s){var f=i.displayName||i.name||"";return r.displayName||(f!==""?s+"("+f+")":s)}function Uo(r){return r.displayName||"Context"}function $t(r){var i=r.tag,s=r.type;switch(i){case ge:return"Cache";case z:var f=s;return Uo(f)+".Consumer";case P:var p=s;return Uo(p._context)+".Provider";case se:return"DehydratedFragment";case I:return wa(s,s.render,"ForwardRef");case k:return"Fragment";case E:return s;case N:return"Portal";case x:return"Root";case T:return"Text";case ue:return cn(s);case D:return s===Ke?"StrictMode":"Mode";case re:return"Offscreen";case F:return"Profiler";case Ye:return"Scope";case $:return"Suspense";case ye:return"SuspenseList";case Ce:return"TracingMarker";case v:case h:case de:case y:case Y:case Z:if(typeof s=="function")return s.displayName||s.name||null;if(typeof s=="string")return s;break}return null}var Xs=n.ReactDebugCurrentFrame,Sr=null,ja=!1;function Ki(){{if(Sr===null)return null;var r=Sr._debugOwner;if(r!==null&&typeof r<"u")return $t(r)}return null}function co(){return Sr===null?"":$i(Sr)}function _r(){Xs.getCurrentStack=null,Sr=null,ja=!1}function ar(r){Xs.getCurrentStack=r===null?null:co,Sr=r,ja=!1}function $o(){return Sr}function qr(r){ja=r}function mr(r){return""+r}function ri(r){switch(typeof r){case"boolean":case"number":case"string":case"undefined":return r;case"object":return gn(r),r;default:return""}}var Lu={button:!0,checkbox:!0,image:!0,hidden:!0,radio:!0,reset:!0,submit:!0};function fo(r,i){Lu[i.type]||i.onChange||i.onInput||i.readOnly||i.disabled||i.value==null||c("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`."),i.onChange||i.readOnly||i.disabled||i.checked==null||c("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 Na(r){var i=r.type,s=r.nodeName;return s&&s.toLowerCase()==="input"&&(i==="checkbox"||i==="radio")}function Qs(r){return r._valueTracker}function vn(r){r._valueTracker=null}function In(r){var i="";return r&&(Na(r)?i=r.checked?"true":"false":i=r.value),i}function Fl(r){var i=Na(r)?"checked":"value",s=Object.getOwnPropertyDescriptor(r.constructor.prototype,i);gn(r[i]);var f=""+r[i];if(!(r.hasOwnProperty(i)||typeof s>"u"||typeof s.get!="function"||typeof s.set!="function")){var p=s.get,b=s.set;Object.defineProperty(r,i,{configurable:!0,get:function(){return p.call(this)},set:function(R){gn(R),f=""+R,b.call(this,R)}}),Object.defineProperty(r,i,{enumerable:s.enumerable});var C={getValue:function(){return f},setValue:function(R){gn(R),f=""+R},stopTracking:function(){vn(r),delete r[i]}};return C}}function ji(r){Qs(r)||(r._valueTracker=Fl(r))}function W(r){if(!r)return!1;var i=Qs(r);if(!i)return!0;var s=i.getValue(),f=In(r);return f!==s?(i.setValue(f),!0):!1}function J(r){if(r=r||(typeof document<"u"?document:void 0),typeof r>"u")return null;try{return r.activeElement||r.body}catch{return r.body}}var pe=!1,ot=!1,fn=!1,Fn=!1;function Kt(r){var i=r.type==="checkbox"||r.type==="radio";return i?r.checked!=null:r.value!=null}function S(r,i){var s=r,f=i.checked,p=en({},i,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:f??s._wrapperState.initialChecked});return p}function A(r,i){fo("input",i),i.checked!==void 0&&i.defaultChecked!==void 0&&!ot&&(c("%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",Ki()||"A component",i.type),ot=!0),i.value!==void 0&&i.defaultValue!==void 0&&!pe&&(c("%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",Ki()||"A component",i.type),pe=!0);var s=r,f=i.defaultValue==null?"":i.defaultValue;s._wrapperState={initialChecked:i.checked!=null?i.checked:i.defaultChecked,initialValue:ri(i.value!=null?i.value:f),controlled:Kt(i)}}function V(r,i){var s=r,f=i.checked;f!=null&&Ba(s,"checked",f,!1)}function G(r,i){var s=r;{var f=Kt(i);!s._wrapperState.controlled&&f&&!Fn&&(c("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&&!fn&&(c("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"),fn=!0)}V(r,i);var p=ri(i.value),b=i.type;if(p!=null)b==="number"?(p===0&&s.value===""||s.value!=p)&&(s.value=mr(p)):s.value!==mr(p)&&(s.value=mr(p));else if(b==="submit"||b==="reset"){s.removeAttribute("value");return}i.hasOwnProperty("value")?Ve(s,i.type,p):i.hasOwnProperty("defaultValue")&&Ve(s,i.type,ri(i.defaultValue)),i.checked==null&&i.defaultChecked!=null&&(s.defaultChecked=!!i.defaultChecked)}function ne(r,i,s){var f=r;if(i.hasOwnProperty("value")||i.hasOwnProperty("defaultValue")){var p=i.type,b=p==="submit"||p==="reset";if(b&&(i.value===void 0||i.value===null))return;var C=mr(f._wrapperState.initialValue);s||C!==f.value&&(f.value=C),f.defaultValue=C}var R=f.name;R!==""&&(f.name=""),f.defaultChecked=!f.defaultChecked,f.defaultChecked=!!f._wrapperState.initialChecked,R!==""&&(f.name=R)}function ze(r,i){var s=r;G(s,i),Ee(s,i)}function Ee(r,i){var s=i.name;if(i.type==="radio"&&s!=null){for(var f=r;f.parentNode;)f=f.parentNode;Sn(s,"name");for(var p=f.querySelectorAll("input[name="+JSON.stringify(""+s)+'][type="radio"]'),b=0;b.")))}):i.dangerouslySetInnerHTML!=null&&(kt||(kt=!0,c("Pass a `value` prop if you set dangerouslyInnerHTML so React knows which value should be selected.")))),i.selected!=null&&!st&&(c("Use the `defaultValue` or `value` props on must be a scalar value if `multiple` is false.%s",s,Ci())}}}}function On(r,i,s,f){var p=r.options;if(i){for(var b=s,C={},R=0;R.");var f=en({},i,{value:void 0,defaultValue:void 0,children:mr(s._wrapperState.initialValue)});return f}function Rv(r,i){var s=r;fo("textarea",i),i.value!==void 0&&i.defaultValue!==void 0&&!my&&(c("%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",Ki()||"A component"),my=!0);var f=i.value;if(f==null){var p=i.children,b=i.defaultValue;if(p!=null){c("Use the `defaultValue` or `value` props instead of setting children on