File size: 47,905 Bytes
71b378e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0935d82
 
 
71b378e
 
 
 
 
 
 
 
 
 
 
0935d82
 
 
 
 
71b378e
0935d82
 
 
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
"""
Image Generation Utility - DALL-E 3 Integration for Character Portraits
"""

import os
import base64
from typing import Optional, Tuple, Literal
from pathlib import Path
import requests
from io import BytesIO
import openai
from openai import OpenAI

from src.config import config
from src.models.character import Character, DnDRace, DnDClass

ImageProvider = Literal["openai", "huggingface", "auto"]

# Skin tone options by race (3-5 options each)
RACE_SKIN_TONES = {
    DnDRace.HUMAN: [
        "Fair/Pale",
        "Light",
        "Medium/Olive",
        "Tan/Brown",
        "Deep/Dark"
    ],
    DnDRace.ELF: [
        "Pale/Moonlight",
        "Porcelain",
        "Honey/Golden",
        "Bronze",
        "Dark Copper"
    ],
    DnDRace.DWARF: [
        "Ruddy/Tan",
        "Bronze",
        "Deep Brown",
        "Stone Gray"
    ],
    DnDRace.HALFLING: [
        "Fair/Rosy",
        "Light Tan",
        "Medium Brown",
        "Dark Brown"
    ],
    DnDRace.DRAGONBORN: [
        "Brass (Golden)",
        "Copper (Reddish)",
        "Bronze (Brown)",
        "Silver (Silvery)",
        "Red (Crimson)",
        "Blue (Sapphire)",
        "Green (Emerald)",
        "Black (Obsidian)",
        "White (Pearl)"
    ],
    DnDRace.GNOME: [
        "Fair/Pink",
        "Tan/Sandy",
        "Light Brown",
        "Deep Brown"
    ],
    DnDRace.HALF_ELF: [
        "Fair",
        "Light Olive",
        "Medium Tan",
        "Brown",
        "Dark"
    ],
    DnDRace.HALF_ORC: [
        "Gray-Green",
        "Olive-Green",
        "Deep Green",
        "Gray-Brown"
    ],
    DnDRace.TIEFLING: [
        "Red",
        "Purple",
        "Blue-Gray",
        "Dark Gray",
        "Pink-Red"
    ],
    DnDRace.DROW: [
        "Dark Gray",
        "Purple-Black",
        "Blue-Black",
        "Obsidian Black"
    ]
}


