File size: 5,971 Bytes
71b378e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""
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