""" Character Portrait Tab for D'n'D Campaign Manager """ import gradio as gr from typing import Optional, Tuple from src.agents.character_agent import CharacterAgent from src.ui.components.dropdown_manager import DropdownManager class CharacterPortraitTab: """Generate Portrait tab for AI character portrait generation""" def __init__(self, character_agent: CharacterAgent, dropdown_manager: DropdownManager): self.character_agent = character_agent self.dropdown_manager = dropdown_manager def generate_portrait_ui( self, character_id: str, style: str = "fantasy art", quality: str = "standard", provider: str = "auto" ) -> Tuple[Optional[str], str]: """ Generate character portrait Returns: Tuple of (image_path, status_message) """ try: if not character_id.strip(): return None, "❌ Error: Please provide a character ID" # Load character character = self.character_agent.load_character(character_id) if not character: return None, f"❌ Character not found: {character_id}" # Generate portrait file_path, status = self.character_agent.generate_portrait( character=character, style=style, quality=quality, provider=provider ) return file_path, status except Exception as e: import traceback error_msg = f"❌ Error generating portrait:\n\n{str(e)}\n\n{traceback.format_exc()}" return None, error_msg def create(self) -> gr.Dropdown: """Create and return the Generate Portrait tab component""" with gr.Tab("Generate Portrait"): gr.Markdown(""" ### 🎨 AI Character Portrait Generator Generate stunning character portraits using DALL-E 3 or HuggingFace! """) with gr.Row(): with gr.Column(): portrait_refresh_btn = gr.Button("🔄 Refresh Character List", variant="secondary") portrait_character_dropdown = gr.Dropdown( choices=[], label="Select Character", info="Choose a character to generate portrait for (type to search)", allow_custom_value=False, interactive=True ) portrait_provider = gr.Radio( choices=["auto", "openai", "huggingface"], label="Image Provider", value="auto", info="Auto: Try OpenAI first, fallback to HuggingFace if needed" ) portrait_style = gr.Dropdown( choices=[ "fantasy art", "digital painting", "anime style", "oil painting", "watercolor", "comic book art", "concept art" ], label="Art Style", value="fantasy art", info="Choose the artistic style" ) portrait_quality = gr.Radio( choices=["standard", "hd"], label="Image Quality (OpenAI only)", value="standard", info="HD costs more tokens (OpenAI only)" ) generate_portrait_btn = gr.Button( "🎨 Generate Portrait", variant="primary", size="lg" ) portrait_status = gr.Textbox( label="Status", lines=4 ) with gr.Column(): portrait_output = gr.Image( label="Generated Portrait", type="filepath", height=512 ) gr.Markdown(""" **Providers:** - **OpenAI DALL-E 3**: High quality, costs $0.04/image (standard) or $0.08/image (HD) - **HuggingFace (Free!)**: Stable Diffusion XL, ~100 requests/day on free tier - **Auto**: Tries OpenAI first, automatically falls back to HuggingFace if billing issues Portraits are automatically saved to `data/portraits/` directory. **Tips:** - Use "auto" mode for seamless fallback - OpenAI HD quality produces better results but costs 2x - HuggingFace is free but may have a 30-60s warm-up time - Different styles work better for different races/classes """) # Refresh portrait character dropdown portrait_refresh_btn.click( fn=self.dropdown_manager.refresh_character_dropdown, inputs=[], outputs=[portrait_character_dropdown] ) # Generate portrait action - convert dropdown label to ID def generate_portrait_from_dropdown(label, style, quality, provider): char_id = self.dropdown_manager.get_character_id_from_label(label) return self.generate_portrait_ui(char_id, style, quality, provider) generate_portrait_btn.click( fn=generate_portrait_from_dropdown, inputs=[portrait_character_dropdown, portrait_style, portrait_quality, portrait_provider], outputs=[portrait_output, portrait_status] ) # Return the dropdown for auto-population return portrait_character_dropdown