Spaces:
Restarting
on
Zero
Restarting
on
Zero
| import logging | |
| from dataclasses import dataclass, field | |
| from typing import Dict, List, Optional | |
| logger = logging.getLogger(__name__) | |
| class InpaintingTemplate: | |
| """Data class representing an inpainting template.""" | |
| key: str | |
| name: str | |
| category: str | |
| icon: str | |
| description: str | |
| # Prompt templates | |
| prompt_template: str | |
| negative_prompt: str | |
| # Recommended parameters | |
| controlnet_conditioning_scale: float = 0.7 | |
| feather_radius: int = 8 | |
| guidance_scale: float = 7.5 | |
| num_inference_steps: int = 25 | |
| # Conditioning type preference | |
| preferred_conditioning: str = "canny" # "canny" or "depth" | |
| # Tips for users | |
| usage_tips: List[str] = field(default_factory=list) | |
| class InpaintingTemplateManager: | |
| """ | |
| Manages inpainting templates for various use cases. | |
| Provides categorized presets optimized for different inpainting scenarios | |
| including object replacement, removal, style transfer, and enhancement. | |
| Attributes: | |
| TEMPLATES: Dictionary of all available templates | |
| CATEGORIES: List of category names in display order | |
| Example: | |
| >>> manager = InpaintingTemplateManager() | |
| >>> template = manager.get_template("object_replacement") | |
| >>> print(template.prompt_template) | |
| """ | |
| TEMPLATES: Dict[str, InpaintingTemplate] = { | |
| # Object Replacement Category | |
| "object_replacement": InpaintingTemplate( | |
| key="object_replacement", | |
| name="Object Replacement", | |
| category="Replacement", | |
| icon="🔄", | |
| description="Replace selected objects with new content while preserving context", | |
| prompt_template="{content}, seamlessly integrated into scene, matching lighting and perspective, realistic placement", | |
| negative_prompt=( | |
| "inconsistent lighting, wrong perspective, mismatched colors, " | |
| "visible seams, floating objects, unrealistic placement, original object, " | |
| "poorly integrated, disconnected from scene, keeping original" | |
| ), | |
| controlnet_conditioning_scale=0.48, | |
| feather_radius=14, | |
| guidance_scale=10.0, | |
| num_inference_steps=38, | |
| preferred_conditioning="canny", | |
| usage_tips=[ | |
| "Draw mask PRECISELY around the object to replace (include small margin)", | |
| "Be specific: Instead of 'plant', use 'large green potted fern with detailed leaves'", | |
| "📝 Example prompts:", | |
| " • 'elegant white ceramic teacup with delicate gold rim and floral pattern'", | |
| " • 'modern silver laptop computer with sleek metallic finish'", | |
| " • 'vintage wooden desk lamp with warm brass details'" | |
| ] | |
| ), | |
| "face_swap": InpaintingTemplate( | |
| key="face_swap", | |
| name="Face Enhancement", | |
| category="Replacement", | |
| icon="👤", | |
| description="Enhance or modify facial features while maintaining identity cues", | |
| prompt_template="{content}, natural skin texture, proper facial proportions, realistic lighting, detailed facial features", | |
| negative_prompt=( | |
| "deformed face, asymmetric features, unnatural skin, " | |
| "plastic appearance, wrong eye direction, blurry features, " | |
| "artificial smoothing, uncanny valley, distorted proportions" | |
| ), | |
| controlnet_conditioning_scale=0.88, | |
| feather_radius=6, | |
| guidance_scale=8.5, | |
| num_inference_steps=35, | |
| preferred_conditioning="canny", | |
| usage_tips=[ | |
| "Draw mask CAREFULLY around face outline (avoid hair and neck)", | |
| "High conditioning preserves facial structure - good for subtle enhancements", | |
| "📝 Example prompts:", | |
| " • 'warm friendly smile with bright eyes and natural expression'", | |
| " • 'professional headshot with confident neutral expression'", | |
| " • 'gentle smile with soft natural lighting on face'" | |
| ] | |
| ), | |
| "clothing_change": InpaintingTemplate( | |
| key="clothing_change", | |
| name="Clothing Change", | |
| category="Replacement", | |
| icon="👕", | |
| description="Change clothing color, pattern, style, and material (moderate changes)", | |
| prompt_template="transform to {content}, vivid fabric texture, natural folds and wrinkles, correct fit, proper color saturation", | |
| negative_prompt=( | |
| "wrong body proportions, floating fabric, unrealistic wrinkles, " | |
| "mismatched lighting, visible edges, original clothing style, " | |
| "keeping same color, faded colors, unchanged appearance, partial change" | |
| ), | |
| controlnet_conditioning_scale=0.42, | |
| feather_radius=14, | |
| guidance_scale=10.5, | |
| num_inference_steps=38, | |
| preferred_conditioning="depth", | |
| usage_tips=[ | |
| "Best for: Similar color changes (black→navy, white→cream) and pattern changes", | |
| "For extreme changes (black↔white), use 'Dramatic Color Change' template instead", | |
| "📝 Example prompts:", | |
| " • 'deep navy blue formal blazer with fine texture and professional fit'", | |
| " • 'bright red polo shirt with clean collar and soft fabric'", | |
| " • 'charcoal gray sweater with ribbed knit texture'" | |
| ] | |
| ), | |
| "dramatic_color_change": InpaintingTemplate( | |
| key="dramatic_color_change", | |
| name="Dramatic Color Change", | |
| category="Replacement", | |
| icon="🎨", | |
| description="Extreme color transformations (dark↔light, black↔white)", | |
| prompt_template="completely transform to {content}, vivid color saturation, high contrast, sharp color definition, clear distinct color", | |
| negative_prompt=( | |
| "original color, keeping same shade, partial change, color bleeding, " | |
| "faded colors, mixed tones, subtle change, gradual transition, " | |
| "original appearance, unchanged, dark remnants, light patches" | |
| ), | |
| controlnet_conditioning_scale=0.38, | |
| feather_radius=16, | |
| guidance_scale=12.0, | |
| num_inference_steps=42, | |
| preferred_conditioning="depth", | |
| usage_tips=[ | |
| "Optimized for: black→white, white→black, dark→light extreme transformations", | |
| "Use VERY vivid descriptors: 'PURE WHITE', 'JET BLACK', 'BRIGHT crimson red'", | |
| "📝 Example prompts:", | |
| " • 'pure white dress shirt with crisp texture and sharp collar'", | |
| " • 'jet black leather jacket with smooth matte finish'", | |
| " • 'bright crimson red blazer with vivid saturated color'" | |
| ] | |
| ), | |
| "clothing_addition": InpaintingTemplate( | |
| key="clothing_addition", | |
| name="Add Accessories", | |
| category="Replacement", | |
| icon="👔", | |
| description="Add ties, pockets, buttons, or accessories to clothing (Advanced)", | |
| prompt_template="{content}, clearly visible, highly detailed accessory, seamlessly integrated into clothing, proper placement and perspective", | |
| negative_prompt=( | |
| "missing details, incomplete, floating objects, disconnected, " | |
| "unrealistic placement, wrong perspective, blurry, poorly integrated, " | |
| "invisible, faint, unclear, hidden, absent, not visible" | |
| ), | |
| controlnet_conditioning_scale=0.25, | |
| feather_radius=12, | |
| guidance_scale=14.0, | |
| num_inference_steps=50, | |
| preferred_conditioning="depth", | |
| usage_tips=[ | |
| "⚡ Advanced feature: For adding neckties, pocket squares, badges, or buttons", | |
| "Draw mask from NECK to CHEST (vertical strip) for ties, not just collar area", | |
| "📝 Example prompts:", | |
| " • 'burgundy silk necktie with diagonal stripes and Windsor knot, hanging down from collar to chest'", | |
| " • 'white pocket square with neat fold, visible in breast pocket'", | |
| " • 'silver lapel pin with detailed engraving on left collar'", | |
| "⚠️ TIP: For ties, mask should cover the entire length where tie should appear" | |
| ] | |
| ), | |
| # Object Removal Category | |
| "object_removal": InpaintingTemplate( | |
| key="object_removal", | |
| name="Object Removal", | |
| category="Removal", | |
| icon="🗑️", | |
| description="Remove unwanted objects and fill with matching background", | |
| prompt_template="clean background, seamless continuation, {content}", | |
| negative_prompt=( | |
| "visible patches, color mismatch, texture inconsistency, " | |
| "ghost artifacts, blur spots, repeated patterns, visible seams" | |
| ), | |
| controlnet_conditioning_scale=0.48, | |
| feather_radius=14, | |
| guidance_scale=8.0, | |
| num_inference_steps=30, | |
| preferred_conditioning="canny", | |
| usage_tips=[ | |
| "Draw mask slightly BEYOND object edges for better blending", | |
| "Describe what background SHOULD look like (e.g., 'grass lawn', 'wooden floor')", | |
| "📝 Example prompts:", | |
| " • 'clean grass lawn with natural green color'", | |
| " • 'smooth wooden floor with consistent grain pattern'", | |
| " • 'plain white wall with even texture'" | |
| ] | |
| ), | |
| "watermark_removal": InpaintingTemplate( | |
| key="watermark_removal", | |
| name="Watermark Removal", | |
| category="Removal", | |
| icon="💧", | |
| description="Remove watermarks and text overlays", | |
| prompt_template="clean image, no text, seamless background, {content}", | |
| negative_prompt=( | |
| "text, watermark, logo, signature, letters, numbers, visible artifacts, " | |
| "color inconsistency, blur, remnants, ghost text" | |
| ), | |
| controlnet_conditioning_scale=0.45, | |
| feather_radius=12, | |
| guidance_scale=8.5, | |
| num_inference_steps=30, | |
| preferred_conditioning="canny", | |
| usage_tips=[ | |
| "Draw mask covering ALL watermark/text areas precisely", | |
| "Describe what SHOULD be there instead (e.g., 'sky', 'fabric texture')", | |
| "📝 Example prompts:", | |
| " • 'clean blue sky with smooth gradient'", | |
| " • 'natural skin texture without marks'", | |
| " • 'smooth fabric surface with consistent color'" | |
| ] | |
| ), | |
| "blemish_removal": InpaintingTemplate( | |
| key="blemish_removal", | |
| name="Blemish Removal", | |
| category="Removal", | |
| icon="✨", | |
| description="Remove skin blemishes, scratches, or small imperfections", | |
| prompt_template="clean smooth surface, natural texture, {content}", | |
| negative_prompt=( | |
| "artificial smoothing, plastic texture, visible editing, " | |
| "color patches, unnatural appearance, over-processed" | |
| ), | |
| controlnet_conditioning_scale=0.6, | |
| feather_radius=6, | |
| guidance_scale=6.5, | |
| num_inference_steps=20, | |
| preferred_conditioning="canny", | |
| usage_tips=[ | |
| "Draw small precise masks for EACH blemish/imperfection", | |
| "Lower guidance (6.5) preserves natural skin texture", | |
| "📝 Example prompts:", | |
| " • 'natural clean skin with smooth texture'", | |
| " • 'smooth surface without scratches or marks'", | |
| " • 'clear skin with natural pores and texture'" | |
| ] | |
| ), | |
| # Style Transfer Category | |
| "style_artistic": InpaintingTemplate( | |
| key="style_artistic", | |
| name="Artistic Style", | |
| category="Style", | |
| icon="🎨", | |
| description="Apply artistic style to selected region", | |
| prompt_template="{content}, distinctive artistic style, strong painterly effect, creative interpretation, visible brushstrokes", | |
| negative_prompt=( | |
| "photorealistic, plain, boring, low contrast, unchanged, " | |
| "inconsistent style, harsh transitions, original appearance, realistic photo" | |
| ), | |
| controlnet_conditioning_scale=0.52, | |
| feather_radius=12, | |
| guidance_scale=11.5, | |
| num_inference_steps=38, | |
| preferred_conditioning="canny", | |
| usage_tips=[ | |
| "Works best on larger areas (faces, clothing, backgrounds) for visible transformation", | |
| "Be VERY specific about art style for best results", | |
| "📝 Example prompts:", | |
| " • 'impressionist oil painting with visible thick brushstrokes and vibrant colors'", | |
| " • 'watercolor painting with soft edges and delicate color washes'", | |
| " • 'Van Gogh style with swirling brushstrokes and bold color contrasts'" | |
| ] | |
| ), | |
| "style_vintage": InpaintingTemplate( | |
| key="style_vintage", | |
| name="Vintage Look", | |
| category="Style", | |
| icon="📻", | |
| description="Apply vintage or retro aesthetic to selected area", | |
| prompt_template="{content}, strong vintage aesthetic, warm sepia tones, film grain texture, nostalgic atmosphere", | |
| negative_prompt=( | |
| "modern, digital, cold colors, harsh contrast, " | |
| "oversaturated, neon colors, contemporary look, clean digital, crisp" | |
| ), | |
| controlnet_conditioning_scale=0.55, | |
| feather_radius=14, | |
| guidance_scale=10.5, | |
| num_inference_steps=35, | |
| preferred_conditioning="canny", | |
| usage_tips=[ | |
| "Works best on medium to large regions for visible aesthetic change", | |
| "Specify era and style for best results", | |
| "📝 Example prompts:", | |
| " • '1920s sepia photograph with faded brown tones and soft grain'", | |
| " • '1970s vintage photo with warm orange tones and slight film grain'", | |
| " • '1950s Kodachrome with saturated warm colors and nostalgic feel'" | |
| ] | |
| ), | |
| "style_anime": InpaintingTemplate( | |
| key="style_anime", | |
| name="Anime Style", | |
| category="Style", | |
| icon="🎌", | |
| description="Transform selected region to anime/illustration style", | |
| prompt_template="{content}, anime illustration style, clean sharp lines, vibrant saturated colors, cel-shaded with flat colors", | |
| negative_prompt=( | |
| "photorealistic, blurry lines, muddy colors, realistic photo, " | |
| "3D render, uncanny valley, western cartoon, gradient shading, photographic" | |
| ), | |
| controlnet_conditioning_scale=0.48, | |
| feather_radius=10, | |
| guidance_scale=12.5, | |
| num_inference_steps=40, | |
| preferred_conditioning="canny", | |
| usage_tips=[ | |
| "⚡ DRAMATIC transformation - best for portraits and characters", | |
| "Expect significant stylistic changes from realistic to anime", | |
| "📝 Example prompts:", | |
| " • 'modern anime style with large expressive eyes and vibrant colors'", | |
| " • 'Studio Ghibli style with soft features and warm color palette'", | |
| " • 'manga style with clean black lines and cel-shaded coloring'" | |
| ] | |
| ), | |
| # Detail Enhancement Category | |
| "detail_enhance": InpaintingTemplate( | |
| key="detail_enhance", | |
| name="Detail Enhancement", | |
| category="Enhancement", | |
| icon="🔍", | |
| description="Add fine details and textures to selected area", | |
| prompt_template="{content}, highly detailed, intricate textures, fine details, sharp focus", | |
| negative_prompt=( | |
| "blurry, smooth, low detail, soft focus, " | |
| "oversimplified, lacking texture" | |
| ), | |
| controlnet_conditioning_scale=0.85, | |
| feather_radius=4, | |
| guidance_scale=8.0, | |
| num_inference_steps=30, | |
| preferred_conditioning="depth", | |
| usage_tips=[ | |
| "High conditioning (0.85) preserves overall structure while adding detail", | |
| "Best for adding fine details to existing objects", | |
| "📝 Example prompts:", | |
| " • 'highly detailed fabric with visible weave and fine threads'", | |
| " • 'intricate wood grain with natural knots and detailed texture'", | |
| " • 'sharp facial features with fine skin pores and detail'" | |
| ] | |
| ), | |
| "texture_add": InpaintingTemplate( | |
| key="texture_add", | |
| name="Texture Addition", | |
| category="Enhancement", | |
| icon="🧱", | |
| description="Add or enhance surface textures", | |
| prompt_template="{content} texture, realistic surface detail, natural material appearance", | |
| negative_prompt=( | |
| "flat, smooth, unrealistic, plastic, " | |
| "wrong material, inconsistent texture" | |
| ), | |
| controlnet_conditioning_scale=0.8, | |
| feather_radius=5, | |
| guidance_scale=7.5, | |
| num_inference_steps=25, | |
| preferred_conditioning="depth", | |
| usage_tips=[ | |
| "Specify material type clearly for best results", | |
| "Depth conditioning preserves 3D form while changing texture", | |
| "📝 Example prompts:", | |
| " • 'rough wood texture with natural grain and knots'", | |
| " • 'soft cotton fabric with gentle weave pattern'", | |
| " • 'smooth marble surface with subtle veining'" | |
| ] | |
| ), | |
| "lighting_fix": InpaintingTemplate( | |
| key="lighting_fix", | |
| name="Lighting Correction", | |
| category="Enhancement", | |
| icon="💡", | |
| description="Correct or enhance lighting in selected area", | |
| prompt_template="{content}, proper lighting, natural shadows, balanced exposure", | |
| negative_prompt=( | |
| "harsh shadows, overexposed, underexposed, " | |
| "flat lighting, unnatural highlights" | |
| ), | |
| controlnet_conditioning_scale=0.65, | |
| feather_radius=15, | |
| guidance_scale=7.0, | |
| num_inference_steps=25, | |
| preferred_conditioning="depth", | |
| usage_tips=[ | |
| "Use large feather (15px) for smooth lighting transitions", | |
| "Best for fixing uneven lighting or adding natural light", | |
| "📝 Example prompts:", | |
| " • 'soft natural lighting from window, gentle shadows'", | |
| " • 'balanced exposure with warm golden hour light'", | |
| " • 'even studio lighting with soft diffused shadows'" | |
| ] | |
| ), | |
| # Background Category | |
| "background_extend": InpaintingTemplate( | |
| key="background_extend", | |
| name="Background Extension", | |
| category="Background", | |
| icon="📐", | |
| description="Extend image background seamlessly", | |
| prompt_template="seamless background extension, {content}, consistent style and lighting", | |
| negative_prompt=( | |
| "visible seams, style mismatch, lighting inconsistency, " | |
| "repeated elements, unnatural continuation, abrupt changes" | |
| ), | |
| controlnet_conditioning_scale=0.55, | |
| feather_radius=20, | |
| guidance_scale=8.0, | |
| num_inference_steps=32, | |
| preferred_conditioning="canny", | |
| usage_tips=[ | |
| "Draw mask on area to extend (edges of image)", | |
| "Large feather (20px) ensures smooth blending with existing background", | |
| "📝 Example prompts:", | |
| " • 'continue the wooden floor with same grain pattern'", | |
| " • 'extend blue sky with matching clouds and lighting'", | |
| " • 'seamless continuation of brick wall texture'" | |
| ] | |
| ), | |
| "background_replace": InpaintingTemplate( | |
| key="background_replace", | |
| name="Background Replacement", | |
| category="Background", | |
| icon="🖼️", | |
| description="Replace background while keeping subject intact", | |
| prompt_template="{content}, professional background scene, seamless integration with subject, matching lighting and atmosphere", | |
| negative_prompt=( | |
| "floating subject, inconsistent lighting, disconnected, " | |
| "wrong perspective, visible edges, color mismatch, original background, " | |
| "poor integration, obvious composite" | |
| ), | |
| controlnet_conditioning_scale=0.60, | |
| feather_radius=12, | |
| guidance_scale=9.5, | |
| num_inference_steps=35, | |
| preferred_conditioning="depth", | |
| usage_tips=[ | |
| "Draw mask around ENTIRE background (leave subject unmasked with small margin)", | |
| "Include lighting description to match subject for natural results", | |
| "📝 Example prompts:", | |
| " • 'professional photography studio with white backdrop and soft lighting'", | |
| " • 'modern minimalist office with white walls and bright natural lighting'", | |
| " • 'sunny beach with blue ocean and golden hour lighting'" | |
| ] | |
| ), | |
| } | |
| # Category display order | |
| CATEGORIES = ["Replacement", "Removal", "Style", "Enhancement", "Background"] | |
| def __init__(self): | |
| """Initialize the InpaintingTemplateManager.""" | |
| logger.info(f"InpaintingTemplateManager initialized with {len(self.TEMPLATES)} templates") | |
| def get_all_templates(self) -> Dict[str, InpaintingTemplate]: | |
| """ | |
| Get all available templates. | |
| Returns | |
| ------- | |
| dict | |
| Dictionary of all templates keyed by template key | |
| """ | |
| return self.TEMPLATES | |
| def get_template(self, key: str) -> Optional[InpaintingTemplate]: | |
| """ | |
| Get a specific template by key. | |
| Parameters | |
| ---------- | |
| key : str | |
| Template identifier | |
| Returns | |
| ------- | |
| InpaintingTemplate or None | |
| Template if found, None otherwise | |
| """ | |
| return self.TEMPLATES.get(key) | |
| def get_templates_by_category(self, category: str) -> List[InpaintingTemplate]: | |
| """ | |
| Get all templates in a specific category. | |
| Parameters | |
| ---------- | |
| category : str | |
| Category name | |
| Returns | |
| ------- | |
| list | |
| List of templates in the category | |
| """ | |
| return [t for t in self.TEMPLATES.values() if t.category == category] | |
| def get_categories(self) -> List[str]: | |
| """ | |
| Get list of all categories in display order. | |
| Returns | |
| ------- | |
| list | |
| Category names | |
| """ | |
| return self.CATEGORIES | |
| def get_template_choices_sorted(self) -> List[str]: | |
| """ | |
| Get template choices formatted for Gradio dropdown. | |
| Returns list of display strings sorted by category then A-Z. | |
| Format: "icon Name" | |
| Returns | |
| ------- | |
| list | |
| Formatted display strings for dropdown | |
| """ | |
| display_list = [] | |
| for category in self.CATEGORIES: | |
| templates = self.get_templates_by_category(category) | |
| for template in sorted(templates, key=lambda t: t.name): | |
| display_name = f"{template.icon} {template.name}" | |
| display_list.append(display_name) | |
| return display_list | |
| def get_template_key_from_display(self, display_name: str) -> Optional[str]: | |
| """ | |
| Get template key from display name. | |
| Parameters | |
| ---------- | |
| display_name : str | |
| Display string like "🔄 Object Replacement" | |
| Returns | |
| ------- | |
| str or None | |
| Template key if found | |
| """ | |
| if not display_name: | |
| return None | |
| for key, template in self.TEMPLATES.items(): | |
| if f"{template.icon} {template.name}" == display_name: | |
| return key | |
| return None | |
| def get_parameters_for_template(self, key: str) -> Dict[str, any]: | |
| """ | |
| Get recommended parameters for a template. | |
| Parameters | |
| ---------- | |
| key : str | |
| Template key | |
| Returns | |
| ------- | |
| dict | |
| Dictionary of parameter names and values | |
| """ | |
| template = self.get_template(key) | |
| if not template: | |
| return {} | |
| return { | |
| "controlnet_conditioning_scale": template.controlnet_conditioning_scale, | |
| "feather_radius": template.feather_radius, | |
| "guidance_scale": template.guidance_scale, | |
| "num_inference_steps": template.num_inference_steps, | |
| "preferred_conditioning": template.preferred_conditioning | |
| } | |
| def build_prompt(self, key: str, content: str) -> str: | |
| """ | |
| Build complete prompt from template and user content. | |
| Parameters | |
| ---------- | |
| key : str | |
| Template key | |
| content : str | |
| User-provided content description | |
| Returns | |
| ------- | |
| str | |
| Formatted prompt with content inserted | |
| """ | |
| template = self.get_template(key) | |
| if not template: | |
| return content | |
| return template.prompt_template.format(content=content) | |
| def get_negative_prompt(self, key: str) -> str: | |
| """ | |
| Get negative prompt for a template. | |
| Parameters | |
| ---------- | |
| key : str | |
| Template key | |
| Returns | |
| ------- | |
| str | |
| Negative prompt string | |
| """ | |
| template = self.get_template(key) | |
| if not template: | |
| return "" | |
| return template.negative_prompt | |
| def get_usage_tips(self, key: str) -> List[str]: | |
| """ | |
| Get usage tips for a template. | |
| Parameters | |
| ---------- | |
| key : str | |
| Template key | |
| Returns | |
| ------- | |
| list | |
| List of tip strings | |
| """ | |
| template = self.get_template(key) | |
| if not template: | |
| return [] | |
| return template.usage_tips | |
| def build_gallery_html(self) -> str: | |
| """ | |
| Build HTML for template gallery display. | |
| Returns | |
| ------- | |
| str | |
| HTML string for Gradio display | |
| """ | |
| html_parts = ['<div class="inpainting-gallery">'] | |
| for category in self.CATEGORIES: | |
| templates = self.get_templates_by_category(category) | |
| if not templates: | |
| continue | |
| html_parts.append(f''' | |
| <div class="inpainting-category"> | |
| <h4 class="inpainting-category-title">{category}</h4> | |
| <div class="inpainting-grid"> | |
| ''') | |
| for template in sorted(templates, key=lambda t: t.name): | |
| html_parts.append(f''' | |
| <div class="inpainting-card" data-template="{template.key}"> | |
| <span class="inpainting-icon">{template.icon}</span> | |
| <span class="inpainting-name">{template.name}</span> | |
| <span class="inpainting-desc">{template.description[:50]}...</span> | |
| </div> | |
| ''') | |
| html_parts.append('</div></div>') | |
| html_parts.append('</div>') | |
| return ''.join(html_parts) | |