import asyncio import base64 import importlib import io import logging import os from datetime import datetime import gradio as gr import httpx from PIL import Image import config importlib.reload(config) from config import CONFIG, get_api_headers, get_api_url from models import ( TaskSubmission, ImageToImageSubmission, PhotoStyleSubmission, InteriorDesignRenderingSubmission, WatermarkRemovalSubmission, LineArtConversionSubmission, AnimeToRealSubmission, RealToAnimeSubmission, ImageOutpaintingSubmission, FiveViewGenerationSubmission, Figure3DSubmission, CharacterFigureCollaborationSubmission ) from examples_config import ( TEXT_TO_IMAGE_EXAMPLES_WITH_RESULTS, FIVE_VIEW_GENERATION_EXAMPLES_WITH_RESULTS, FIGURE_3D_EXAMPLES_WITH_RESULTS, CHARACTER_FIGURE_COLLABORATION_EXAMPLES_WITH_RESULTS, IMAGE_OUTPAINTING_EXAMPLES_WITH_RESULTS, LINE_ART_CONVERSION_EXAMPLES_WITH_RESULTS, ANIME_TO_REAL_EXAMPLES_WITH_RESULTS, REAL_TO_ANIME_EXAMPLES_WITH_RESULTS, INTERIOR_DESIGN_EXAMPLES_WITH_RESULTS ) logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) PHOTO_STYLE_DISPLAY_MAPPING = { "camera_movement": "📹 Camera Movement", "relighting": "💡 Relighting", "camera_zoom": "🔍 Camera Zoom", "product_photo": "📦 Professional Product Photography", "miniature": "🏠 Tilt-Shift Miniature", "reflection": "🪞 Reflection Addition", "pose_change": "🎭 Pose & Viewpoint Change" } def preset_key_to_display_name(preset_key: str) -> str: return PHOTO_STYLE_DISPLAY_MAPPING.get(preset_key, preset_key) def display_name_to_preset_key(display_name: str) -> str: for key, value in PHOTO_STYLE_DISPLAY_MAPPING.items(): if value == display_name: return key return display_name PHOTO_STYLE_CHOICES = [ (preset_key_to_display_name(key), key) for key in PHOTO_STYLE_DISPLAY_MAPPING.keys() ] INTERIOR_DESIGN_STYLE_MAPPING = { "japanese_wabi_sabi": "🏯 Japanese Wabi-Sabi", "nordic_cozy": "🏔️ Nordic Cozy", "italian_luxury": "🇮🇹 Italian Luxury", "parisian_apartment": "🗼 Parisian Apartment" } def interior_style_key_to_display_name(style_key: str) -> str: return INTERIOR_DESIGN_STYLE_MAPPING.get(style_key, style_key) def display_name_to_interior_style_key(display_name: str) -> str: for key, value in INTERIOR_DESIGN_STYLE_MAPPING.items(): if value == display_name: return key return display_name INTERIOR_DESIGN_STYLE_CHOICES = [ (interior_style_key_to_display_name(key), key) for key in INTERIOR_DESIGN_STYLE_MAPPING.keys() ] # 3D Figure Style Mapping FIGURE_3D_STYLE_MAPPING = { "professional_lighting": "💡 Professional Lighting Scene", "collector_shelf": "📚 Collector's Display Scene", "desktop_display": "💻 Desktop Display Scene" } FIGURE_3D_STYLE_CHOICES = [ (display_name, key) for key, display_name in FIGURE_3D_STYLE_MAPPING.items() ] # ============================================================================ # Utility Functions # ============================================================================ def pil_to_base64(pil_image: Image.Image) -> str: buffer = io.BytesIO() pil_image.save(buffer, format='PNG') image_data = buffer.getvalue() return base64.b64encode(image_data).decode('utf-8') def resize_image_if_needed(image: Image.Image, max_size: int = 1536, min_size: int = 512) -> Image.Image: width, height = image.size # Check if resize is needed if max(width, height) <= max_size and min(width, height) >= min_size: return image # Calculate new dimensions if max(width, height) > max_size: ratio = max_size / max(width, height) new_width = int(width * ratio) new_height = int(height * ratio) else: ratio = min_size / min(width, height) new_width = int(width * ratio) new_height = int(height * ratio) return image.resize((new_width, new_height), Image.Resampling.LANCZOS) async def submit_task_with_retry(endpoint: str, payload: dict, task_name: str, max_retries: int = 60) -> dict: """ Submit task with intelligent retry mechanism for 429 errors Args: endpoint: API endpoint payload: Request payload task_name: Task name for user-friendly messages max_retries: Maximum retry attempts (default: 60 for 5 minutes) Returns: API response dict Raises: Exception: User-friendly error messages only """ import asyncio base_delay = 5.0 # Start with 5 seconds max_delay = 30.0 # Cap at 30 seconds for attempt in range(max_retries + 1): try: async with httpx.AsyncClient(timeout=CONFIG.API_TIMEOUT) as client: response = await client.post( get_api_url(endpoint), json=payload, headers=get_api_headers() ) if response.status_code == 429: if attempt < max_retries: delay = min(base_delay * (1.5 ** attempt), max_delay) logger.info(f"System busy, retrying {task_name} in {delay:.1f}s (attempt {attempt + 1}/{max_retries + 1})") await asyncio.sleep(delay) continue else: raise Exception("The system is currently busy, please try again later") elif response.status_code >= 500: logger.error(f"Server error {response.status_code} for {task_name}") raise Exception("Service temporarily unavailable, please try again later") elif response.status_code >= 400: logger.error(f"Client error {response.status_code} for {task_name}") raise Exception("Invalid request parameters, please check your input") # Success response.raise_for_status() return response.json() except httpx.TimeoutException: logger.error(f"Timeout error for {task_name}") raise Exception("Network timeout, please check your connection") except httpx.ConnectError: logger.error(f"Connection error for {task_name}") raise Exception("Unable to connect to the server, please try again later") except Exception as e: if any(msg in str(e) for msg in ["System is currently busy", "Service temporarily unavailable", "Invalid request parameters", "Network connection timeout", "Unable to connect to server"]): # Re-raise user-friendly messages (English messages for detection) raise else: # Log technical error but show user-friendly message logger.error(f"Unexpected error submitting {task_name}: {e}") raise Exception("An error occurred while submitting the task, please try again later") # Should never reach here raise Exception("The system is currently busy, please try again later") async def submit_text_to_image_task(prompt: str, resolution: str) -> dict: """Submit text-to-image task to backend API with intelligent retry""" # Parse resolution width, height = 1024, 1024 # Default if "1024x1024" in resolution: width, height = 1024, 1024 elif "1152x896" in resolution: width, height = 1152, 896 elif "896x1152" in resolution: width, height = 896, 1152 elif "1344x768" in resolution: width, height = 1344, 768 elif "768x1344" in resolution: width, height = 768, 1344 elif "1216x832" in resolution: width, height = 1216, 832 elif "1536x640" in resolution: width, height = 1536, 640 # Create submission submission = TaskSubmission( prompt=prompt, width=width, height=height ) return await submit_task_with_retry( endpoint="api/v1/tasks/", payload=submission.to_api_payload(), task_name="Text to Image" ) async def get_task_status(task_id: str) -> dict: """Get task status from backend API""" try: async with httpx.AsyncClient(timeout=30) as client: response = await client.get( get_api_url(f"api/v1/tasks/{task_id}"), headers=get_api_headers() ) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error getting task status: {e}") raise async def get_task_result(task_id: str) -> dict: """Get task result from backend API""" try: async with httpx.AsyncClient(timeout=30) as client: response = await client.get( get_api_url(f"api/v1/tasks/{task_id}/result"), headers=get_api_headers() ) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error getting task result: {e}") raise async def load_image_from_result(result_data: dict) -> Image.Image: """Load PIL Image from task result data via URL download only""" if not result_data: raise ValueError("No result data received") result_url = result_data.get("result_url") logger.info(f"🔗 Result URL: {result_url}") if not result_url: logger.error(f"❌ No result_url in response. Available fields: {list(result_data.keys())}") raise ValueError("No result_url found in result") if not result_url.startswith("http"): raise ValueError(f"Invalid URL format: {result_url}") try: logger.info(f"📥 Downloading image from: {result_url}") async with httpx.AsyncClient(timeout=60.0) as client: response = await client.get(result_url) logger.info(f"📊 HTTP response status: {response.status_code}") response.raise_for_status() # Check content length content_length = len(response.content) logger.info(f"📦 Content size: {content_length} bytes") if content_length < 1024: # Less than 1KB is suspicious raise ValueError("Downloaded content too small to be a valid image") return Image.open(io.BytesIO(response.content)) except Exception as e: logger.error(f"Error downloading image from {result_url}: {e}") raise def create_placeholder_image(prompt: str, resolution: str) -> Image.Image: """Create a placeholder image for examples""" try: # Parse resolution to get dimensions if "1024x1024" in resolution: width, height = 1024, 1024 elif "1280x1024" in resolution: width, height = 1280, 1024 else: width, height = 1024, 1024 # Create a simple placeholder image img = Image.new('RGB', (width, height), color='#f5f5f5') return img except Exception as e: logger.error(f"Error creating placeholder image: {e}") return Image.new('RGB', (1024, 1024), color='#f5f5f5') def create_placeholder_image_inline(prompt: str, resolution: str) -> Image.Image: """Create a placeholder image for examples""" try: # Parse resolution to get dimensions if "1024x1024" in resolution: width, height = 1024, 1024 elif "1152x896" in resolution: width, height = 1152, 896 elif "896x1152" in resolution: width, height = 896, 1152 else: width, height = 1024, 1024 # Create a simple placeholder image img = Image.new('RGB', (width, height), color='lightgray') return img except Exception: # Fallback to default size return Image.new('RGB', (1024, 1024), color='lightgray') def load_example_result(prompt: str, resolution: str, result_path: str = None): """Load example result image for text-to-image""" try: # Find matching example in TEXT_TO_IMAGE_EXAMPLES_WITH_RESULTS for example_prompt, example_resolution, example_path in TEXT_TO_IMAGE_EXAMPLES_WITH_RESULTS: if example_prompt == prompt and example_resolution == resolution: if os.path.exists(example_path): logger.info(f"Loading example result: {example_path}") return Image.open(example_path) else: logger.warning(f"Example image not found: {example_path}") return create_placeholder_image_inline(prompt, resolution) # If no matching example found, create placeholder logger.warning(f"No matching example found for: {prompt[:50]}...") return create_placeholder_image_inline(prompt, resolution) except Exception as e: logger.error(f"Error loading example result: {e}") return create_placeholder_image_inline(prompt, resolution) def load_line_art_example_result(input_image_path, result_path = None): """Load line art conversion example images""" try: input_image = None if isinstance(input_image_path, Image.Image): input_image = input_image_path logger.info(f"Using PIL.Image object for line art input (cache mode): {input_image.size}") elif isinstance(input_image_path, str): if os.path.exists(input_image_path): logger.info(f"Loading line art input image: {input_image_path}") input_image = Image.open(input_image_path) else: logger.warning(f"Line art input image not found: {input_image_path}") input_image = create_placeholder_image_inline("Input Image", "1024x1024") else: logger.warning(f"Unexpected input_image_path type: {type(input_image_path)}") input_image = create_placeholder_image_inline("Input Image", "1024x1024") result_image = None if isinstance(result_path, Image.Image): result_image = result_path logger.info(f"Using PIL.Image object for line art result (cache mode): {result_image.size}") return (input_image, result_image) from examples_config import LINE_ART_CONVERSION_EXAMPLES_WITH_RESULTS if isinstance(input_image_path, str): search_path = input_image_path for example_input, example_path in LINE_ART_CONVERSION_EXAMPLES_WITH_RESULTS: if example_input == search_path: if os.path.exists(example_path): logger.info(f"Loading line art example result: {example_path}") result_image = Image.open(example_path) return (input_image, result_image) else: image_size = input_image.size logger.info(f"Cache mode: identifying example by image size: {image_size}") size_to_result = { (474, 845): "examples/results/line_art_example1.jpg", (720, 1104): "examples/results/line_art_example2.jpg", (736, 1308): "examples/results/line_art_example3.jpg", } result_path = size_to_result.get(image_size) if result_path and os.path.exists(result_path): logger.info(f"Loading line art result by size mapping: {result_path}") result_image = Image.open(result_path) return (input_image, result_image) # If no matching example is found, create a placeholder logger.warning(f"No matching line art example found") result_image = create_placeholder_image_inline("Line Art Result", "1024x1024") return (input_image, result_image) except Exception as e: logger.error(f"Error loading line art example: {e}") input_placeholder = create_placeholder_image_inline("Input Image", "1024x1024") result_placeholder = create_placeholder_image_inline("Line Art Result", "1024x1024") return (input_placeholder, result_placeholder) def load_anime_to_real_example_result(input_image_path, result_path=None): """ Load example for Anime to Real: input image and pre-generated result image Simplified version using fixed paths (since only one example exists) """ try: logger.info(f"=== ANIME FUNCTION CALLED ===") logger.info(f"Args: {input_image_path}, {result_path}") # 简化版本:直接使用固定路径 input_path = "examples/anime_input/example1.jpg" result_path_fixed = "examples/results/anime_to_real_example1.jpg" logger.info(f"Loading fixed paths: {input_path}, {result_path_fixed}") # Load input image if os.path.exists(input_path): input_image = Image.open(input_path) logger.info(f"Input loaded: {input_image.size}") else: logger.error(f"Input not found: {input_path}") input_image = create_placeholder_image_inline("Input Error", "512x512") # Load result image if os.path.exists(result_path_fixed): result_image = Image.open(result_path_fixed) logger.info(f"Result loaded: {result_image.size}") else: logger.error(f"Result not found: {result_path_fixed}") result_image = create_placeholder_image_inline("Result Error", "512x512") logger.info(f"=== RETURNING: {input_image.size}, {result_image.size} ===") return (input_image, result_image) except Exception as e: logger.error(f"Error loading anime to real example: {e}", exc_info=True) input_placeholder = create_placeholder_image_inline("Anime Input", "1024x1024") result_placeholder = create_placeholder_image_inline("Real Person Result", "1024x1024") logger.info(f"=== Returning with error placeholders ===") return (input_placeholder, result_placeholder) def load_real_to_anime_example_result(input_image_path, result_path=None): """Load example for Real to Anime conversion""" try: input_image = None if isinstance(input_image_path, Image.Image): input_image = input_image_path logger.info(f"Using PIL.Image object for real to anime input (cache mode): {input_image.size}") elif isinstance(input_image_path, str): if os.path.exists(input_image_path): logger.info(f"Loading real to anime input image: {input_image_path}") input_image = Image.open(input_image_path) else: logger.warning(f"Real to anime input image not found: {input_image_path}") input_image = create_placeholder_image_inline("Input Image", "1024x1024") else: logger.warning(f"Unexpected input_image_path type: {type(input_image_path)}") input_image = create_placeholder_image_inline("Input Image", "1024x1024") result_image = None if isinstance(result_path, Image.Image): result_image = result_path logger.info(f"Using PIL.Image object for real to anime result (cache mode): {result_image.size}") return (input_image, result_image) from examples_config import REAL_TO_ANIME_EXAMPLES_WITH_RESULTS if isinstance(input_image_path, str): search_path = input_image_path for example_input, example_result in REAL_TO_ANIME_EXAMPLES_WITH_RESULTS: if example_input == search_path: if os.path.exists(example_result): logger.info(f"Loading real to anime example result: {example_result}") result_image = Image.open(example_result) return (input_image, result_image) else: image_size = input_image.size logger.info(f"Cache mode: identifying real to anime example by image size: {image_size}") size_to_result = { (736, 1104): "examples/results/real_to_anime_example1.jpg", (736, 946): "examples/results/real_to_anime_example2.jpg", (1206, 796): "examples/results/real_to_anime_example3.jpg", } result_path_mapped = size_to_result.get(image_size) if result_path_mapped and os.path.exists(result_path_mapped): logger.info(f"Loading real to anime result by size mapping: {result_path_mapped}") result_image = Image.open(result_path_mapped) return (input_image, result_image) # If no matching example is found, create a placeholder logger.warning(f"No matching real to anime example found") result_image = create_placeholder_image_inline("Anime Style Result", "1024x1024") return (input_image, result_image) except Exception as e: logger.error(f"Error loading real to anime example: {e}", exc_info=True) input_placeholder = create_placeholder_image_inline("Real Photo Input", "1024x1024") result_placeholder = create_placeholder_image_inline("Anime Style Result", "1024x1024") return (input_placeholder, result_placeholder) def load_dual_output_example(input_path, param1=None, param2=None): """Load example with both input and result images - enhanced version for different use cases""" try: # Handle input image if isinstance(input_path, Image.Image): input_image = input_path elif isinstance(input_path, str) and os.path.exists(input_path): input_image = Image.open(input_path) else: input_image = create_placeholder_image_inline("Input", "1024x1024") # Try to find result image from various examples configs result_image = None if isinstance(input_path, str): # Check different examples configs to find matching result from examples_config import ( IMAGE_OUTPAINTING_EXAMPLES_WITH_RESULTS, INTERIOR_DESIGN_EXAMPLES_WITH_RESULTS ) # Try image outpainting examples first for example in IMAGE_OUTPAINTING_EXAMPLES_WITH_RESULTS: if example[0] == input_path and len(example) > 3: result_path = example[3] # Result is at index 3 if os.path.exists(result_path): result_image = Image.open(result_path) logger.info(f"Found outpainting result: {result_path}") break # Try interior design examples if not found if not result_image: for example in INTERIOR_DESIGN_EXAMPLES_WITH_RESULTS: if example[0] == input_path and len(example) > 2: result_path = example[2] # Result is at index 2 if os.path.exists(result_path): result_image = Image.open(result_path) logger.info(f"Found interior design result: {result_path}") break if not result_image: result_image = create_placeholder_image_inline("Result", "1024x1024") return input_image, result_image except Exception as e: logger.error(f"Error loading dual output example: {e}") placeholder = create_placeholder_image_inline("Error", "1024x1024") return placeholder, placeholder def load_five_view_example(input_path): """Load five view generation example with input and result images""" try: # Handle input image if isinstance(input_path, Image.Image): input_image = input_path elif isinstance(input_path, str) and os.path.exists(input_path): input_image = Image.open(input_path) else: input_image = create_placeholder_image_inline("Input", "1024x1024") # Try to find result from FIVE_VIEW_GENERATION_EXAMPLES_WITH_RESULTS from examples_config import FIVE_VIEW_GENERATION_EXAMPLES_WITH_RESULTS result_image = None # When Gradio caches examples, it passes PIL Image objects, not file paths # We need to match by image size or use the first available result if isinstance(input_path, Image.Image): # For PIL Image inputs (cache mode), use the first available result if len(FIVE_VIEW_GENERATION_EXAMPLES_WITH_RESULTS) > 0: result_path_found = FIVE_VIEW_GENERATION_EXAMPLES_WITH_RESULTS[0][1] if os.path.exists(result_path_found): result_image = Image.open(result_path_found) logger.info(f"Found five view result: {result_path_found}") elif isinstance(input_path, str): for example in FIVE_VIEW_GENERATION_EXAMPLES_WITH_RESULTS: if example[0] == input_path and len(example) > 1: result_path_found = example[1] if os.path.exists(result_path_found): result_image = Image.open(result_path_found) logger.info(f"Found five view result: {result_path_found}") break if not result_image: result_image = create_placeholder_image_inline("Five View Result", "1024x1024") return input_image, result_image except Exception as e: logger.error(f"Error loading five view example: {e}") placeholder = create_placeholder_image_inline("Error", "1024x1024") return placeholder, placeholder def load_figure_3d_example(input_image_path, figure_style: str, resolution: str = "square - 1024x1024 (1:1)", result_path=None): """ Load 3D figure generation example input image and pre-generated result image Supports two modes: 1. Runtime mode: input_image_path is a string path, result_path is a string path 2. Cache generation mode: input_image_path is a PIL.Image object, result_path is a PIL.Image object """ try: # 1. 处理输入图片 - 支持字符串路径和PIL.Image对象 input_image = None if isinstance(input_image_path, Image.Image): # Cache mode: directly use PIL.Image object input_image = input_image_path logger.info(f"Using PIL.Image object for input (cache mode): {input_image.size}") elif isinstance(input_image_path, str): # 运行时模式:从路径加载图片 if os.path.exists(input_image_path): logger.info(f"Loading figure 3D input image: {input_image_path}") input_image = Image.open(input_image_path) else: logger.warning(f"Figure 3D input image not found: {input_image_path}") input_image = create_placeholder_image("Input Image", "1024x1024") else: logger.warning(f"Unexpected input_image_path type: {type(input_image_path)}") input_image = create_placeholder_image("Input Image", "1024x1024") # 2. Handle result image - supports string path and PIL.Image object result_image = None if isinstance(result_path, Image.Image): # Cache mode: directly use PIL.Image object result_image = result_path logger.info(f"Using PIL.Image object for result (cache mode): {result_image.size}") elif isinstance(result_path, str): # 运行时模式:从路径加载结果图片 if os.path.exists(result_path): logger.info(f"Loading figure 3D result image: {result_path}") result_image = Image.open(result_path) else: logger.warning(f"Figure 3D result image not found: {result_path}") result_image = create_placeholder_image("Result Image", "1024x1024") else: # 尝试从FIGURE_3D_EXAMPLES_WITH_RESULTS中查找匹配的结果 from examples_config import FIGURE_3D_EXAMPLES_WITH_RESULTS result_image = None # 当Gradio缓存examples时,它传递PIL Image对象,不是文件路径 # 我们需要通过风格匹配或使用第一个可用结果 if isinstance(input_image_path, Image.Image): # 对于PIL Image输入(缓存模式),通过风格查找结果 for example in FIGURE_3D_EXAMPLES_WITH_RESULTS: if len(example) >= 3 and example[1] == figure_style: result_path_found = example[2] if os.path.exists(result_path_found): result_image = Image.open(result_path_found) logger.info(f"Found figure 3D result by style: {result_path_found}") break elif isinstance(input_image_path, str): # 对于字符串路径输入,精确匹配 for example in FIGURE_3D_EXAMPLES_WITH_RESULTS: if len(example) >= 3 and example[0] == input_image_path and example[1] == figure_style: result_path_found = example[2] if os.path.exists(result_path_found): result_image = Image.open(result_path_found) logger.info(f"Found figure 3D result: {result_path_found}") break if not result_image: result_image = create_placeholder_image("Figure 3D Result", "1024x1024") return input_image, figure_style, resolution, result_image except Exception as e: logger.error(f"Error loading figure 3D example: {e}") placeholder = create_placeholder_image_inline("Error", "1024x1024") return placeholder, figure_style, resolution, placeholder def load_character_figure_collaboration_example(input_image_path, result_path=None): """ Load character figure collaboration example input image and pre-generated result image Args: input_image_path: Path to input image or PIL.Image object result_path: Path to result image or PIL.Image object Returns: tuple: (input_image, result_image) """ try: # 1. 处理输入图片 - 支持字符串路径和PIL.Image对象 input_image = None if isinstance(input_image_path, Image.Image): # Cache mode: directly use PIL.Image object input_image = input_image_path logger.info(f"Using PIL.Image object for input (cache mode): {input_image.size}") elif isinstance(input_image_path, str): # 运行时模式:从路径加载图片 if os.path.exists(input_image_path): logger.info(f"Loading character figure collaboration input image: {input_image_path}") input_image = Image.open(input_image_path) else: logger.warning(f"Character figure collaboration input image not found: {input_image_path}") input_image = create_placeholder_image("Input Image", "1024x1024") else: logger.warning(f"Unexpected input_image_path type: {type(input_image_path)}") input_image = create_placeholder_image("Input Image", "1024x1024") # 2. Handle result image - 完全按照Line Art的模式 result_image = None if isinstance(result_path, Image.Image): # Cache mode: directly use PIL.Image object result_image = result_path logger.info(f"Using PIL.Image object for result (cache mode): {result_image.size}") return input_image, result_image from examples_config import CHARACTER_FIGURE_COLLABORATION_EXAMPLES_WITH_RESULTS if isinstance(input_image_path, str): # 运行时模式:通过输入路径查找匹配的结果 search_path = input_image_path for example_input, example_result in CHARACTER_FIGURE_COLLABORATION_EXAMPLES_WITH_RESULTS: if example_input == search_path: if os.path.exists(example_result): logger.info(f"Loading character figure collaboration example result: {example_result}") result_image = Image.open(example_result) return input_image, result_image else: # Cache mode: 通过图片尺寸匹配(完全按照Line Art的模式) image_size = input_image.size logger.info(f"Cache mode: identifying character figure collaboration example by image size: {image_size}") # 创建尺寸到结果图片的映射(完全按照Line Art的模式) size_to_result = { (1206, 776): "examples/results/character_figure_collaboration_example1.jpg", (1320, 1920): "examples/results/character_figure_collaboration_example2.jpg", (1536, 2200): "examples/results/character_figure_collaboration_example3.jpg", (1206, 1787): "examples/results/character_figure_collaboration_example4.jpg", } result_path = size_to_result.get(image_size) if result_path and os.path.exists(result_path): logger.info(f"Loading character figure collaboration result by size mapping: {result_path}") result_image = Image.open(result_path) return input_image, result_image # If no matching example is found, create a placeholder logger.warning(f"No matching character figure collaboration example found") result_image = create_placeholder_image_inline("Character Figure Collaboration Result", "1024x1024") return input_image, result_image except Exception as e: logger.error(f"Error loading character figure collaboration example: {e}") placeholder = create_placeholder_image_inline("Error", "1024x1024") return placeholder, placeholder def load_outpainting_example_result(input_image_path, expand_height, expand_width, result_path=None): """ Load outpainting example input image, parameters, and pre-generated result image Args: input_image_path: input image path or PIL.Image object expand_height: outpainting height percentage expand_width: outpainting width percentage result_path: result image path or PIL.Image object Returns: Tuple[Image.Image, float, float, Image.Image]: (输入图片, 扩展高度, 扩展宽度, 结果图片) """ try: # 确保参数是正确的数字类型(防止缓存序列化问题) expand_height = float(expand_height) expand_width = float(expand_width) # 处理输入图片 if isinstance(input_image_path, str) and os.path.exists(input_image_path): input_image = Image.open(input_image_path) logger.info(f"Loading outpainting input image: {input_image_path}") elif isinstance(input_image_path, Image.Image): input_image = input_image_path logger.info(f"Using PIL.Image object for outpainting input (cache mode): {input_image.size}") else: input_image = create_placeholder_image_inline("Input Image", "1024x1024") # Handle result image if isinstance(result_path, Image.Image): # 缓存生成模式:直接使用PIL.Image对象 result_image = result_path logger.info(f"Using PIL.Image object for outpainting result (cache mode): {result_image.size}") return (input_image, expand_height, expand_width, result_image) elif isinstance(result_path, str) and os.path.exists(result_path): result_image = Image.open(result_path) logger.info(f"Loading outpainting result from path: {result_path}") return (input_image, expand_height, expand_width, result_image) # Runtime mode: find result image from IMAGE_OUTPAINTING_EXAMPLES_WITH_RESULTS from examples_config import IMAGE_OUTPAINTING_EXAMPLES_WITH_RESULTS if isinstance(input_image_path, str): # 运行时模式:精确匹配路径和参数 search_path = input_image_path for example_input, example_height, example_width, example_result in IMAGE_OUTPAINTING_EXAMPLES_WITH_RESULTS: if example_input == search_path and abs(example_height - expand_height) < 0.01 and abs(example_width - expand_width) < 0.01: if os.path.exists(example_result): logger.info(f"Loading outpainting example result: {example_result}") result_image = Image.open(example_result) return (input_image, expand_height, expand_width, result_image) else: # Cache mode: identify example by image size and parameters image_size = input_image.size logger.info(f"Cache mode: identifying outpainting example by image size: {image_size} and params: height={expand_height}, width={expand_width}") # 预定义的尺寸和参数映射(根据实际图片尺寸和参数) size_param_to_result = { ((1070, 1906), 0.2, 0.3): "examples/results/outpainting_example1.jpg", # example1: 原始测试图片 ((960, 1200), 0.2, 0.4): "examples/results/outpainting_example2.jpg", # example2: 日式住宅 ((641, 1200), 0.5, 0.2): "examples/results/outpainting_example3.jpg", # example3: 人物自拍 } # Find matching result key = (image_size, expand_height, expand_width) if key in size_param_to_result: result_path_mapped = size_param_to_result[key] if os.path.exists(result_path_mapped): logger.info(f"Loading outpainting result by size and param mapping: {result_path_mapped}") result_image = Image.open(result_path_mapped) return (input_image, expand_height, expand_width, result_image) # If no matching result is found, use the first available result logger.info("Using first available outpainting result") for example_input, example_height, example_width, example_result in IMAGE_OUTPAINTING_EXAMPLES_WITH_RESULTS: if os.path.exists(example_result): logger.info(f"Loading first available outpainting result: {example_result}") result_image = Image.open(example_result) return (input_image, expand_height, expand_width, result_image) # As a fallback: create a placeholder logger.info("No outpainting results found, creating placeholder") result_image = create_placeholder_image_inline("Outpainting Result", "1536x1024") return (input_image, expand_height, expand_width, result_image) except Exception as e: logger.error(f"Error loading outpainting example: {e}", exc_info=True) input_placeholder = create_placeholder_image_inline("Input Image", "1024x1024") result_placeholder = create_placeholder_image_inline("Outpainting Result", "1536x1024") return (input_placeholder, 0.2, 0.2, result_placeholder) def load_outpainting_example_for_gradio(input_image_path, expand_height, expand_width, result_path): """ Designed specifically for Gradio Examples to ensure input-output counts match Args: input_image_path: input image path or PIL.Image object expand_height: outpainting height percentage expand_width: outpainting width percentage result_path: result image path (from examples config) Returns: Tuple[Image.Image, float, float, Image.Image]: (input image, outpaint height, outpaint width, result image) """ try: # 确保参数是正确的数字类型 expand_height = float(expand_height) expand_width = float(expand_width) # 调用原有的加载函数,传入result_path result = load_outpainting_example_result(input_image_path, expand_height, expand_width, result_path) # 确保返回4个值 if len(result) == 4: return result else: logger.error(f"load_outpainting_example_result returned {len(result)} values, expected 4") # 创建默认返回值 input_placeholder = create_placeholder_image_inline("Input Image", "1024x1024") result_placeholder = create_placeholder_image_inline("Outpainting Result", "1536x1024") return (input_placeholder, expand_height, expand_width, result_placeholder) except Exception as e: logger.error(f"Error in load_outpainting_example_for_gradio: {e}", exc_info=True) # 创建默认返回值 input_placeholder = create_placeholder_image_inline("Input Image", "1024x1024") result_placeholder = create_placeholder_image_inline("Outpainting Result", "1536x1024") return (input_placeholder, float(expand_height), float(expand_width), result_placeholder) def load_interior_design_example_result(input_image_path, design_style: str, result_path=None): """ Load interior design rendering example input image and pre-generated result image Supports two modes: 1. Runtime mode: input_image_path is a string path, result_path is a string path 2. Cache generation mode: input_image_path is a PIL.Image object, result_path is a PIL.Image object """ try: # 1. 处理输入图片 - 支持字符串路径和PIL.Image对象 input_image = None if isinstance(input_image_path, Image.Image): # Cache mode: directly use PIL.Image object input_image = input_image_path logger.info(f"Using PIL.Image object for input (cache mode): {input_image.size}") elif isinstance(input_image_path, str): # 运行时模式:从路径加载图片 if os.path.exists(input_image_path): logger.info(f"Loading interior design input image: {input_image_path}") input_image = Image.open(input_image_path) else: logger.warning(f"Interior design input image not found: {input_image_path}") input_image = create_placeholder_image("Input Image", "1024x1024") else: logger.warning(f"Unexpected input_image_path type: {type(input_image_path)}") input_image = create_placeholder_image("Input Image", "1024x1024") # 2. Handle result image - supports string path and PIL.Image object result_image = None if isinstance(result_path, Image.Image): # 缓存生成模式:直接使用PIL.Image对象 result_image = result_path logger.info(f"Using PIL.Image object for result (cache mode): {result_image.size}") return (input_image, result_image) # 运行时模式:从INTERIOR_DESIGN_EXAMPLES_WITH_RESULTS查找结果图片 from examples_config import INTERIOR_DESIGN_EXAMPLES_WITH_RESULTS # 对于运行时模式,需要用字符串路径进行匹配 search_path = input_image_path if isinstance(input_image_path, str) else "examples/interior_input.png" for example_input, example_style, example_path in INTERIOR_DESIGN_EXAMPLES_WITH_RESULTS: if example_input == search_path and example_style == design_style: if os.path.exists(example_path): logger.info(f"Loading interior design example result: {example_path}") result_image = Image.open(example_path) return (input_image, result_image) else: logger.warning(f"Interior design example result not found: {example_path}") result_image = create_placeholder_image("Interior Design Result", "1280x1024") return (input_image, result_image) # If no matching example is found, create a placeholder logger.warning(f"No matching interior design example found for: {search_path}, {design_style}") result_image = create_placeholder_image("Interior Design Result", "1280x1024") return (input_image, result_image) except Exception as e: logger.error(f"Error loading interior design example: {e}") input_placeholder = create_placeholder_image("Input Image", "1024x1024") result_placeholder = create_placeholder_image("Interior Design Result", "1280x1024") return (input_placeholder, result_placeholder) # Task submission functions for all AI features async def submit_image_to_image_task(pil_image: Image.Image) -> dict: """Submit image-to-image task to backend API""" processed_image = resize_image_if_needed(pil_image, max_size=1536, min_size=512) base64_data = pil_to_base64(processed_image) submission = ImageToImageSubmission(image_data=base64_data) return await submit_task_with_retry( endpoint="api/v1/tasks/image-to-image", payload=submission.to_api_payload(), task_name="Image Conversion" ) async def submit_photo_style_task(pil_image: Image.Image, style_preset: str) -> dict: """Submit photo style transfer task to backend API""" processed_image = resize_image_if_needed(pil_image, max_size=1536, min_size=512) base64_data = pil_to_base64(processed_image) submission = PhotoStyleSubmission(image_data=base64_data, style_preset=style_preset) return await submit_task_with_retry( endpoint="api/v1/tasks/photo-style", payload=submission.to_api_payload(), task_name="Photo Style Transfer" ) async def submit_interior_design_task(pil_image: Image.Image, design_style: str) -> dict: """Submit interior design rendering task to backend API""" processed_image = resize_image_if_needed(pil_image, max_size=1536, min_size=512) base64_data = pil_to_base64(processed_image) submission = InteriorDesignRenderingSubmission(image_data=base64_data, design_style=design_style) return await submit_task_with_retry( endpoint="api/v1/tasks/interior-design-rendering", payload=submission.to_api_payload(), task_name="Interior Design Rendering" ) async def submit_watermark_removal_task(pil_image: Image.Image) -> dict: """Submit watermark removal task to backend API""" processed_image = resize_image_if_needed(pil_image, max_size=1536, min_size=512) base64_data = pil_to_base64(processed_image) submission = WatermarkRemovalSubmission(image_data=base64_data) return await submit_task_with_retry( endpoint="api/v1/tasks/watermark-removal", payload=submission.to_api_payload(), task_name="Watermark Removal" ) async def submit_line_art_task(pil_image: Image.Image) -> dict: """Submit line art conversion task to backend API""" processed_image = resize_image_if_needed(pil_image, max_size=1536, min_size=512) base64_data = pil_to_base64(processed_image) submission = LineArtConversionSubmission(image_data=base64_data) return await submit_task_with_retry( endpoint="api/v1/tasks/line-art-conversion", payload=submission.to_api_payload(), task_name="Line Art Conversion" ) async def submit_image_outpainting_task(pil_image: Image.Image, expand_height: float, expand_width: float) -> dict: """Submit image outpainting task to backend API""" processed_image = resize_image_if_needed(pil_image, max_size=1536, min_size=512) base64_data = pil_to_base64(processed_image) submission = ImageOutpaintingSubmission( image_data=base64_data, expand_height=expand_height, expand_width=expand_width ) return await submit_task_with_retry( endpoint="api/v1/tasks/image-outpainting", payload=submission.to_api_payload(), task_name="Image Outpainting" ) async def submit_anime_to_real_task(pil_image: Image.Image) -> dict: """Submit anime to real conversion task to backend API""" processed_image = resize_image_if_needed(pil_image, max_size=1536, min_size=512) base64_data = pil_to_base64(processed_image) submission = AnimeToRealSubmission(image_data=base64_data) return await submit_task_with_retry( endpoint="api/v1/tasks/anime-to-real", payload=submission.to_api_payload(), task_name="Anime to Real" ) async def submit_real_to_anime_task(pil_image: Image.Image) -> dict: """Submit real to anime conversion task to backend API""" processed_image = resize_image_if_needed(pil_image, max_size=1536, min_size=512) base64_data = pil_to_base64(processed_image) submission = RealToAnimeSubmission(image_data=base64_data) return await submit_task_with_retry( endpoint="api/v1/tasks/real-to-anime", payload=submission.to_api_payload(), task_name="Real to Anime" ) async def submit_five_view_generation_task(input_image: Image.Image) -> dict: """Submit five-view generation task to backend API""" # Convert PIL image to base64 processed_image = input_image.convert("RGB") base64_data = pil_to_base64(processed_image) submission = FiveViewGenerationSubmission(image_data=base64_data) return await submit_task_with_retry( endpoint="api/v1/tasks/five-view-generation", payload=submission.to_api_payload(), task_name="Five-View Generation" ) async def submit_figure_3d_generation_task(input_image: Image.Image, figure_style: str, resolution: str) -> dict: """Submit 2D to 3D figure generation task to backend API""" # Convert PIL image to base64 processed_image = input_image.convert("RGB") base64_data = pil_to_base64(processed_image) submission = Figure3DSubmission( image_data=base64_data, figure_style=figure_style, resolution=resolution ) return await submit_task_with_retry( endpoint="api/v1/tasks/figure-3d-generation", payload=submission.to_api_payload(), task_name="2D to 3D Figure" ) async def submit_character_figure_collaboration_task(input_image: Image.Image) -> dict: """Submit character figure collaboration task to backend API""" # Convert PIL image to base64 processed_image = input_image.convert("RGB") base64_data = pil_to_base64(processed_image) submission = CharacterFigureCollaborationSubmission( image_data=base64_data ) return await submit_task_with_retry( endpoint="api/v1/tasks/character-figure-collaboration", payload=submission.to_api_payload(), task_name="Character Figure Collaboration" ) # ============================================================================ # UI Configuration and Theme Setup # ============================================================================ def create_custom_theme(): """Create a clean light theme using Gradio's Default theme""" return gr.themes.Default( primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.gray, neutral_hue=gr.themes.colors.slate, font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"], font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "ui-monospace", "Consolas", "monospace"], ) # ============================================================================ # AI Processing Functions # ============================================================================ # Global task tracking CURRENT_TASK_ID = None TASK_CANCELLED = False def cancel_current_task(): """Cancel the current running task""" global TASK_CANCELLED # pylint: disable=global-statement TASK_CANCELLED = True logger.info("Task cancellation requested") return ( gr.update(visible=True), # generate_btn gr.update(visible=False), # cancel_btn "Task cancelled" # status_info ) async def generate_text_to_image(prompt: str, resolution: str, progress=None): """Generate image from text prompt""" global CURRENT_TASK_ID, TASK_CANCELLED # pylint: disable=global-statement try: TASK_CANCELLED = False if not prompt.strip(): yield None, "❌ Please enter an image description", "", True, False return logger.info(f"Starting text-to-image generation: {prompt[:50]}...") start_time = datetime.now() yield ( None, "🚀 Submitting task to AI server...", "**Tip**: The AI is processing your description and preparing to create...", False, True ) try: task_data = await submit_text_to_image_task(prompt, resolution) task_id = task_data.get("task_id") CURRENT_TASK_ID = task_id if not task_id: raise Exception("Server did not return a task ID") logger.info(f"Task submitted successfully: {task_id}") except Exception as e: logger.error(f"Task submission failed: {e}") error_message = str(e) if any(msg in str(e) for msg in ["System is currently busy", "Service temporarily unavailable", "Invalid request parameters", "Network connection timeout", "Unable to connect to server", "Task submission failed"]) else "Task submission failed, please try again later" yield ( None, f"❌ {error_message}", "**Tip**: If the system is busy, please wait a moment and try again", True, False ) return # Poll for completion max_attempts = CONFIG.MAX_POLL_ATTEMPTS poll_interval = CONFIG.POLL_INTERVAL for attempt in range(max_attempts): if TASK_CANCELLED: yield ( None, "Task cancelled", "", True, False ) return try: status_data = await get_task_status(task_id) status = status_data.get("status", "unknown") elapsed_time = (datetime.now() - start_time).total_seconds() progress_percent = min(95, (attempt / max_attempts) * 100) if progress: progress(progress_percent / 100, f"Generating... ({elapsed_time:.0f}s)") if status == "completed": # Get result result_data = await get_task_result(task_id) result_image = await load_image_from_result(result_data) logger.info(f"Generation completed in {elapsed_time:.1f}s") yield ( result_image, "✓ Image generated!", f"Generation time: {elapsed_time:.1f} s", True, False ) return elif status == "failed": error_msg = status_data.get("error", "Unknown error") yield ( None, f"❌ Generation failed: {error_msg}", "", True, False ) return # Update status with helpful tips tips = [ "💡 Tip: More detailed descriptions lead to more accurate images", "🎨 Creating: The AI is drawing a unique image based on your description", "⏱️ Please wait: Complex images take more time to refine", "🔥 Almost done: The AI is adding final touches" ] tip_index = min(attempt // 5, len(tips) - 1) yield ( None, f"🎨 AI is generating the image... ({elapsed_time:.0f}s)", tips[tip_index], False, True ) await asyncio.sleep(poll_interval) except Exception as e: logger.error(f"Error polling task status: {e}") yield ( None, "❌ Status query failed. Please try again later.", "", True, False ) return # Timeout yield ( None, "Timeout: Generation timed out, please try again later", "", True, False ) except Exception as e: logger.error(f"Unexpected error in text-to-image generation: {e}") yield ( None, "❌ An unexpected error occurred during generation. Please try again later.", "", True, False ) async def generic_image_processing( input_image: Image.Image, task_name: str, submit_func, submit_args: tuple = (), progress=None ): """Generic image processing function for all AI features""" global CURRENT_TASK_ID, TASK_CANCELLED # pylint: disable=global-statement try: TASK_CANCELLED = False if input_image is None: yield None, f"❌ Please upload an image", "", True, False return logger.info(f"Starting {task_name} processing...") start_time = datetime.now() yield ( None, f"🚀 Submitting {task_name} task to the AI server...", f"**Tip**: The AI is analyzing your image and preparing to start {task_name}...", False, True ) try: task_data = await submit_func(input_image, *submit_args) task_id = task_data.get("task_id") CURRENT_TASK_ID = task_id if not task_id: raise Exception("Server did not return a task ID") logger.info(f"{task_name} task submitted successfully: {task_id}") except Exception as e: logger.error(f"{task_name} task submission failed: {e}") # Show user-friendly error message error_message = str(e) if any(msg in str(e) for msg in ["System is currently busy", "Service temporarily unavailable", "Invalid request parameters", "Network connection timeout", "Unable to connect to server", "Task submission failed"]) else "Task submission failed, please try again later" yield ( None, f"❌ {error_message}", "**Tip**: If the system is busy, please wait a moment and try again", True, False ) return # Poll for completion max_attempts = CONFIG.MAX_POLL_ATTEMPTS poll_interval = CONFIG.POLL_INTERVAL for attempt in range(max_attempts): if TASK_CANCELLED: yield ( None, "Task cancelled", "", True, False ) return try: status_data = await get_task_status(task_id) status = status_data.get("status", "unknown") elapsed_time = (datetime.now() - start_time).total_seconds() progress_percent = min(95, (attempt / max_attempts) * 100) if progress: progress(progress_percent / 100, f"Processing... ({elapsed_time:.0f}s)") if status == "completed": # Get result result_data = await get_task_result(task_id) result_image = await load_image_from_result(result_data) logger.info(f"{task_name} completed in {elapsed_time:.1f}s") yield ( result_image, f"✓ {task_name} completed!", f"Processing time: {elapsed_time:.1f} s", True, False ) return elif status == "failed": error_msg = status_data.get("error", "Unknown error") yield ( None, f"❌ {task_name} failed: {error_msg}", "", True, False ) return # Update status with task-specific tips task_tips = { "Image Conversion": ["🔄 Processing: analyzing image features", "🎯 Optimizing: applying advanced image processing"], "Five-View Generation": ["👁️ Analyzing: understanding facial and pose features", "🔄 Generating: creating multiple viewpoints"], "Photo Style Transfer": ["📸 Stylizing: applying professional photography techniques", "✨ Enhancing: optimizing lighting and colors"], "Interior Design Rendering": ["🏠 Designing: composing interior layout", "🎨 Rendering: adding furniture and decor"], "Watermark Removal": ["🚫 Detecting: locating watermark areas", "🔧 Repairing: intelligently inpainting background"], "Line Art Conversion": ["✏️ Outlining: extracting contours", "🎨 Refining: improving line details"], "Image Outpainting": ["📐 Extending: adding coherent border content", "🔄 Blending: ensuring seamless continuity"], "Anime to Real": ["👤 Converting: mapping anime features to realistic ones", "🎭 Refining: adjusting facial details"], "Real to Anime": ["🎌 Stylizing: applying anime art style", "✨ Enhancing: optimizing anime effects"] } tips = task_tips.get(task_name, ["🔄 Processing: AI is working hard", "⏱️ Please wait: almost done"]) tip_index = min(attempt // 8, len(tips) - 1) yield ( None, f"🎨 AI is processing {task_name}... ({elapsed_time:.0f}s)", tips[tip_index], False, True ) await asyncio.sleep(poll_interval) except Exception as e: logger.error(f"Error polling {task_name} task status: {e}") yield ( None, "❌ Status query failed. Please try again later.", "", True, False ) return # Timeout yield ( None, f"Timeout: {task_name} timed out, please try again later", "", True, False ) except Exception as e: logger.error(f"Unexpected error in {task_name}: {e}") yield ( None, f"❌ An unexpected error occurred during {task_name}. Please try again later.", "", True, False ) # Specific AI processing functions async def generate_image_to_image(input_image: Image.Image, progress=None): """Generate image from image conversion""" async for result in generic_image_processing( input_image, "Image to Image", submit_image_to_image_task, (), progress ): yield result async def generate_photo_style(input_image: Image.Image, style_preset: str, progress=None): """Apply photo style transfer""" async for result in generic_image_processing( input_image, "Photo Style Transfer", submit_photo_style_task, (style_preset,), progress ): yield result async def generate_interior_design(input_image: Image.Image, design_style: str, progress=None): """Generate interior design rendering""" async for result in generic_image_processing( input_image, "Interior Design Rendering", submit_interior_design_task, (design_style,), progress ): yield result async def generate_watermark_removal(input_image: Image.Image, progress=None): """Remove watermark from image""" async for result in generic_image_processing( input_image, "Watermark Removal", submit_watermark_removal_task, (), progress ): yield result async def generate_line_art(input_image: Image.Image, progress=None): """Convert image to line art""" async for result in generic_image_processing( input_image, "Line Art Conversion", submit_line_art_task, (), progress ): yield result async def generate_image_outpainting( input_image: Image.Image, expand_height: float, expand_width: float, progress=None ): """Expand image boundaries""" async for result in generic_image_processing( input_image, "Image Outpainting", submit_image_outpainting_task, (expand_height, expand_width), progress ): yield result async def generate_anime_to_real(input_image: Image.Image, progress=None): """Convert anime character to real person""" async for result in generic_image_processing( input_image, "Anime to Real", submit_anime_to_real_task, (), progress ): yield result async def generate_real_to_anime(input_image: Image.Image, progress=None): """Convert real person to anime character""" async for result in generic_image_processing( input_image, "Real to Anime", submit_real_to_anime_task, (), progress ): yield result # Five view generation needs special handling async def generate_five_view(input_image: Image.Image, progress=None): """Generate five view angles from portrait""" async for result in generic_image_processing( input_image, "Five-View Generation", submit_five_view_generation_task, (), progress ): yield result async def generate_figure_3d(input_image: Image.Image, figure_style: str, resolution: str, progress=None): """Generate 3D figure from 2D character image""" async for result in generic_image_processing( input_image, "2D to 3D Figure", submit_figure_3d_generation_task, (figure_style, resolution), progress ): yield result # Character figure collaboration generation async def generate_character_figure_collaboration(input_image: Image.Image, progress=None): """Generate character figure collaboration image""" async for result in generic_image_processing( input_image, "Character Figure Collaboration", submit_character_figure_collaboration_task, (), progress ): yield result def placeholder_handler(*args): """Placeholder function for AI processing""" # Suppress unused arguments warning _ = args return "Feature under development, stay tuned!", gr.update(visible=True) def create_text_to_image_interface(): """Create text-to-image interface""" with gr.Column(): gr.Markdown("## 📝 Text to Image") gr.Markdown("Enter a textual description and let AI generate an image for you") with gr.Row(): with gr.Column(scale=2): prompt_input = gr.Textbox( label="Image Description", placeholder="Describe the image you want in detail, e.g., a cute cat playing in a garden, anime style, high-quality details", lines=4, max_lines=6 ) resolution_input = gr.Dropdown( label="Resolution", choices=[ "portrait - 896x1152 (3:4)", "square - 1024x1024 (1:1)", "landscape - 1152x896 (4:3)" ], value="square - 1024x1024 (1:1)", interactive=True ) with gr.Row(): generate_btn = gr.Button("🚀 Generate Image", variant="primary", scale=2) cancel_btn = gr.Button("Cancel", variant="secondary", scale=1, visible=False) status_info = gr.Markdown("") with gr.Column(scale=3): result_image = gr.Image( label="Result", show_label=True, show_download_button=True, show_share_button=True ) image_info = gr.Markdown("Waiting for image generation...") # Bind events generate_btn.click( fn=generate_text_to_image, inputs=[prompt_input, resolution_input], outputs=[result_image, status_info, image_info, generate_btn, cancel_btn], show_progress=True ) cancel_btn.click( fn=cancel_current_task, outputs=[generate_btn, cancel_btn, status_info], queue=False ) return gr.Column() def switch_to_function(function_name: str): """Switch to a specific function and show its interface""" if function_name == "text_to_image": interface_html = """
Enter a textual description and let AI generate an image for you
How to use:
Feature under development, stay tuned!
Select a feature on the left to start your AI creation journey
Text to Image, Image to Image, Multi-view Generation
Photo Style, Interior Design
Watermark Removal, Line Art, Outpainting
Anime to Real, Real to Anime