import random import numpy as np from PIL import Image, ImageDraw, ImageFont from typing import Dict, Any, List, Tuple, Optional import json from datetime import datetime import trimesh class FallbackManager: """Manages fallback strategies when AI models fail""" def __init__(self): # Predefined fallback templates self.monster_templates = { 'fire': { 'names': ['Pyromon', 'Blazefang', 'Emberclaw', 'Infernox'], 'colors': ['#ff4444', '#ff6600', '#ffaa00'], 'traits': ['aggressive', 'brave', 'fierce'], 'abilities': ['Flame Burst', 'Heat Wave', 'Fire Shield'] }, 'water': { 'names': ['Aquamon', 'Tidalfin', 'Wavecrest', 'Hydropex'], 'colors': ['#4444ff', '#00aaff', '#00ffff'], 'traits': ['calm', 'wise', 'gentle'], 'abilities': ['Water Jet', 'Bubble Shield', 'Healing Wave'] }, 'earth': { 'names': ['Terramon', 'Boulderback', 'Stoneguard', 'Geomancer'], 'colors': ['#885533', '#aa7744', '#665544'], 'traits': ['sturdy', 'patient', 'protective'], 'abilities': ['Rock Throw', 'Earth Shield', 'Quake'] }, 'electric': { 'names': ['Voltmon', 'Sparkfang', 'Thunderclaw', 'Electrix'], 'colors': ['#ffff00', '#ffcc00', '#ffffaa'], 'traits': ['energetic', 'quick', 'playful'], 'abilities': ['Thunder Shock', 'Static Field', 'Lightning Speed'] }, 'nature': { 'names': ['Floramon', 'Leafguard', 'Vineclaw', 'Botanix'], 'colors': ['#44ff44', '#00aa00', '#88ff88'], 'traits': ['peaceful', 'nurturing', 'wise'], 'abilities': ['Vine Whip', 'Healing Bloom', 'Nature Shield'] }, 'neutral': { 'names': ['Digipet', 'Cybermon', 'Neobit', 'Alphacode'], 'colors': ['#888888', '#aaaaaa', '#cccccc'], 'traits': ['balanced', 'adaptable', 'loyal'], 'abilities': ['Tackle', 'Defense Boost', 'Quick Attack'] } } # Emoji dialogue patterns self.dialogue_patterns = { 'happy': ['😊', '😄', '🎉', '💖', '✨'], 'hungry': ['🍖', '🍗', '🥘', '😋', '🤤'], 'tired': ['😴', '💤', '🥱', '😪', '🛌'], 'excited': ['🤩', '🎊', '🔥', '⚡', '🌟'], 'sad': ['😢', '😔', '💔', '😞', '☔'], 'angry': ['😤', '💢', '😠', '🔥', '⚔️'] } def handle_stt_failure(self, text_input: Optional[str]) -> str: """Fallback for speech-to-text failure""" if text_input: return text_input # Generate random description templates = [ "Create a friendly digital monster companion", "Design a unique creature with special powers", "Make a loyal monster friend", "Generate a mysterious digital being", "Create an evolved cyber creature" ] return random.choice(templates) def handle_text_gen_failure(self, description: str) -> Tuple[Dict[str, Any], str]: """Fallback for text generation failure""" # Analyze description for keywords element = self._detect_element(description) template = self.monster_templates[element] # Generate traits traits = { 'name': random.choice(template['names']) + str(random.randint(1, 99)), 'species': f"{element.capitalize()} Type Monster", 'element': element, 'personality': random.choice(template['traits']), 'color_scheme': f"Primary: {template['colors'][0]}, Secondary: {template['colors'][1]}", 'abilities': random.sample(template['abilities'], 2), 'description': description } # Generate dialogue mood = 'happy' if 'friendly' in description.lower() else 'excited' dialogue = self._generate_emoji_dialogue(mood) return traits, dialogue def handle_image_gen_failure(self, description: str) -> Image.Image: """Fallback for image generation failure""" # Create procedural monster image width, height = 512, 512 image = Image.new('RGBA', (width, height), (0, 0, 0, 0)) draw = ImageDraw.Draw(image) # Detect element for color scheme element = self._detect_element(description) colors = self.monster_templates[element]['colors'] primary_color = colors[0] secondary_color = colors[1] if len(colors) > 1 else colors[0] # Draw monster shape self._draw_procedural_monster(draw, width, height, primary_color, secondary_color) return image def handle_3d_gen_failure(self, image: Optional[Image.Image]) -> trimesh.Trimesh: """Fallback for 3D generation failure""" # Create simple 3D primitive shapes = [ trimesh.creation.icosphere(subdivisions=2, radius=1.0), trimesh.creation.box(extents=[1.5, 1.0, 1.0]), trimesh.creation.cylinder(radius=0.8, height=1.5), trimesh.creation.cone(radius=0.8, height=1.5) ] base_shape = random.choice(shapes) # Add some deformation for variety noise = np.random.normal(0, 0.05, base_shape.vertices.shape) base_shape.vertices += noise # Smooth the result base_shape = base_shape.smoothed() return base_shape def handle_rigging_failure(self, mesh: trimesh.Trimesh) -> trimesh.Trimesh: """Fallback for rigging failure - return unrigged mesh""" return mesh def complete_fallback_generation(self, description: str, generation_log: Dict) -> Dict[str, Any]: """Complete fallback generation when entire pipeline fails""" print("🔄 Starting complete fallback generation...") # Generate all components using fallbacks print("📝 Generating fallback text...") traits, dialogue = self.handle_text_gen_failure(description) print(f"✅ Fallback text generated: {traits.get('name', 'Unknown')}") print("🎨 Generating fallback image...") image = self.handle_image_gen_failure(description) print("✅ Fallback image generated") print("🔲 Generating fallback 3D model...") model_3d = self.handle_3d_gen_failure(image) print("✅ Fallback 3D model generated") # Save fallback results timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") image_path = f"/tmp/fallback_monster_{timestamp}.png" model_path = f"/tmp/fallback_monster_{timestamp}.glb" print(f"💾 Saving fallback files...") image.save(image_path) model_3d.export(model_path) print(f"✅ Fallback files saved: {image_path}, {model_path}") print("🎉 Complete fallback generation finished!") return { 'description': description, 'traits': traits, 'dialogue': dialogue, 'image': image, 'model_3d': model_path, 'download_files': [image_path, model_path], 'generation_log': generation_log, 'status': 'fallback', 'message': '⚡ Generated using quick fallback mode' } def _detect_element(self, description: str) -> str: """Detect element type from description""" description_lower = description.lower() element_keywords = { 'fire': ['fire', 'flame', 'burn', 'hot', 'lava', 'ember', 'blaze'], 'water': ['water', 'aqua', 'ocean', 'sea', 'wave', 'liquid', 'swim'], 'earth': ['earth', 'rock', 'stone', 'ground', 'mountain', 'dirt', 'soil'], 'electric': ['electric', 'thunder', 'lightning', 'spark', 'volt', 'shock'], 'nature': ['nature', 'plant', 'tree', 'leaf', 'flower', 'grass', 'forest'] } for element, keywords in element_keywords.items(): if any(keyword in description_lower for keyword in keywords): return element return 'neutral' def _generate_emoji_dialogue(self, mood: str) -> str: """Generate emoji-based dialogue""" emojis = self.dialogue_patterns.get(mood, self.dialogue_patterns['happy']) # Select 2-3 emojis selected_emojis = random.sample(emojis, min(2, len(emojis))) # Add status numbers hp = random.randint(70, 100) happiness = random.randint(60, 95) dialogue = ''.join(selected_emojis) dialogue += f"{hp}️⃣{happiness}️⃣" return dialogue def _draw_procedural_monster(self, draw: ImageDraw.Draw, width: int, height: int, primary_color: str, secondary_color: str): """Draw a procedural monster shape""" center_x, center_y = width // 2, height // 2 # Body (main shape) body_type = random.choice(['circle', 'oval', 'polygon']) if body_type == 'circle': radius = random.randint(80, 120) draw.ellipse( [center_x - radius, center_y - radius, center_x + radius, center_y + radius], fill=primary_color, outline=secondary_color, width=3 ) elif body_type == 'oval': width_r = random.randint(80, 120) height_r = random.randint(100, 140) draw.ellipse( [center_x - width_r, center_y - height_r, center_x + width_r, center_y + height_r], fill=primary_color, outline=secondary_color, width=3 ) else: # polygon num_points = random.randint(5, 8) points = [] for i in range(num_points): angle = (2 * np.pi * i) / num_points r = random.randint(80, 120) x = center_x + int(r * np.cos(angle)) y = center_y + int(r * np.sin(angle)) points.append((x, y)) draw.polygon(points, fill=primary_color, outline=secondary_color, width=3) # Eyes eye_y = center_y - 30 eye_spacing = 40 eye_radius = 15 # Left eye draw.ellipse( [center_x - eye_spacing - eye_radius, eye_y - eye_radius, center_x - eye_spacing + eye_radius, eye_y + eye_radius], fill='white', outline='black', width=2 ) # Pupil draw.ellipse( [center_x - eye_spacing - 5, eye_y - 5, center_x - eye_spacing + 5, eye_y + 5], fill='black' ) # Right eye draw.ellipse( [center_x + eye_spacing - eye_radius, eye_y - eye_radius, center_x + eye_spacing + eye_radius, eye_y + eye_radius], fill='white', outline='black', width=2 ) # Pupil draw.ellipse( [center_x + eye_spacing - 5, eye_y - 5, center_x + eye_spacing + 5, eye_y + 5], fill='black' ) # Add some features features = random.randint(1, 3) if features >= 1: # Add spikes or horns for i in range(3): spike_x = center_x + (i - 1) * 40 spike_y = center_y - 100 draw.polygon( [(spike_x - 10, spike_y + 20), (spike_x, spike_y), (spike_x + 10, spike_y + 20)], fill=secondary_color, outline='black', width=1 ) if features >= 2: # Add arms # Left arm draw.ellipse( [center_x - 100, center_y - 20, center_x - 60, center_y + 20], fill=primary_color, outline=secondary_color, width=2 ) # Right arm draw.ellipse( [center_x + 60, center_y - 20, center_x + 100, center_y + 20], fill=primary_color, outline=secondary_color, width=2 ) if features >= 3: # Add pattern pattern_type = random.choice(['spots', 'stripes']) if pattern_type == 'spots': for _ in range(5): spot_x = center_x + random.randint(-60, 60) spot_y = center_y + random.randint(-40, 40) draw.ellipse( [spot_x - 10, spot_y - 10, spot_x + 10, spot_y + 10], fill=secondary_color ) def get_fallback_stats(self, element: str) -> Dict[str, int]: """Get fallback stats based on element""" base_stats = { 'fire': {'hp': 80, 'attack': 90, 'defense': 60, 'speed': 70, 'special': 85}, 'water': {'hp': 90, 'attack': 70, 'defense': 80, 'speed': 65, 'special': 80}, 'earth': {'hp': 100, 'attack': 75, 'defense': 95, 'speed': 50, 'special': 65}, 'electric': {'hp': 70, 'attack': 80, 'defense': 60, 'speed': 95, 'special': 90}, 'nature': {'hp': 85, 'attack': 65, 'defense': 75, 'speed': 70, 'special': 90}, 'neutral': {'hp': 80, 'attack': 75, 'defense': 75, 'speed': 75, 'special': 75} } stats = base_stats.get(element, base_stats['neutral']).copy() # Add some variation for stat in stats: stats[stat] += random.randint(-10, 10) stats[stat] = max(10, min(150, stats[stat])) # Clamp values return stats