digiPal / utils /fallbacks.py
BladeSzaSza's picture
added more logs
e8293cd
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