class ImageGenerator:
    """Generate character portraits using DALL-E 3 or HuggingFace Inference"""

    def __init__(self):
        """Initialize image generator with available providers"""
        # Read API keys directly from environment
        self.openai_api_key = os.getenv("OPENAI_API_KEY")
        self.hf_api_key = os.getenv("HUGGINGFACE_API_KEY") or os.getenv("HF_TOKEN")

        self.output_dir = Path("data/portraits")
        self.output_dir.mkdir(parents=True, exist_ok=True)

        # HuggingFace model for fallback (Stable Diffusion XL)
        self.hf_model = "stabilityai/stable-diffusion-xl-base-1.0"

        # Check available providers
        self.has_openai = bool(self.openai_api_key)
        self.has_hf = bool(self.hf_api_key)

        if self.openai_api_key:
            print(f"βœ… OpenAI key found for image generation (key: {self.openai_api_key[:10]}...)")
        if self.hf_api_key:
            print(f"βœ… HuggingFace key found for image generation (key: {self.hf_api_key[:10]}...)")

        if not self.has_openai and not self.has_hf:
            print("⚠️  Warning: No image generation API keys found")
            print("   Image generation will not be available")
            print("   Add OPENAI_API_KEY or HUGGINGFACE_API_KEY to HF Spaces Secrets")

    def generate_character_portrait(
        self,
        character: Character,
        style: str = "fantasy art",
        quality: str = "standard",
        size: str = "1024x1024",
        provider: ImageProvider = "auto"
    ) -> Tuple[Optional[str], Optional[bytes]]:
        """
        Generate a character portrait using DALL-E 3 or HuggingFace

        Args:
            character: Character object with full details
            style: Art style (e.g., "fantasy art", "digital painting", "anime")
            quality: Image quality ("standard" or "hd") - OpenAI only
            size: Image size ("1024x1024", "1024x1792", "1792x1024")
            provider: "openai", "huggingface", or "auto" (tries OpenAI first, falls back to HF)

        Returns:
            Tuple of (file_path, image_bytes)
        """
        # Build detailed prompt from character
        prompt = self._build_character_prompt(character, style)

        # Determine which provider to use
        if provider == "auto":
            # Try OpenAI first, fallback to HuggingFace
            if self.has_openai:
                try:
                    image_bytes = self._generate_image_openai(prompt, quality, size)
                except Exception as openai_error:
                    print(f"OpenAI failed: {openai_error}")
                    if self.has_hf:
                        print("Falling back to HuggingFace Inference...")
                        image_bytes = self._generate_image_huggingface(prompt)
                    else:
                        raise  # Re-raise OpenAI error if no fallback
            elif self.has_hf:
                image_bytes = self._generate_image_huggingface(prompt)
            else:
                raise ValueError("No image generation provider available")

        elif provider == "openai":
            if not self.has_openai:
                raise ValueError("OpenAI API key not configured")
            image_bytes = self._generate_image_openai(prompt, quality, size)

        elif provider == "huggingface":
            if not self.has_hf:
                raise ValueError("HuggingFace API key not configured")
            image_bytes = self._generate_image_huggingface(prompt)

        else:
            raise ValueError(f"Unknown provider: {provider}")

        if image_bytes:
            # Save to file
            filename = f"{character.id}_portrait.png"
            file_path = self.output_dir / filename

            with open(file_path, 'wb') as f:
                f.write(image_bytes)

            return str(file_path), image_bytes

        return None, None

    def generate_custom_portrait(
        self,
        prompt: str,
        character_id: Optional[str] = None,
        quality: str = "standard",
        size: str = "1024x1024"
    ) -> Tuple[Optional[str], Optional[bytes]]:
        """
        Generate a portrait from custom prompt

        Args:
            prompt: Custom DALL-E prompt
            character_id: Optional character ID for filename
            quality: Image quality
            size: Image size

        Returns:
            Tuple of (file_path, image_bytes)
        """
        try:
            image_bytes = self._generate_image(prompt, quality, size)

            if image_bytes:
                # Save to file
                if character_id:
                    filename = f"{character_id}_custom_portrait.png"
                else:
                    import uuid
                    filename = f"portrait_{uuid.uuid4().hex[:8]}.png"

                file_path = self.output_dir / filename

                with open(file_path, 'wb') as f:
                    f.write(image_bytes)

                return str(file_path), image_bytes

            return None, None

        except Exception as e:
            print(f"Error generating custom image: {e}")
            return None, None

    def _build_character_prompt(self, character: Character, style: str) -> str:
        """
        Build detailed DALL-E prompt from character data

        Includes race, class, gender, alignment, skin tone, physical description
        """
        # Base description
        parts = []

        # Rich style prefix
        parts.append(f"Highly detailed {style} character portrait,")

        # Determine gender for trait customization
        character_gender = None
        if hasattr(character, 'gender') and character.gender and character.gender != "Not specified":
            character_gender = character.gender.lower()

        # Build VERY strong gender emphasis
        gender_prefix = ""
        gender_features = ""

        if character_gender == "female":
            gender_prefix = "BEAUTIFUL FEMININE FEMALE WOMAN"
            gender_features = "distinctly feminine facial features, soft feminine jawline, delicate bone structure, graceful feminine appearance"
        elif character_gender == "male":
            gender_prefix = "HANDSOME MASCULINE MALE MAN"
            gender_features = "distinctly masculine facial features, strong masculine jawline, defined bone structure, rugged masculine appearance"
        elif character_gender:
            gender_prefix = character_gender.upper()
            gender_features = "unique facial features"
        else:
            gender_prefix = "PERSON"
            gender_features = "expressive facial features"

        # Get skin tone with STRONG emphasis
        skin_emphasis = ""
        if hasattr(character, 'skin_tone') and character.skin_tone:
            # Make skin tone VERY prominent
            skin_emphasis = f"WITH {character.skin_tone.upper()} COLORED SKIN,"

        # Build the core character description
        parts.append(f"{gender_prefix} {character.race.value.upper()} {character.character_class.value.upper()} {skin_emphasis}")

        # Add gender-specific features and race traits (passing gender for customization)
        race_traits = self._get_race_traits(character.race, character_gender)
        parts.append(f"{gender_features}, {race_traits},")

        # Rich class-specific elements
        class_elements = self._get_class_elements(character.character_class)
        parts.append(f"{class_elements},")

        # Alignment influence
        alignment_mood = self._get_alignment_mood(character.alignment.value)
        if alignment_mood:
            parts.append(f"{alignment_mood},")

        # Rich artistic details with gender reinforcement
        parts.append("epic fantasy character portrait, professional illustration,")
        if character_gender == "female":
            parts.append("CLEARLY FEMALE CHARACTER, feminine beauty,")
        elif character_gender == "male":
            parts.append("CLEARLY MALE CHARACTER, masculine presence,")

        parts.append("highly detailed face with intricate features, expressive eyes,")
        parts.append("dramatic cinematic lighting with rim light, volumetric atmosphere,")
        parts.append("sharp focus on facial features and expression,")
        parts.append(f"masterpiece quality {style} artwork, trending on artstation, 8k resolution")

        prompt = " ".join(parts)

        # Ensure prompt isn't too long (DALL-E 3 has 4000 char limit)
        if len(prompt) > 3000:
            print(f"⚠️ Warning: Prompt truncated from {len(prompt)} to 3000 characters")
            prompt = prompt[:3000]

        return prompt

    def _get_race_traits(self, race: DnDRace, gender: Optional[str] = None) -> str:
        """Get ultra-detailed physical traits for race with gender-appropriate descriptions"""

        if gender == "female":
            traits = {
                DnDRace.HUMAN: "FEMININE WOMAN with expressive almond or round eyes showing depth and intelligence, natural human facial proportions with soft cheekbones, graceful jawline, delicate yet strong features, smooth skin with subtle weathering from adventure, diverse beauty with character lines from experience, warm approachable mouth with natural lips, well-defined brow expressing emotion, elegant neck, hair framing face naturally (flowing, braided, or practical), subtle beauty marks or freckles adding character, human versatility and adaptability shown in features, CLEARLY FEMALE facial structure",

                DnDRace.ELF: "FEMININE ELVEN WOMAN with long gracefully pointed ears swept back, sharp elegant angular features showing timeless beauty, almond-shaped luminous eyes with otherworldly gaze (colors like silver, gold, violet, or emerald), impossibly high sculpted cheekbones, delicate pointed chin, slender refined nose, soft bow-shaped lips, smooth porcelain-like skin with ageless quality, flowing silken hair (often silver, gold, auburn, or raven) cascading past shoulders or in elaborate braids, ethereal feminine grace, elegant arched eyebrows, slender graceful neck, mystical fey beauty radiating ancient wisdom, subtle glow to skin, CLEARLY FEMALE ELF with otherworldly elegance",

                DnDRace.DWARF: "FEMININE DWARVEN WOMAN with strong beautiful features, thick lustrous hair in elaborate braids adorned with metal rings and gemstones, fierce intelligent eyes showing determination and warmth, pronounced cheekbones with weathered sun-kissed complexion, broad but feminine jawline, strong nose with character, full lips often set in confident expression, weathered skin from forge work and mountain living showing texture and history, ornate beard jewelry or traditional face tattoos (cultural choice), powerful neck and shoulders showing strength, earthen beauty combining power and femininity, hair in rich colors (copper, bronze, black, auburn), skin with ruddy or bronze undertones, CLEARLY FEMALE DWARF with warrior-artisan beauty",

                DnDRace.HALFLING: "FEMININE HALFLING WOMAN with round friendly face showing youthful charm, large bright cheerful eyes (often hazel, brown, or green) sparkling with warmth and curiosity, naturally rosy cheeks with healthy glow, button nose, sweet gentle smile with dimples, soft rounded chin, curly or wavy tousled hair in warm colors (brown, auburn, golden, chestnut), smooth skin with youthful texture, delicate pointed ears (smaller than elves), laugh lines from joyful life, slightly plump cherubic features, cozy homely beauty radiating kindness, hair often adorned with flowers or ribbons, CLEARLY FEMALE HALFLING with welcoming maternal energy",

                DnDRace.DRAGONBORN: "FEMININE DRAGONBORN WOMAN with elegant draconic head featuring graceful reptilian features, sleek scaled skin covering face and neck with metallic or chromatic sheen matching dragon ancestry, fierce yet intelligent eyes with slit pupils glowing with inner fire, refined snout with noble profile, sharp teeth visible when speaking, pronounced facial ridges and horns sweeping back elegantly, scales with iridescent quality catching light beautifully, no hair but decorative horn jewelry or scale paint, expressive eye ridges conveying emotion, strong graceful neck with visible scale texture, regal bearing combining dragon majesty with feminine grace, face scales in beautiful patterns, CLEARLY FEMALE DRAGONBORN with powerful elegant beauty",

                DnDRace.GNOME: "FEMININE GNOME WOMAN with large expressive eyes full of endless curiosity and mischief (often in unusual colors like amber, turquoise, or violet), delicate upturned pointed nose, wild energetic hair in vibrant colors (pink, green, blue, purple, or natural tones) styled chaotically or with practical clips, mischievous playful grin showing cleverness, soft cheeks with youthful appearance, pointed chin, arched expressive eyebrows always moving with thought, smooth skin often smudged with grease or colored powder, tiny adorable ears, laugh lines around eyes from constant amusement, face showing brilliant intellect and creative energy, often wearing magnifying goggles or spectacles pushed up, CLEARLY FEMALE GNOME with inventive spirited beauty",

                DnDRace.HALF_ELF: "FEMININE HALF-ELF WOMAN with subtly pointed ears (shorter than full elves), beautiful blend of human warmth and elven ethereal grace, refined yet approachable facial features, high but not extreme cheekbones, expressive eyes with hint of otherworldly luminescence (various colors with subtle glow), elegant straight nose between human and elven, soft lips with gentle smile, smooth skin with light natural glow, flowing hair in diverse colors showing mixed heritage, graceful neck and shoulders, features combining best of both ancestries, slightly elongated facial proportions, eyebrows with elegant arch, skin texture between human and elven smoothness, versatile beauty adaptable to any culture, CLEARLY FEMALE HALF-ELF with hybrid enchanting beauty",

                DnDRace.HALF_ORC: "BEAUTIFUL FEMININE HALF-ORC WOMAN, CLEARLY FEMALE WARRIOR WOMAN, delicate small lower canines (petite feminine tusks barely visible), SOFT FEMININE FACIAL FEATURES with graceful cheekbones, FEMININE JAWLINE strong but elegant, fierce intelligent eyes showing both strength and compassion (colors like amber, green, brown), slightly prominent brow ridge adding character not masculinity, FEMININE NOSE with strong bridge, full lips with natural beauty, SMOOTH SKIN with greenish or grayish undertones (matching selected skin tone), LONG FLOWING HAIR in warrior braids or wild mane (black, brown, auburn, or dark green), graceful neck showing both power and femininity, war paint or tribal markings enhancing feminine beauty, ATHLETIC FEMININE PHYSIQUE radiating strength and grace, DISTINCTLY FEMALE ORC-BLOODED BEAUTY, warrior goddess appearance combining fierceness with unmistakable femininity, scarring or battle marks adding to beauty not diminishing it, WOMAN HALF-ORC FEMALE",

                DnDRace.TIEFLING: "FEMININE TIEFLING WOMAN with elegant curved horns sweeping back from forehead or temples (ram-like, gazelle-like, or demonic style) adorned with jewelry, long graceful pointed tail often visible, exotic glowing eyes without pupils (colors like molten gold, burning silver, fiery red, violet, or emerald), sharp elegant cheekbones, delicate pointed chin, soft lips with hint of sharp canine teeth visible when smiling, EXOTIC SKIN in vibrant colors (deep crimson, wine red, purple, blue-gray, pink-red, or obsidian) matching heritage, high arched eyebrows, hair in unusual colors (black, white, red, purple, blue) flowing or styled dramatically, smooth skin with slight infernal texture or patterns, elegant pointed ears or human-like ears, graceful neck, facial features combining demonic heritage with seductive beauty, CLEARLY FEMALE TIEFLING with dangerous alluring charm, infernal markings or tattoos enhancing features, OTHERWORLDLY FEMININE BEAUTY with edge of darkness",

                DnDRace.DROW: "FEMININE DROW ELF WOMAN with striking white or silver luminous hair (flowing, braided in elaborate warrior styles, or styled in matriarchal fashion), MIDNIGHT OBSIDIAN-BLACK SKIN or deep purple-black skin with smooth flawless texture, long gracefully pointed elven ears, GLOWING EYES in crimson red or violet purple with otherworldly luminescence piercing through darkness, razor-sharp elegant cheekbones, delicate pointed chin, straight refined nose, soft dark lips (deep purple or black), ageless ethereal features combining elven beauty with dark elegance, high arched eyebrows, slender graceful neck, hair-skin contrast creating striking visual, skin with subtle undertone (blue-black, purple-black, or pure obsidian), CLEARLY FEMALE DROW with dangerous seductive beauty, spider web jewelry or dark metal accessories, face radiating lethal grace and underground nobility, DARK ELF WOMAN with predatory elegance",
            }
        elif gender == "male":
            traits = {
                DnDRace.HUMAN: "MASCULINE MAN with strong expressive eyes showing determination and wisdom, natural human facial proportions with defined cheekbones, STRONG MASCULINE JAWLINE with character, broader features showing strength, skin with weathering from adventure and sun exposure, diverse rugged handsomeness with battle scars or life experience, firm mouth with natural lips, PROMINENT BROW showing resolve, POWERFUL NECK and shoulders, facial hair options (beard, stubble, or clean-shaven), hair in practical warrior styles or flowing heroic length, skin texture showing outdoor life, human adaptability in features, CLEARLY MALE facial structure with masculine presence",

                DnDRace.ELF: "MASCULINE ELVEN MAN with long gracefully pointed ears swept back, sharp noble angular features showing ancient bloodline, almond-shaped piercing eyes with otherworldly gaze (colors like silver, gold, violet, or emerald), high prominent cheekbones, STRONG ELEGANT JAWLINE, refined straight nose, firm lips, smooth ageless skin with ethereal quality, flowing silken hair (often silver, gold, auburn, or raven) worn long or in warrior braids, CLEARLY MALE ELF with masculine grace, high arched brows, LEAN POWERFUL NECK, ethereal masculine beauty combining strength with timeless elegance, skin with subtle glow, facial features showing both warrior prowess and ancient wisdom, no facial hair typical of elves, aristocratic bearing",

                DnDRace.DWARF: "MASCULINE DWARVEN MAN with MAGNIFICENT THICK BEARD in elaborate braids adorned with metal rings and gemstones (beard is point of pride), thick lustrous hair in warrior braids or bound styles, fierce determined eyes showing strength and honor, VERY PRONOUNCED STRONG CHEEKBONES, BROAD POWERFUL JAWLINE partially hidden by beard, STRONG NOSE with character, weathered ruddy complexion from forge work and mountain living showing texture and history, BROAD POWERFUL NECK and shoulders radiating strength, skin in ruddy or bronze tones, traditional clan tattoos or ritual scars, hair in rich colors (copper, bronze, black, auburn, iron-gray), CLEARLY MALE DWARF with warrior-craftsman presence, rugged masculine beauty, features carved by mountain stone and forge fire",

                DnDRace.HALFLING: "MASCULINE HALFLING MAN with round friendly face showing cheerful courage, large bright eyes (often hazel, brown, or green) sparkling with warmth and mischief, naturally rosy cheeks with healthy glow, button nose, wide genuine smile with laugh lines, STRONG ROUNDED CHIN, curly or wavy tousled hair in warm colors (brown, auburn, golden, chestnut), youthful skin texture showing outdoor life, delicate pointed ears (smaller than elves), crow's feet from constant smiling, jovial features radiating hospitality, possible sideburns or chin beard, hair slightly wild from adventure, CLEARLY MALE HALFLING with welcoming paternal energy and hidden bravery",

                DnDRace.DRAGONBORN: "MASCULINE DRAGONBORN MAN with powerful draconic head featuring STRONG reptilian features, THICK scaled skin covering face and neck with metallic or chromatic sheen matching dragon ancestry, fierce commanding eyes with slit pupils burning with inner fire, PRONOUNCED STRONG SNOUT with noble warrior profile, sharp dangerous teeth visible when speaking, PROMINENT FACIAL RIDGES and LARGE HORNS sweeping back powerfully, scales with armored quality showing battle-worn texture, no hair but war trophies or horn decorations, STRONG BROW RIDGE conveying intensity, POWERFUL THICK NECK with pronounced scale texture, commanding presence radiating dragon majesty and masculine power, face scales in intimidating patterns, CLEARLY MALE DRAGONBORN with fierce noble strength",

                DnDRace.GNOME: "MASCULINE GNOME MAN with large expressive eyes full of endless curiosity and clever mischief (often in unusual colors like amber, turquoise, or violet), pointed upturned nose, wild energetic hair or beard in vibrant colors (pink, green, blue, purple, or natural tones) styled chaotically, mischievous clever grin showing wit and intelligence, STRONG POINTED CHIN, bushy expressive eyebrows always moving with thought, weathered skin often smudged with grease or colored powder from experiments, tiny ears, laugh lines and wrinkles from constant invention, face showing brilliant intellect and manic creativity, often wearing magnifying goggles or spectacles, possible elaborate mustache or beard, CLEARLY MALE GNOME with inventive eccentric charm",

                DnDRace.HALF_ELF: "MASCULINE HALF-ELF MAN with subtly pointed ears (shorter than full elves), handsome blend of human strength and elven grace, refined yet approachable facial features, STRONG DEFINED CHEEKBONES, FIRM MASCULINE JAWLINE showing mixed heritage, piercing eyes with hint of otherworldly luminescence (various colors with subtle glow), straight nose between human and elven proportions, firm lips with confident expression, skin with light natural glow, hair in diverse colors showing mixed heritage (worn in various styles), STRONG NECK and shoulders, features combining human vitality with elven elegance, slightly elongated facial proportions, strong brow, skin texture between human ruggedness and elven smoothness, adaptable handsome features, CLEARLY MALE HALF-ELF with hybrid noble bearing",

                DnDRace.HALF_ORC: "MASCULINE HALF-ORC MAN, CLEARLY MALE WARRIOR, LARGE PROMINENT LOWER CANINES (impressive tusks) protruding from mouth, VERY STRONG PROMINENT BROW RIDGE, BROAD POWERFUL MASCULINE FEATURES, SQUARE MASSIVE JAWLINE showing orcish strength, fierce intense eyes showing battle fury and determination (colors like amber, red, green, brown), WIDE FLAT NOSE with strong bridge, scarred weathered skin showing countless battles, THICK SKIN with greenish or grayish undertones (matching selected skin tone), hair in warrior styles (topknot, war braids, or shaved patterns) in dark colors (black, brown, dark green), MASSIVE POWERFUL NECK and shoulders radiating raw strength, war paint or tribal scars marking face, battle-scarred features adding to intimidating presence, CLEARLY MALE HALF-ORC with fearsome warrior appearance, rugged brutal handsomeness, MAN HALF-ORC MALE",

                DnDRace.TIEFLING: "MASCULINE TIEFLING MAN with prominent curved horns sweeping from forehead or temples (ram-like, demonic, or dragon-like style) possibly adorned with rings, long pointed tail often visible, exotic glowing eyes without pupils (colors like molten gold, burning silver, hellfire red, violet, or emerald), STRONG SHARP CHEEKBONES, DEFINED MASCULINE JAWLINE, firm lips with visible sharp canine teeth when grinning, EXOTIC SKIN in vibrant infernal colors (deep crimson, wine red, purple, blue-gray, pink-red, or obsidian) matching heritage, STRONG BROW with commanding presence, hair in unusual colors (black, white, red, purple, blue) worn in dramatic styles, skin with slight infernal texture or glowing patterns, pointed ears or human-like ears, POWERFUL NECK, facial features combining demonic heritage with dark handsomeness, CLEARLY MALE TIEFLING with dangerous charismatic presence, infernal markings or ritual scars, OTHERWORLDLY MASCULINE BEAUTY with menacing edge",

                DnDRace.DROW: "MASCULINE DROW ELF MAN with striking white or silver luminous hair (long warrior style, braided in military fashion, or swept back dramatically), MIDNIGHT OBSIDIAN-BLACK SKIN or deep purple-black skin with smooth flawless texture, long gracefully pointed elven ears, GLOWING EYES in crimson red or violet purple with otherworldly intensity piercing darkness, RAZOR-SHARP CHEEKBONES, STRONG MASCULINE JAWLINE, straight refined nose, firm dark lips (deep purple or black), ageless features combining elven beauty with dark masculine power, STRONG ARCHED BROWS, LEAN POWERFUL NECK, hair-skin contrast creating striking warrior appearance, skin with subtle undertone (blue-black, purple-black, or pure obsidian), CLEARLY MALE DROW with dangerous lethal handsomeness, spider web scars or dark metal piercings, face radiating predatory grace and underground warrior nobility, DARK ELF MAN with deadly elegant presence, no facial hair typical of elves",
            }
        else:
            # Androgynous/non-binary descriptions
            traits = {
                DnDRace.HUMAN: "ANDROGYNOUS HUMAN with expressive eyes showing depth and soulful intelligence, balanced facial proportions combining strength and grace, elegantly defined cheekbones neither overly sharp nor soft, refined jawline with harmonious structure, captivating features transcending gender norms, smooth skin with subtle weathering from adventure showing life experience, balanced mouth with expressive lips, thoughtfully arched brow, graceful neck, hair styled in ways celebrating fluidity (undercut, flowing, asymmetric, or artistic), natural beauty radiating confidence and self-expression, features showing human diversity and non-conformity, face with androgynous allure and mysterious charm",
                DnDRace.ELF: "ANDROGYNOUS ELF with long gracefully pointed ears swept back elegantly, sharp perfectly balanced angular features transcending gender, almond-shaped luminous eyes with captivating otherworldly gaze (colors like silver, gold, violet, or emerald), impossibly high sculpted cheekbones creating divine symmetry, refined jawline neither distinctly masculine nor feminine, delicate straight nose, soft balanced lips, smooth porcelain-like ageless skin with ethereal quality, flowing silken hair in mystical colors (silver, gold, auburn, raven, or pastel tones) styled in fluid artistic ways, graceful slender neck, features radiating fey beauty beyond mortal gender concepts, face showing ancient wisdom and timeless allure, subtle luminescence to skin, androgynous elven perfection with magnetic presence",
                DnDRace.DWARF: "ANDROGYNOUS DWARF with thick lustrous hair in artistic braids adorned with unique gemstones and personal metalwork, fierce intelligent eyes showing determination and creative spirit, pronounced balanced cheekbones, weathered complexion from forge work and mountain living with beautiful texture, strong yet refined jawline, noble nose with character, expressive lips, neck and shoulders showing both strength and grace, skin in warm tones (ruddy, bronze, or deep brown), traditional face tattoos or unique piercings celebrating identity, hair in unconventional colors or natural tones, features combining dwarven strength with fluid beauty, face showing artisan soul and warrior heart, androgynous dwarven presence breaking traditional molds",
                DnDRace.HALFLING: "ANDROGYNOUS HALFLING with round friendly face radiating warmth and acceptance, large bright expressive eyes (often hazel, brown, or green) sparkling with joy and wisdom, naturally rosy cheeks with healthy glow, button nose, gentle welcoming smile with dimples, soft balanced chin, curly or wavy hair in warm or creative colors styled in personal expression, smooth youthful skin, delicate pointed ears (smaller than elves), features combining childlike wonder with ageless understanding, face showing both nurturing spirit and adventurous soul, hair adorned with meaningful tokens or natural elements, androgynous halfling beauty radiating community and individuality",
                DnDRace.DRAGONBORN: "ANDROGYNOUS DRAGONBORN with elegant draconic head featuring balanced reptilian features, sleek scaled skin covering face and neck with beautiful metallic or chromatic sheen, fierce intelligent eyes with slit pupils showing wisdom and power, refined snout with noble profile, sharp teeth visible when speaking, facial ridges and horns swept back in artistic arrangement, scales with mesmerizing iridescent patterns, no hair but decorative horn jewelry or ritual scale painting, expressive eye ridges conveying deep emotion, graceful powerful neck with scale texture, presence combining dragon majesty with fluid grace, face scales in unique beautiful patterns, androgynous dragonborn radiating ancient power beyond gender",
                DnDRace.GNOME: "ANDROGYNOUS GNOME with large expressive eyes full of boundless curiosity and creative wonder (often in unique colors like amber, turquoise, or violet), delicate pointed nose, wild artistic hair in vibrant unconventional colors (rainbow, pastel gradients, or natural tones) styled in expressive ways, clever mischievous smile showing brilliant wit, balanced chin, constantly moving expressive eyebrows, skin with inventor's marks (oil smudges, colorful stains, creative tattoos), small ears with multiple piercings or decorations, face radiating innovation and free spirit, often wearing unique goggles or handcrafted accessories, features showing genius that defies categorization, androgynous gnome charm combining intellect with whimsy",
                DnDRace.HALF_ELF: "ANDROGYNOUS HALF-ELF with subtly pointed ears (shorter than full elves), captivating blend of human warmth and elven ethereal beauty, refined yet approachable features transcending binary presentation, balanced elegant cheekbones, graceful jawline neither distinctly masculine nor feminine, mesmerizing eyes with hint of otherworldly luminescence (various colors with gentle glow), straight nose combining human and elven characteristics, expressive lips with enigmatic smile, smooth skin with subtle natural glow, flowing hair in diverse colors showing mixed heritage (styled in personal artistic expression), elegant neck, features perfectly balancing both ancestries, slightly elongated proportions with harmonious beauty, skin texture between human character and elven smoothness, versatile beauty that belongs to all worlds and none, androgynous half-elf with magnetic hybrid allure",
                DnDRace.HALF_ORC: "ANDROGYNOUS HALF-ORC with moderate lower canines (tusks of medium prominence), powerful yet graceful build transcending traditional gender, balanced prominent brow ridge adding character without overpowering, strong features combining orcish heritage with fluid beauty, fierce intelligent eyes showing both warrior spirit and deep wisdom (colors like amber, green, hazel), balanced nose with strong bridge, expressive lips, SKIN with greenish or grayish undertones (matching selected skin tone) with beautiful texture, hair in warrior styles (braids, shaved patterns, or flowing) in unconventional colors, graceful powerful neck, war paint or artistic tattoos celebrating identity and clan, features showing both battle prowess and spiritual depth, scars telling stories of survival and growth, androgynous half-orc beauty breaking warrior stereotypes, presence radiating strength without conforming to binary expectations, face combining orcish power with transcendent grace",
                DnDRace.TIEFLING: "ANDROGYNOUS TIEFLING with elegant artistic horns sweeping from forehead (ram-like, gazelle-like, or unique style) adorned with meaningful jewelry, long expressive pointed tail often visible, exotic glowing eyes without pupils (colors like molten gold, burning silver, mystic violet, or ethereal teal), balanced sharp cheekbones creating striking symmetry, refined jawline neither distinctly masculine nor feminine, expressive lips with hint of sharp canine teeth, EXOTIC SKIN in vibrant mystical colors (deep crimson, royal purple, midnight blue, pink-red, or silver-gray) matching infernal heritage, elegantly arched brows, hair in supernatural colors (starlight white, shadow black, flame red, void purple, or gradient tones) styled in personal artistic expression, smooth skin with subtle infernal patterns or glowing sigils, pointed ears or human-like ears, graceful neck, features combining demonic heritage with transcendent beauty beyond mortal categories, androgynous tiefling radiating otherworldly allure and dangerous mystique, infernal markings celebrating unique identity, face showing both darkness and light in perfect balance",
                DnDRace.DROW: "ANDROGYNOUS DROW ELF with striking luminous white or silver hair (styled in unique artistic ways, warrior braids, or flowing freely), MIDNIGHT OBSIDIAN-BLACK SKIN or deep purple-black skin with flawless smooth texture, long gracefully pointed elven ears, MESMERIZING GLOWING EYES in crimson red or violet purple with captivating otherworldly luminescence, razor-sharp perfectly balanced cheekbones, refined jawline transcending gender, straight elegant nose, expressive dark lips (deep purple or black), ageless features combining elven beauty with dark androgynous allure, gracefully arched brows, elegant neck, hair-skin contrast creating stunning visual impact, skin with subtle undertone (blue-black, purple-black, or pure obsidian), androgynous drow radiating lethal grace and underground nobility beyond binary concepts, spider web jewelry or unique dark metal adornments celebrating identity, face showing predatory elegance and wisdom of the Underdark, dark elf presence with transcendent dangerous beauty, features commanding respect through power not gender",
            }

        return traits.get(race, "distinctive fantasy features")

    def _get_class_elements(self, character_class: DnDClass) -> str:
        """Get rich equipment/clothing descriptions for class"""
        elements = {
            DnDClass.FIGHTER: "wearing ornate heavy plate armor with battle scars, wielding a masterwork longsword, shield with heraldic emblem, warrior's stance, battle-ready posture",

            DnDClass.WIZARD: "wearing flowing arcane robes covered in mystical runes and symbols, holding an ancient gnarled staff with glowing crystal, spellbook at belt, magical energy crackling around hands, scholarly yet powerful presence",

            DnDClass.ROGUE: "wearing form-fitting leather armor in dark colors, multiple daggers and lockpicks visible, hooded cloak, agile stance, tools of the trade on belt, mysterious air, shadows clinging to figure",

            DnDClass.CLERIC: "wearing holy vestments and blessed armor adorned with sacred symbols, divine light emanating softly, holy symbol prominently displayed, mace or shield with deity's iconography, righteous bearing",

            DnDClass.RANGER: "wearing weathered leather armor decorated with natural elements, longbow and quiver of arrows, hunting knives, camouflage cloak, survival gear, at one with nature, tracker's keen gaze",

            DnDClass.PALADIN: "wearing radiant plate armor gleaming with holy light, sacred sword with blessed runes, shield bearing oath symbol, divine aura surrounding them, righteous and noble bearing, armor reflecting inner conviction",

            DnDClass.BARD: "wearing flamboyant colorful clothes with artistic flair, ornate musical instrument (lute or flute), decorative rapier, charismatic pose, theatrical presence, jewelry and fine accessories",

            DnDClass.BARBARIAN: "wearing minimal tribal armor showing powerful muscles, massive greataxe or battle weapon, war paint or tribal tattoos, furs and bone decorations, primal fierce energy, untamed strength visible",

            DnDClass.DRUID: "wearing natural robes made from leaves and vines, wooden staff carved with nature symbols, animal companion nearby or nature motifs, flowers or moss in hair, earth-toned garments, wild natural aesthetic",

            DnDClass.MONK: "wearing simple martial arts robes or wraps, disciplined stance showing combat training, meditation beads or prayer rope, barefoot or simple sandals, focused ki energy visible, balanced pose, inner peace and outer strength",

            DnDClass.SORCERER: "wearing elegant robes with innate magical patterns, raw arcane energy visibly crackling and swirling around body, no spellbook (power from within), draconic or wild magic aesthetic, chaotic magical aura",

            DnDClass.WARLOCK: "wearing dark mysterious robes with eldritch patterns, otherworldly patron's influence visible in design, arcane focus or pact weapon, eerie magical glow, forbidden knowledge in eyes, shadows and mysterious energies swirling",
        }
        return elements.get(character_class, "adventurer's gear and equipment, ready for questing")

    def _get_alignment_mood(self, alignment: str) -> str:
        """Get rich mood/expression and visual atmosphere based on alignment"""
        moods = {
            "Lawful Good": "noble righteous expression radiating honor and justice, heroic stance, warm golden lighting highlighting features, aura of divine protection, inspiring presence, defender of the weak demeanor, lawful symbols glowing softly",

            "Neutral Good": "kind compassionate expression with gentle warm smile, caring eyes full of empathy, soft natural lighting, helping hand gesture, approachable and trustworthy aura, peaceful yet determined energy",

            "Chaotic Good": "rebellious freedom-fighter expression with fierce determination, defiant heroic gaze, dynamic wild hair, passionate energy, breaking chains imagery, fighting for justice with unconventional methods, vibrant chaotic-good energy swirling",

            "Lawful Neutral": "disciplined stoic expression showing unwavering resolve, stern neutral features, cold calculated gaze, orderly balanced lighting, perfect symmetry, following code without emotion, judge-like presence, scales of balance nearby",

            "True Neutral": "perfectly balanced contemplative expression, serene eyes seeing all sides, neutral gray and earth-tone lighting, harmonious natural pose, druid-like connection to balance, neither light nor dark dominating, peaceful equilibrium",

            "Chaotic Neutral": "unpredictable wild expression with mischievous glint, chaotic energy in eyes, asymmetrical dynamic pose, swirling random elements, free spirit aesthetic, untamed unpredictable aura, living for freedom and self-interest",

            "Lawful Evil": "coldly calculating tyrant expression, cruel methodical eyes, harsh angular shadows, oppressive dark lighting from above, commanding authoritative pose, iron-fisted control visible, merciless lawful darkness, chains and order motifs",

            "Neutral Evil": "cruel cunning expression with sinister smirk, selfish malevolent eyes, shadowy ominous lighting, betrayer's calculating gaze, pure self-interest and cruelty, dark subtle shadows, dangerous predatory presence",

            "Chaotic Evil": "menacing destructive expression with maniacal energy, eyes burning with malevolent chaos, violent chaotic shadows swirling, destructive aura crackling, reveling in cruelty and disorder, demonic wild energy, nightmare-inducing presence, burning destruction in background",
        }
        return moods.get(alignment, "determined focused expression")

    def _generate_image_openai(
        self,
        prompt: str,
        quality: str = "standard",
        size: str = "1024x1024"
    ) -> Optional[bytes]:
        """
        Call DALL-E 3 API to generate image

        Args:
            prompt: Image generation prompt
            quality: "standard" or "hd"
            size: Image dimensions

        Returns:
            Image bytes or None
        """
        client = OpenAI(api_key=self.openai_api_key)

        try:
            response = client.images.generate(
                model="dall-e-3",
                prompt=prompt,
                size=size,
                quality=quality,
                n=1,
            )

            # Get image URL
            image_url = response.data[0].url

            # Download image
            image_response = requests.get(image_url)
            if image_response.status_code == 200:
                return image_response.content

            return None

        except openai.BadRequestError as e:
            error_str = str(e)
            print(f"DALL-E API BadRequest error: {error_str}")

            # Check for billing issues
            if "billing_hard_limit" in error_str or "billing hard limit" in error_str.lower():
                raise ValueError(
                    "πŸ’³ Billing limit reached: Your OpenAI account has reached its billing limit.\n\n"
                    "To fix this:\n"
                    "1. Go to https://platform.openai.com/account/billing\n"
                    "2. Add credits or update your payment method\n"
                    "3. Check/increase your usage limits\n\n"
                    "Note: DALL-E 3 costs $0.040 per standard image or $0.080 per HD image."
                )
            elif "content_policy" in error_str.lower() or "safety" in error_str.lower():
                raise ValueError(
                    "Content policy violation: The generated prompt was flagged by OpenAI's safety system. "
                    "Try using a different art style or character description."
                )
            else:
                raise ValueError(f"DALL-E API Error: {error_str}")

        except openai.AuthenticationError as e:
            print(f"DALL-E Authentication error: {e}")
            raise ValueError(
                "πŸ”‘ Authentication error: Your OpenAI API key is invalid or expired.\n"
                "Please check that OPENAI_API_KEY in your .env file is correct."
            )

        except openai.RateLimitError as e:
            print(f"DALL-E Rate limit error: {e}")
            raise ValueError(
                "⏰ Rate limit exceeded: Too many requests to OpenAI API.\n"
                "Please wait a moment and try again."
            )

        except Exception as e:
            error_str = str(e)
            print(f"DALL-E API error: {error_str}")

            # Catch-all with helpful hint
            if "billing" in error_str.lower() or "quota" in error_str.lower():
                raise ValueError(
                    "πŸ’³ Billing/Quota error: " + error_str + "\n\n"
                    "Check your OpenAI account at https://platform.openai.com/account/billing"
                )
            else:
                raise ValueError(f"DALL-E API Error: {error_str}")

    def _generate_image_huggingface(self, prompt: str) -> Optional[bytes]:
        """
        Call HuggingFace Inference API to generate image using Stable Diffusion

        Args:
            prompt: Image generation prompt

        Returns:
            Image bytes or None
        """
        from huggingface_hub import InferenceClient

        client = InferenceClient(token=self.hf_api_key)

        try:
            # Generate image using Stable Diffusion XL
            image = client.text_to_image(
                prompt=prompt,
                model=self.hf_model
            )

            # Convert PIL Image to bytes
            from PIL import Image
            import io

            if isinstance(image, Image.Image):
                img_byte_arr = io.BytesIO()
                image.save(img_byte_arr, format='PNG')
                img_byte_arr.seek(0)
                return img_byte_arr.read()

            return None

        except Exception as e:
            error_str = str(e)
            print(f"HuggingFace API error: {error_str}")

            # Provide helpful error messages
            if "401" in error_str or "authentication" in error_str.lower():
                raise ValueError(
                    "πŸ”‘ HuggingFace Authentication error: Your HF API key is invalid.\n"
                    "Get a free key at https://huggingface.co/settings/tokens"
                )
            elif "503" in error_str or "model" in error_str.lower() and "loading" in error_str.lower():
                raise ValueError(
                    "⏰ Model loading: The HuggingFace model is starting up.\n"
                    "Please wait 30-60 seconds and try again."
                )
            elif "rate" in error_str.lower() or "quota" in error_str.lower():
                raise ValueError(
                    "⏰ Rate limit: HuggingFace API rate limit reached.\n"
                    "Free tier: ~100 requests/day. Upgrade at https://huggingface.co/pricing"
                )
            else:
                raise ValueError(f"HuggingFace API Error: {error_str}")

    def get_portrait_path(self, character_id: str) -> Optional[str]:
        """Get saved portrait path for character"""
        filename = f"{character_id}_portrait.png"
        file_path = self.output_dir / filename

        if file_path.exists():
            return str(file_path)

        # Check for custom portrait
        filename = f"{character_id}_custom_portrait.png"
        file_path = self.output_dir / filename

        if file_path.exists():
            return str(file_path)

        return None


# Global instance
_image_generator: Optional[ImageGenerator] = None


def get_image_generator() -> ImageGenerator:
    """Get or create global image generator instance"""
    global _image_generator
    if _image_generator is None:
        _image_generator = ImageGenerator()
    return _image_generator