diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -15,11 +15,14 @@ from models import ( TaskSubmission, ImageToImageSubmission, PhotoStyleSubmission, InteriorDesignRenderingSubmission, WatermarkRemovalSubmission, LineArtConversionSubmission, AnimeToRealSubmission, RealToAnimeSubmission, - ImageOutpaintingSubmission, FiveViewGenerationSubmission + 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, @@ -71,6 +74,19 @@ INTERIOR_DESIGN_STYLE_CHOICES = [ 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 # ============================================================================ @@ -131,23 +147,19 @@ async def submit_task_with_retry(endpoint: str, payload: dict, task_name: str, m ) if response.status_code == 429: - # System is busy, implement exponential backoff 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: - # Max retries exceeded raise Exception("The system is currently busy, please try again later") elif response.status_code >= 500: - # Server error 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: - # Client error (except 429) logger.error(f"Client error {response.status_code} for {task_name}") raise Exception("Invalid request parameters, please check your input") @@ -162,8 +174,8 @@ async def submit_task_with_retry(endpoint: str, payload: dict, task_name: str, m 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 ["系统当前繁忙", "服务暂时不可用", "请求参数有误", "网络连接超时", "无法连接到服务器"]): - # Re-raise user-friendly messages (Chinese messages preserved for detection) + 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 @@ -234,11 +246,10 @@ async def get_task_result(task_id: str) -> dict: raise async def load_image_from_result(result_data: dict) -> Image.Image: - """Load PIL Image from task result data - simplified version matching original logic""" + """Load PIL Image from task result data via URL download only""" if not result_data: raise ValueError("No result data received") - # Get result_url (the field backend actually returns) result_url = result_data.get("result_url") logger.info(f"🔗 Result URL: {result_url}") @@ -328,30 +339,14 @@ def load_example_result(prompt: str, resolution: str, result_path: str = None): return create_placeholder_image_inline(prompt, resolution) def load_line_art_example_result(input_image_path, result_path = None): - """ - Load line art conversion example input and pre-generated result images - - 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 - - Args: - input_image_path: input image path (str) or PIL.Image object - result_path: result image path (str) or PIL.Image object - - Returns: - Tuple[PIL.Image, PIL.Image]: (input image, result image) - """ + """Load line art conversion example images""" try: - # 1. 处理输入图片 - 支持字符串路径和PIL.Image对象 input_image = None if isinstance(input_image_path, Image.Image): - # 缓存生成模式:直接使用PIL.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) @@ -362,21 +357,16 @@ def load_line_art_example_result(input_image_path, result_path = None): logger.warning(f"Unexpected input_image_path type: {type(input_image_path)}") input_image = create_placeholder_image_inline("Input Image", "1024x1024") - # 2. 处理结果图片 - 支持字符串路径和PIL.Image对象 result_image = None if isinstance(result_path, Image.Image): - # 缓存生成模式:直接使用PIL.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) - # 运行时模式:从LINE_ART_CONVERSION_EXAMPLES_WITH_RESULTS查找结果图片 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: @@ -385,15 +375,13 @@ def load_line_art_example_result(input_image_path, result_path = None): result_image = Image.open(example_path) return (input_image, result_image) else: - # Cache mode: identify example by image size 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", # example1的尺寸 - (720, 1104): "examples/results/line_art_example2.jpg", # example2的尺寸 - (736, 1308): "examples/results/line_art_example3.jpg", # example3的尺寸 + (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) @@ -456,23 +444,14 @@ def load_anime_to_real_example_result(input_image_path, result_path=None): return (input_placeholder, result_placeholder) def load_real_to_anime_example_result(input_image_path, result_path=None): - """ - Load example for Real to Anime: input image and pre-generated result image - - Supports two modes (mirrors Line Art Conversion implementation): - 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 - """ + """Load example for Real to Anime conversion""" 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 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) @@ -483,21 +462,16 @@ def load_real_to_anime_example_result(input_image_path, result_path=None): logger.warning(f"Unexpected input_image_path type: {type(input_image_path)}") input_image = create_placeholder_image_inline("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 real to anime result (cache mode): {result_image.size}") return (input_image, result_image) - # 运行时模式:从REAL_TO_ANIME_EXAMPLES_WITH_RESULTS查找结果图片 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: @@ -506,15 +480,13 @@ def load_real_to_anime_example_result(input_image_path, result_path=None): result_image = Image.open(example_result) return (input_image, result_image) else: - # Cache mode: identify example by image size 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", # example1的尺寸 - (736, 946): "examples/results/real_to_anime_example2.jpg", # example2的尺寸 - (1206, 796): "examples/results/real_to_anime_example3.jpg", # example3的尺寸 + (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) @@ -582,7 +554,7 @@ def load_dual_output_example(input_path, param1=None, param2=None): placeholder = create_placeholder_image_inline("Error", "1024x1024") return placeholder, placeholder -def load_five_view_example(input_path, result_path=None): +def load_five_view_example(input_path): """Load five view generation example with input and result images""" try: # Handle input image @@ -593,31 +565,191 @@ def load_five_view_example(input_path, result_path=None): else: input_image = create_placeholder_image_inline("Input", "1024x1024") - # Handle result image + # 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 - elif isinstance(result_path, str) and os.path.exists(result_path): - result_image = Image.open(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: - # Try to find result from FIVE_VIEW_GENERATION_EXAMPLES_WITH_RESULTS - from examples_config import FIVE_VIEW_GENERATION_EXAMPLES_WITH_RESULTS + # 尝试从FIGURE_3D_EXAMPLES_WITH_RESULTS中查找匹配的结果 + from examples_config import FIGURE_3D_EXAMPLES_WITH_RESULTS result_image = None - if 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] + # 当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 five view result: {result_path_found}") + logger.info(f"Found figure 3D result: {result_path_found}") break if not result_image: - result_image = create_placeholder_image_inline("Five View Result", "1024x1024") + 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 five view example: {e}") + logger.error(f"Error loading character figure collaboration example: {e}") placeholder = create_placeholder_image_inline("Error", "1024x1024") return placeholder, placeholder @@ -824,7 +956,7 @@ async def submit_image_to_image_task(pil_image: Image.Image) -> dict: return await submit_task_with_retry( endpoint="api/v1/tasks/image-to-image", payload=submission.to_api_payload(), - task_name="图像转换" + task_name="Image Conversion" ) async def submit_photo_style_task(pil_image: Image.Image, style_preset: str) -> dict: @@ -836,7 +968,7 @@ async def submit_photo_style_task(pil_image: Image.Image, style_preset: str) -> return await submit_task_with_retry( endpoint="api/v1/tasks/photo-style", payload=submission.to_api_payload(), - task_name="摄影风格转换" + task_name="Photo Style Transfer" ) async def submit_interior_design_task(pil_image: Image.Image, design_style: str) -> dict: @@ -848,7 +980,7 @@ async def submit_interior_design_task(pil_image: Image.Image, design_style: str) return await submit_task_with_retry( endpoint="api/v1/tasks/interior-design-rendering", payload=submission.to_api_payload(), - task_name="室内设计渲染" + task_name="Interior Design Rendering" ) async def submit_watermark_removal_task(pil_image: Image.Image) -> dict: @@ -860,7 +992,7 @@ async def submit_watermark_removal_task(pil_image: Image.Image) -> dict: return await submit_task_with_retry( endpoint="api/v1/tasks/watermark-removal", payload=submission.to_api_payload(), - task_name="水印移除" + task_name="Watermark Removal" ) async def submit_line_art_task(pil_image: Image.Image) -> dict: @@ -872,7 +1004,7 @@ async def submit_line_art_task(pil_image: Image.Image) -> dict: return await submit_task_with_retry( endpoint="api/v1/tasks/line-art-conversion", payload=submission.to_api_payload(), - task_name="线稿转换" + task_name="Line Art Conversion" ) async def submit_image_outpainting_task(pil_image: Image.Image, expand_height: float, expand_width: float) -> dict: @@ -888,7 +1020,7 @@ async def submit_image_outpainting_task(pil_image: Image.Image, expand_height: f return await submit_task_with_retry( endpoint="api/v1/tasks/image-outpainting", payload=submission.to_api_payload(), - task_name="图像扩展" + task_name="Image Outpainting" ) async def submit_anime_to_real_task(pil_image: Image.Image) -> dict: @@ -900,7 +1032,7 @@ async def submit_anime_to_real_task(pil_image: Image.Image) -> dict: return await submit_task_with_retry( endpoint="api/v1/tasks/anime-to-real", payload=submission.to_api_payload(), - task_name="二次元转真人" + task_name="Anime to Real" ) async def submit_real_to_anime_task(pil_image: Image.Image) -> dict: @@ -912,7 +1044,7 @@ async def submit_real_to_anime_task(pil_image: Image.Image) -> dict: return await submit_task_with_retry( endpoint="api/v1/tasks/real-to-anime", payload=submission.to_api_payload(), - task_name="真人转动漫" + task_name="Real to Anime" ) async def submit_five_view_generation_task(input_image: Image.Image) -> dict: @@ -929,6 +1061,42 @@ async def submit_five_view_generation_task(input_image: Image.Image) -> dict: 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 # ============================================================================ @@ -976,16 +1144,13 @@ async def generate_text_to_image(prompt: str, resolution: str, progress=None): logger.info(f"Starting text-to-image generation: {prompt[:50]}...") start_time = datetime.now() - # Update UI to show processing yield ( - None, # result_image - "🚀 Submitting task to AI server...", # status_info - "**Tip**: The AI is processing your description and preparing to create...", # image_info - False, # generate_btn visible - True # cancel_btn visible + None, + "🚀 Submitting task to AI server...", + "**Tip**: The AI is processing your description and preparing to create...", + False, + True ) - - # Submit task try: task_data = await submit_text_to_image_task(prompt, resolution) task_id = task_data.get("task_id") @@ -998,8 +1163,7 @@ async def generate_text_to_image(prompt: str, resolution: str, progress=None): except Exception as e: logger.error(f"Task submission failed: {e}") - # Show user-friendly error message - error_message = str(e) if any(msg in str(e) for msg in ["系统当前繁忙", "服务暂时不可用", "请求参数有误", "网络连接超时", "无法连接到服务器", "提交任务时发生错误"]) else "Task submission failed, please try again later" + 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}", @@ -1084,7 +1248,7 @@ async def generate_text_to_image(prompt: str, resolution: str, progress=None): logger.error(f"Error polling task status: {e}") yield ( None, - f"❌ Status query failed: {str(e)}", + "❌ Status query failed. Please try again later.", "", True, False @@ -1104,7 +1268,7 @@ async def generate_text_to_image(prompt: str, resolution: str, progress=None): logger.error(f"Unexpected error in text-to-image generation: {e}") yield ( None, - f"❌ An error occurred during generation: {str(e)}", + "❌ An unexpected error occurred during generation. Please try again later.", "", True, False @@ -1130,16 +1294,13 @@ async def generic_image_processing( logger.info(f"Starting {task_name} processing...") start_time = datetime.now() - # Update UI to show processing yield ( - None, # result_image - f"🚀 Submitting {task_name} task to the AI server...", # status_info - f"**Tip**: The AI is analyzing your image and preparing to start {task_name}...", # image_info - False, # generate_btn visible - True # cancel_btn visible + 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 ) - - # Submit task try: task_data = await submit_func(input_image, *submit_args) task_id = task_data.get("task_id") @@ -1153,7 +1314,7 @@ async def generic_image_processing( 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 ["系统当前繁忙", "服务暂时不可用", "请求参数有误", "网络连接超时", "无法连接到服务器", "提交任务时发生错误"]) else "Task submission failed, please try again later" + 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}", @@ -1217,15 +1378,15 @@ async def generic_image_processing( # Update status with task-specific tips task_tips = { - "图像转换": ["🔄 Processing: analyzing image features", "🎯 Optimizing: applying advanced image processing"], - "五视角生成": ["👁️ Analyzing: understanding facial and pose features", "🔄 Generating: creating multiple viewpoints"], - "摄影风格转换": ["📸 Stylizing: applying professional photography techniques", "✨ Enhancing: optimizing lighting and colors"], - "室内设计渲染": ["🏠 Designing: composing interior layout", "🎨 Rendering: adding furniture and decor"], - "水印移除": ["🚫 Detecting: locating watermark areas", "🔧 Repairing: intelligently inpainting background"], - "线稿转换": ["✏️ Outlining: extracting contours", "🎨 Refining: improving line details"], - "图像扩展": ["📐 Extending: adding coherent border content", "🔄 Blending: ensuring seamless continuity"], - "二次元转真人": ["👤 Converting: mapping anime features to realistic ones", "🎭 Refining: adjusting facial details"], - "真人转动漫": ["🎌 Stylizing: applying anime art style", "✨ Enhancing: optimizing anime effects"] + "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"]) @@ -1245,7 +1406,7 @@ async def generic_image_processing( logger.error(f"Error polling {task_name} task status: {e}") yield ( None, - f"❌ Status query failed: {str(e)}", + "❌ Status query failed. Please try again later.", "", True, False @@ -1265,7 +1426,7 @@ async def generic_image_processing( logger.error(f"Unexpected error in {task_name}: {e}") yield ( None, - f"❌ An error occurred during {task_name}: {str(e)}", + f"❌ An unexpected error occurred during {task_name}. Please try again later.", "", True, False @@ -1339,6 +1500,22 @@ async def generate_five_view(input_image: Image.Image, progress=None): ): 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 @@ -1363,9 +1540,9 @@ def create_text_to_image_interface(): resolution_input = gr.Dropdown( label="Resolution", choices=[ + "portrait - 896x1152 (3:4)", "square - 1024x1024 (1:1)", - "landscape - 1152x896 (4:3)", - "portrait - 896x1152 (3:4)" + "landscape - 1152x896 (4:3)" ], value="square - 1024x1024 (1:1)", interactive=True @@ -1465,583 +1642,737 @@ def create_main_interface(): fill_width=True ) as interface: - # Main layout with sidebar - with gr.Sidebar(): - gr.Markdown("# AI Toolbox") - gr.Markdown("Choose a feature below to get started") - - # Creation Tools group - gr.Markdown("## Creation Tools") - with gr.Group(): - text_to_image_btn = gr.Button("Text to Image", size="sm", variant="secondary") - image_convert_btn = gr.Button("Image to Image", size="sm", variant="secondary") - five_view_btn_sidebar = gr.Button("Five-View Generation", size="sm", variant="secondary") - - gr.Markdown("---") - - # Style Transfer group - gr.Markdown("## Style Transfer") - with gr.Group(): - photo_style_btn_sidebar = gr.Button("Photo Style", size="sm", variant="secondary") - interior_btn_sidebar = gr.Button("Interior Design", size="sm", variant="secondary") - - gr.Markdown("---") - - # Image Processing group - gr.Markdown("## Image Processing") - with gr.Group(): - watermark_btn_sidebar = gr.Button("Watermark Removal", size="sm", variant="secondary") - line_art_btn_sidebar = gr.Button("Line Art Conversion", size="sm", variant="secondary") - expand_btn_sidebar = gr.Button("Image Outpainting", size="sm", variant="secondary") - - gr.Markdown("---") - - # Anime Conversion group - gr.Markdown("## Anime Conversion") - with gr.Group(): - anime_to_real_btn_sidebar = gr.Button("Anime to Real", size="sm", variant="secondary") - real_to_anime_btn_sidebar = gr.Button("Real to Anime", size="sm", variant="secondary") - - # 主内容区域 - with gr.Column(scale=4): - # 欢迎页面 - 使用推荐的elem_id方法 - welcome_content = gr.HTML(""" -
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
+ # Main layout with sidebar using Row and Column + with gr.Row(): + # Sidebar column + with gr.Column(scale=1, min_width=250): + gr.Markdown("# AI Toolbox") + gr.Markdown("Choose a feature below to get started") + + # Creation Tools group + gr.Markdown("## Creation Tools") + with gr.Group(): + text_to_image_btn = gr.Button("Text to Image", size="sm", variant="secondary") + image_convert_btn = gr.Button("Image to Image", size="sm", variant="secondary") + five_view_btn_sidebar = gr.Button("Five-View Generation", size="sm", variant="secondary") + figure_3d_btn_sidebar = gr.Button("2D to 3D Figure", size="sm", variant="secondary") + character_figure_btn_sidebar = gr.Button("Character Figure Collaboration", size="sm", variant="secondary") + + + gr.Markdown("---") + + # Style Transfer group + gr.Markdown("## Style Transfer") + with gr.Group(): + photo_style_btn_sidebar = gr.Button("Photo Style", size="sm", variant="secondary") + interior_btn_sidebar = gr.Button("Interior Design", size="sm", variant="secondary") + + gr.Markdown("---") + + # Image Processing group + gr.Markdown("## Image Processing") + with gr.Group(): + watermark_btn_sidebar = gr.Button("Watermark Removal", size="sm", variant="secondary") + line_art_btn_sidebar = gr.Button("Line Art Conversion", size="sm", variant="secondary") + expand_btn_sidebar = gr.Button("Image Outpainting", size="sm", variant="secondary") + + gr.Markdown("---") + + # Anime Conversion group + gr.Markdown("## Anime Conversion") + with gr.Group(): + anime_to_real_btn_sidebar = gr.Button("Anime to Real", size="sm", variant="secondary") + real_to_anime_btn_sidebar = gr.Button("Real to Anime", size="sm", variant="secondary") + + # Main content area + with gr.Column(scale=4): + # 欢迎页面 - 使用推荐的elem_id方法 + welcome_content = gr.HTML(""" +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
+