Spaces:
Paused
Paused
| 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 |