Spaces:
Running
on
Zero
Running
on
Zero
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 |