import os import re import requests import tempfile import gradio as gr from PIL import Image, ImageDraw from config import theme from public.data.images.loras.flux1 import loras as flux1_loras # os.makedirs(os.getenv("HF_HOME"), exist_ok=True) # UI with gr.Blocks( theme=theme, fill_width=True, css_paths=[os.path.join("static/css", f) for f in os.listdir("static/css")], ) as demo: # States data_state = gr.State() local_state = gr.BrowserState( { "selected_loras": [], } ) with gr.Row(): with gr.Column(scale=1): gr.Label("AllFlux", show_label=False) with gr.Accordion("Settings", open=True): with gr.Group(): height_slider = gr.Slider( minimum=64, maximum=2048, value=1024, step=64, label="Height", interactive=True, ) width_slider = gr.Slider( minimum=64, maximum=2048, value=1024, step=64, label="Width", interactive=True, ) with gr.Group(): num_images_slider = gr.Slider( minimum=1, maximum=4, value=1, step=1, label="Number of Images", interactive=True, ) toggles = gr.CheckboxGroup( choices=["Realtime", "Randomize Seed"], value=["Randomize Seed"], show_label=False, interactive=True, ) with gr.Accordion("Advanced", open=False): num_steps_slider = gr.Slider( minimum=1, maximum=100, value=20, step=1, label="Steps", interactive=True, ) guidance_scale_slider = gr.Slider( minimum=1, maximum=10, value=3.5, step=0.1, label="Guidance Scale", interactive=True, ) seed_slider = gr.Slider( minimum=0, maximum=4294967295, value=42, step=1, label="Seed", interactive=True, ) upscale_slider = gr.Slider( minimum=2, maximum=4, value=2, step=2, label="Upscale", interactive=True, ) scheduler_dropdown = gr.Dropdown( label="Scheduler", choices=[ "Euler a", "Euler", "LMS", "Heun", "DPM++ 2", "DPM++ 2 a", "DPM++ SDE", "DPM++ SDE Karras", "DDIM", "PLMS", ], value="Euler a", interactive=True, ) gr.LoginButton() gr.Markdown( """ Yurrrrrrrrrrrr, WIP """ ) with gr.Column(scale=3): with gr.Group(): with gr.Row(): prompt = gr.Textbox( show_label=False, placeholder="Enter your prompt here...", lines=3, ) with gr.Row(): with gr.Column(scale=3): submit_btn = gr.Button("Submit") with gr.Column(scale=1): ai_improve_btn = gr.Button("💡", link="#improve-prompt") with gr.Group(): output_gallery = gr.Gallery( label="Outputs", interactive=False, height=500 ) with gr.Row(): upscale_selected_btn = gr.Button("Upscale Selected", size="sm") upscale_all_btn = gr.Button("Upscale All", size="sm") create_similar_btn = gr.Button("Create Similar", size="sm") with gr.Accordion("Output History", open=False): with gr.Group(): output_history_gallery = gr.Gallery( show_label=False, interactive=False, height=500 ) with gr.Row(): clear_history_btn = gr.Button("Clear All", size="sm") download_history_btn = gr.Button("Download All", size="sm") with gr.Accordion("Image Playground", open=True): def show_info(content: str | None = None): info_checkbox = gr.Checkbox( value=False, label="Show Info", interactive=True ) @gr.render(inputs=info_checkbox) def show_info(info_checkbox): return ( gr.Markdown( f"""Sup, need some help here, please check the community tab. {content}""" ) if info_checkbox else None ) with gr.Tabs(): with gr.Tab("Img 2 Img"): with gr.Group(): img2img_img = gr.Image(show_label=False, interactive=True) img2img_strength_slider = gr.Slider( minimum=0, maximum=1, value=1.0, step=0.1, label="Strength", interactive=True, ) show_info() with gr.Tab("Inpaint"): with gr.Group(): inpaint_img = gr.ImageMask( show_label=False, interactive=True, type="pil" ) generate_mask_btn = gr.Button( "Remove Background", size="sm" ) use_fill_pipe_inpaint = gr.Checkbox( value=True, label="Use Fill Pipeline 🧪", interactive=True, ) show_info() inpaint_img.upload( fn=lambda x: ( gr.update(height=x["layers"][0].height + 96) if x is not None else None ), inputs=inpaint_img, outputs=inpaint_img, ) with gr.Tab("Outpaint"): outpaint_img = gr.Image( show_label=False, interactive=True, type="pil" ) with gr.Row(equal_height=True): with gr.Column(scale=3): ratio_9_16 = gr.Radio( label="Image Ratio", choices=["9:16", "16:9", "1:1", "Height & Width"], value="9:16", container=True, interactive=True, ) with gr.Column(scale=1): mask_position = gr.Dropdown( choices=[ "Middle", "Left", "Right", "Top", "Bottom", ], value="Middle", label="Alignment", interactive=True, ) with gr.Group(): resize_options = gr.Radio( choices=["Full", "75%", "50%", "33%", "25%", "Custom"], value="Full", label="Resize", interactive=True, ) resize_option_custom = gr.State() @gr.render(inputs=resize_options) def resize_options_render(resize_option): if resize_option == "Custom": resize_option_custom = gr.Slider( minimum=1, maximum=100, value=50, step=1, label="Custom Size %", interactive=True, ) with gr.Accordion("Advanced settings", open=False): with gr.Group(): mask_overlap_slider = gr.Slider( label="Mask Overlap %", minimum=1, maximum=50, value=10, step=1, interactive=True, ) with gr.Row(): overlap_top = gr.Checkbox( value=True, label="Overlap Top", interactive=True, ) overlap_right = gr.Checkbox( value=True, label="Overlap Right", interactive=True, ) with gr.Row(): overlap_left = gr.Checkbox( value=True, label="Overlap Left", interactive=True, ) overlap_bottom = gr.Checkbox( value=True, label="Overlap Bottom", interactive=True, ) mask_preview_btn = gr.Button( "Preview", interactive=True ) mask_preview_img = gr.Image( show_label=False, visible=False, interactive=True ) def prepare_image_and_mask( image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom, ): target_size = (width, height) scale_factor = min( target_size[0] / image.width, target_size[1] / image.height, ) new_width = int(image.width * scale_factor) new_height = int(image.height * scale_factor) source = image.resize( (new_width, new_height), Image.LANCZOS ) if resize_option == "Full": resize_percentage = 100 elif resize_option == "75%": resize_percentage = 75 elif resize_option == "50%": resize_percentage = 50 elif resize_option == "33%": resize_percentage = 33 elif resize_option == "25%": resize_percentage = 25 else: # Custom resize_percentage = custom_resize_percentage # Calculate new dimensions based on percentage resize_factor = resize_percentage / 100 new_width = int(source.width * resize_factor) new_height = int(source.height * resize_factor) # Ensure minimum size of 64 pixels new_width = max(new_width, 64) new_height = max(new_height, 64) # Resize the image source = source.resize( (new_width, new_height), Image.LANCZOS ) # Calculate the overlap in pixels based on the percentage overlap_x = int(new_width * (overlap_percentage / 100)) overlap_y = int(new_height * (overlap_percentage / 100)) # Ensure minimum overlap of 1 pixel overlap_x = max(overlap_x, 1) overlap_y = max(overlap_y, 1) # Calculate margins based on alignment if alignment == "Middle": margin_x = (target_size[0] - new_width) // 2 margin_y = (target_size[1] - new_height) // 2 elif alignment == "Left": margin_x = 0 margin_y = (target_size[1] - new_height) // 2 elif alignment == "Right": margin_x = target_size[0] - new_width margin_y = (target_size[1] - new_height) // 2 elif alignment == "Top": margin_x = (target_size[0] - new_width) // 2 margin_y = 0 elif alignment == "Bottom": margin_x = (target_size[0] - new_width) // 2 margin_y = target_size[1] - new_height # Adjust margins to eliminate gaps margin_x = max( 0, min(margin_x, target_size[0] - new_width) ) margin_y = max( 0, min(margin_y, target_size[1] - new_height) ) # Create a new background image and paste the resized source image background = Image.new( "RGB", target_size, (255, 255, 255) ) background.paste(source, (margin_x, margin_y)) # Create the mask mask = Image.new("L", target_size, 255) mask_draw = ImageDraw.Draw(mask) # Calculate overlap areas white_gaps_patch = 2 left_overlap = ( margin_x + overlap_x if overlap_left else margin_x + white_gaps_patch ) right_overlap = ( margin_x + new_width - overlap_x if overlap_right else margin_x + new_width - white_gaps_patch ) top_overlap = ( margin_y + overlap_y if overlap_top else margin_y + white_gaps_patch ) bottom_overlap = ( margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height - white_gaps_patch ) if alignment == "Left": left_overlap = ( margin_x + overlap_x if overlap_left else margin_x ) elif alignment == "Right": right_overlap = ( margin_x + new_width - overlap_x if overlap_right else margin_x + new_width ) elif alignment == "Top": top_overlap = ( margin_y + overlap_y if overlap_top else margin_y ) elif alignment == "Bottom": bottom_overlap = ( margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height ) # Draw the mask mask_draw.rectangle( [ (left_overlap, top_overlap), (right_overlap, bottom_overlap), ], fill=0, ) return background, mask mask_preview_btn.click( fn=prepare_image_and_mask, inputs=[ outpaint_img, width_slider, height_slider, mask_overlap_slider, resize_options, resize_option_custom, mask_position, overlap_left, overlap_right, overlap_top, overlap_bottom, ], outputs=[mask_preview_img, outpaint_img], ) mask_preview_img.clear( fn=lambda: gr.update(visible=False), outputs=mask_preview_img, ) use_fill_pipe_outpaint = gr.Checkbox( value=True, label="Use Fill Pipeline 🧪", interactive=True, ) show_info() with gr.Tab("In-Context"): with gr.Group(): incontext_img = gr.Image(show_label=False, interactive=True) # https://huggingface.co/spaces/Yuanshi/OminiControl show_info(content="1024 res is in beta") with gr.Tab("IP-Adapter"): with gr.Group(): ip_adapter_img = gr.Image( show_label=False, interactive=True ) ip_adapter_img_scale = gr.Slider( minimum=0, maximum=1, value=0.7, step=0.1, label="Scale", interactive=True, ) # https://huggingface.co/InstantX/FLUX.1-dev-IP-Adapter show_info(content="1024 res is in beta") with gr.Tab("Canny"): with gr.Group(): canny_img = gr.Image(show_label=False, interactive=True) with gr.Row(equal_height=True): with gr.Column(scale=3): canny_controlnet_conditioning_scale = gr.Slider( minimum=0, maximum=1, value=0.65, step=0.05, label="ControlNet Conditioning Scale", interactive=True, ) with gr.Column(scale=1): canny_img_is_preprocessed = gr.Checkbox( value=True, label="Preprocessed", interactive=True, ) with gr.Tab("Tile"): with gr.Group(): tile_img = gr.Image(show_label=False, interactive=True) with gr.Row(equal_height=True): with gr.Column(scale=3): tile_controlnet_conditioning_scale = gr.Slider( minimum=0, maximum=1, value=0.45, step=0.05, label="ControlNet Conditioning Scale", interactive=True, ) with gr.Column(scale=1): tile_img_is_preprocessed = gr.Checkbox( value=True, label="Preprocessed", interactive=True, ) with gr.Tab("Depth"): with gr.Group(): depth_img = gr.Image(show_label=False, interactive=True) with gr.Row(equal_height=True): with gr.Column(scale=3): depth_controlnet_conditioning_scale = gr.Slider( minimum=0, maximum=1, value=0.55, step=0.05, label="ControlNet Conditioning Scale", interactive=True, ) with gr.Column(scale=1): depth_img_is_preprocessed = gr.Checkbox( value=True, label="Preprocessed", interactive=True, ) with gr.Tab("Blur"): with gr.Group(): blur_img = gr.Image(show_label=False, interactive=True) with gr.Row(equal_height=True): with gr.Column(scale=3): blur_controlnet_conditioning_scale = gr.Slider( minimum=0, maximum=1, value=0.45, step=0.05, label="ControlNet Conditioning Scale", interactive=True, ) with gr.Column(scale=1): blur_img_is_preprocessed = gr.Checkbox( value=True, label="Preprocessed", interactive=True, ) with gr.Tab("Pose"): with gr.Group(): pose_img = gr.Image(show_label=False, interactive=True) with gr.Row(equal_height=True): with gr.Column(scale=3): pose_controlnet_conditioning_scale = gr.Slider( minimum=0, maximum=1, value=0.55, step=0.05, label="ControlNet Conditioning Scale", interactive=True, ) with gr.Column(scale=1): pose_img_is_preprocessed = gr.Checkbox( value=True, label="Preprocessed", interactive=True, ) with gr.Tab("Gray"): with gr.Group(): gray_img = gr.Image(show_label=False, interactive=True) with gr.Row(equal_height=True): with gr.Column(scale=3): gray_controlnet_conditioning_scale = gr.Slider( minimum=0, maximum=1, value=0.45, step=0.05, label="ControlNet Conditioning Scale", interactive=True, ) with gr.Column(scale=1): gray_img_is_preprocessed = gr.Checkbox( value=True, label="Preprocessed", interactive=True, ) with gr.Tab("Low Quality"): with gr.Group(): low_quality_img = gr.Image( show_label=False, interactive=True ) with gr.Row(equal_height=True): with gr.Column(scale=3): low_quality_controlnet_conditioning_scale = ( gr.Slider( minimum=0, maximum=1, value=0.4, step=0.05, label="ControlNet Conditioning Scale", interactive=True, ) ) with gr.Column(scale=1): low_quality_img_is_preprocessed = gr.Checkbox( value=True, label="Preprocessed", interactive=True, ) # with gr.Tab("Official Canny"): # with gr.Group(): # gr.HTML( # """ # # # """ # ) # with gr.Tab("Official Depth"): # with gr.Group(): # gr.HTML( # """ # # # """ # ) with gr.Tab("Auto Trainer"): gr.HTML( """ """ ) resize_mode_radio = gr.Radio( label="Resize Mode", choices=["Crop & Resize", "Resize Only", "Resize & Fill"], value="Resize & Fill", interactive=True, ) with gr.Accordion("Prompt Generator", open=False): gr.HTML( """ """ ) with gr.Column(scale=1): # Loras with gr.Accordion("Loras", open=True): selected_loras = gr.State([]) lora_selector = gr.Gallery( show_label=False, value=[(l["image"], l["title"]) for l in flux1_loras], container=False, columns=3, show_download_button=False, show_fullscreen_button=False, allow_preview=False, ) with gr.Group(): lora_selected = gr.Textbox( show_label=False, placeholder="Select a Lora to apply...", container=False, ) add_lora_btn = gr.Button("Add Lora", size="sm") gr.Markdown( "*You can add a Lora by entering a URL or a Hugging Face repo path." ) # update the selected_loras state with the new lora @gr.render( inputs=[lora_selected, selected_loras], triggers=[add_lora_btn.click], ) def add_lora(lora_selected): title = None weights = None info = None if isinstance(lora_selected, int): # Add from lora selector title = lora_selector[lora_selected]["title"] weights = lora_selector[lora_selected]["weights"] info = lora_selector[lora_selected]["trigger_word"] elif isinstance(lora_selected, str): # check if url if lora_selected.startswith("http"): # Check if it's a CivitAI URL if "civitai.com/models/" in lora_selected: try: # Extract model ID and version ID from URL model_id = re.search( r"/models/(\d+)", lora_selected ).group(1) version_id = re.search( r"modelVersionId=(\d+)", lora_selected ) version_id = ( version_id.group(1) if version_id else None ) # Get API token from env api_token = os.getenv("CIVITAI_TOKEN") headers = ( {"Authorization": f"Bearer {api_token}"} if api_token else {} ) # Get model version info if version_id: url = f"https://civitai.com/api/v1/model-versions/{version_id}" else: # Get latest version if no specific version url = f"https://civitai.com/api/v1/models/{model_id}" response = requests.get(url, headers=headers) data = response.json() # For models endpoint, get first version if "modelVersions" in data: version_data = data["modelVersions"][0] else: version_data = data # Verify it's a LoRA for Flux if ( "flux" not in version_data["baseModel"].lower() and "1" not in version_data["baseModel"].lower() ): raise ValueError( "This LoRA is not compatible with Flux base model" ) # Find .safetensor file safetensor_file = next( ( f for f in version_data["files"] if f["name"].endswith(".safetensors") ), None, ) if not safetensor_file: raise ValueError("No .safetensor file found") # Download file to temp location temp_dir = tempfile.gettempdir() file_path = os.path.join( temp_dir, safetensor_file["name"] ) download_url = safetensor_file["downloadUrl"] if api_token: download_url += f"?token={api_token}" response = requests.get( download_url, headers=headers ) with open(file_path, "wb") as f: f.write(response.content) # Set info from model data title = data["name"] weights = file_path # Check usage tips for default weight if "description" in version_data: strength_match = re.search( r"strength[:\s]+(\d*\.?\d+)", version_data["description"], re.IGNORECASE, ) if strength_match: weight = float(strength_match.group(1)) info = ", ".join( version_data.get("trainedWords", []) ) except Exception as e: gr.Error(f"Error processing CivitAI URL: {str(e)}") else: # check if a hugging face repo (user/repo) if re.match( r"^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$", lora_selected ): try: # Get API token from env api_token = os.getenv("HF_TOKEN") headers = ( {"Authorization": f"Bearer {api_token}"} if api_token else {} ) # Get model info url = f"https://huggingface.co/api/models/{lora_selected}" response = requests.get(url, headers=headers) data = response.json() # Verify it's a LoRA for Flux if ( "tags" in data and "flux-lora" not in data["tags"] ): raise ValueError( "This model is not tagged as a Flux LoRA" ) # Find .safetensor file files_url = f"https://huggingface.co/api/models/{lora_selected}/tree" response = requests.get(files_url, headers=headers) files = response.json() safetensor_file = next( ( f for f in files if f.get("path", "").endswith( ".safetensors" ) ), None, ) if not safetensor_file: raise ValueError("No .safetensor file found") # Download file to temp location temp_dir = tempfile.gettempdir() file_name = os.path.basename( safetensor_file["path"] ) file_path = os.path.join(temp_dir, file_name) download_url = ( f"https://huggingface.co/{lora_selected}" f"/resolve/main/{safetensor_file['path']}" ) response = requests.get( download_url, headers=headers ) with open(file_path, "wb") as f: f.write(response.content) # Set info from model data title = data.get( "name", lora_selected.split("/")[-1] ) weights = file_path # Check model card for weight recommendations if ( "cardData" in data and "weight" in data["cardData"] ): try: weight = float(data["cardData"]["weight"]) except (ValueError, TypeError): weight = 1.0 # Get trigger words from tags or model card trigger_words = [] if ( "cardData" in data and "trigger_words" in data["cardData"] ): trigger_words.extend( data["cardData"]["trigger_words"] ) if "tags" in data: trigger_words.extend( t for t in data["tags"] if not t.startswith("flux-") ) info = ( ", ".join(trigger_words) if trigger_words else None ) except Exception as e: gr.Error( f"Error processing Hugging Face repo: {str(e)}" ) # add lora to selected_loras selected_loras.append( { "title": title, "weights": weights, # i.e safetensors file path "info": info, } ) # render the selected_loras state as sliders @gr.render(inputs=[selected_loras]) def render_selected_loras(selected_loras): def update_lora_weight(lora_slider, selected_loras): for i, lora in enumerate(selected_loras): if lora["title"] == lora_slider.label: lora["weight"] = lora_slider.value for i, lora in enumerate(selected_loras): lora_slider = gr.Slider( label=lora["title"], value=0.8, interactive=True, info=lora["info"], ) lora_slider.change( fn=update_lora_weight, inputs=[lora_slider, selected_loras], outputs=selected_loras, ) demo.launch()