Spaces:
Paused
Paused
| import torch | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| import json | |
| import random | |
| from typing import Dict, Any, List | |
| class QwenTextGenerator: | |
| """Text generation using Qwen2.5-0.5B-Instruct for monster traits and dialogue""" | |
| def __init__(self, device: str = "cuda"): | |
| self.device = device if torch.cuda.is_available() else "cpu" | |
| self.model = None | |
| self.tokenizer = None | |
| self.model_id = "Qwen/Qwen2.5-0.5B-Instruct" | |
| # Generation parameters | |
| self.max_new_tokens = 150 | |
| self.temperature = 0.8 | |
| self.top_p = 0.9 | |
| # Monster trait templates | |
| self.trait_categories = { | |
| 'elements': ['fire', 'water', 'earth', 'wind', 'electric', 'ice', 'nature', 'dark', 'light', 'neutral'], | |
| 'personalities': ['brave', 'timid', 'aggressive', 'gentle', 'playful', 'serious', 'loyal', 'independent', 'curious', 'protective'], | |
| 'body_types': ['bipedal', 'quadruped', 'serpentine', 'avian', 'aquatic', 'insectoid', 'humanoid', 'amorphous'], | |
| 'sizes': ['tiny', 'small', 'medium', 'large', 'giant'], | |
| 'special_features': ['wings', 'horns', 'tail', 'spikes', 'fur', 'scales', 'armor', 'crystals', 'flames', 'aura'] | |
| } | |
| def load_model(self): | |
| """Lazy load the text generation model""" | |
| if self.model is None: | |
| try: | |
| # Load tokenizer | |
| self.tokenizer = AutoTokenizer.from_pretrained(self.model_id) | |
| # Model configuration | |
| torch_dtype = torch.float16 if self.device == "cuda" else torch.float32 | |
| self.model = AutoModelForCausalLM.from_pretrained( | |
| self.model_id, | |
| torch_dtype=torch_dtype, | |
| device_map="auto" if self.device == "cuda" else None, | |
| low_cpu_mem_usage=True | |
| ) | |
| if self.device == "cpu": | |
| self.model.to(self.device) | |
| except Exception as e: | |
| print(f"Failed to load text generation model: {e}") | |
| raise | |
| def generate_traits(self, description: str) -> Dict[str, Any]: | |
| """Generate monster traits from description""" | |
| try: | |
| self.load_model() | |
| # Create prompt for trait generation | |
| prompt = self._create_trait_prompt(description) | |
| # Generate response | |
| inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device) | |
| with torch.no_grad(): | |
| outputs = self.model.generate( | |
| **inputs, | |
| max_new_tokens=self.max_new_tokens, | |
| temperature=self.temperature, | |
| top_p=self.top_p, | |
| do_sample=True, | |
| pad_token_id=self.tokenizer.eos_token_id | |
| ) | |
| response = self.tokenizer.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True) | |
| # Parse traits from response | |
| traits = self._parse_traits(response, description) | |
| return traits | |
| except Exception as e: | |
| print(f"Error generating traits: {e}") | |
| return self._generate_fallback_traits(description) | |
| def generate_dialogue(self, traits: Dict[str, Any]) -> str: | |
| """Generate monster dialogue (emoji + numbers)""" | |
| try: | |
| # Create emoji dialogue based on personality and mood | |
| personality = traits.get('personality', 'neutral') | |
| # Emoji mapping for personalities | |
| emoji_map = { | |
| 'brave': ['💪', '🔥', '⚔️', '🛡️'], | |
| 'timid': ['😰', '🥺', '💦', '❓'], | |
| 'aggressive': ['😤', '💢', '🔥', '⚡'], | |
| 'gentle': ['💚', '🌸', '✨', '🌟'], | |
| 'playful': ['😊', '🎮', '🎯', '🎪'], | |
| 'serious': ['🤖', '📊', '⚡', '💯'], | |
| 'loyal': ['💖', '🤝', '🛡️', '⭐'], | |
| 'independent': ['🚀', '🌍', '🔮', '💫'], | |
| 'curious': ['🔍', '❓', '💡', '🌟'], | |
| 'protective': ['🛡️', '💪', '🏰', '⚔️'] | |
| } | |
| # Get appropriate emojis | |
| emojis = emoji_map.get(personality, ['🤖', '💚', '✨']) | |
| selected_emojis = random.sample(emojis, min(2, len(emojis))) | |
| # Generate status numbers (representing monster's current state) | |
| hp_percent = random.randint(70, 100) | |
| happiness = random.randint(60, 95) | |
| energy = random.randint(50, 90) | |
| # Create dialogue | |
| dialogue = f"{selected_emojis[0]}{selected_emojis[1] if len(selected_emojis) > 1 else '💚'}" | |
| dialogue += f"{hp_percent}️⃣{happiness}️⃣" | |
| return dialogue | |
| except Exception as e: | |
| print(f"Error generating dialogue: {e}") | |
| return "🤖💚9️⃣0️⃣" | |
| def _create_trait_prompt(self, description: str) -> str: | |
| """Create prompt for trait generation""" | |
| prompt = f"""<|im_start|>system | |
| You are a creative game designer creating unique digital monsters. Generate detailed traits for a monster based on the description. | |
| <|im_end|> | |
| <|im_start|>user | |
| Create traits for this monster: {description} | |
| Include: name, species, element, personality, appearance details, and special abilities. | |
| <|im_end|> | |
| <|im_start|>assistant | |
| """ | |
| return prompt | |
| def _parse_traits(self, response: str, original_description: str) -> Dict[str, Any]: | |
| """Parse traits from model response""" | |
| traits = { | |
| 'description': original_description, | |
| 'raw_response': response | |
| } | |
| # Extract name | |
| if "name:" in response.lower(): | |
| name_start = response.lower().find("name:") + 5 | |
| name_end = response.find("\n", name_start) | |
| if name_end == -1: | |
| name_end = len(response) | |
| traits['name'] = response[name_start:name_end].strip() | |
| else: | |
| traits['name'] = self._generate_name() | |
| # Extract or assign element | |
| element_found = False | |
| for element in self.trait_categories['elements']: | |
| if element in response.lower(): | |
| traits['element'] = element | |
| element_found = True | |
| break | |
| if not element_found: | |
| traits['element'] = random.choice(self.trait_categories['elements']) | |
| # Extract or assign personality | |
| personality_found = False | |
| for personality in self.trait_categories['personalities']: | |
| if personality in response.lower(): | |
| traits['personality'] = personality | |
| personality_found = True | |
| break | |
| if not personality_found: | |
| traits['personality'] = random.choice(self.trait_categories['personalities']) | |
| # Extract appearance | |
| traits['appearance'] = self._extract_appearance(response) | |
| # Extract abilities | |
| traits['abilities'] = self._extract_abilities(response, traits['element']) | |
| # Add color scheme based on element | |
| traits['color_scheme'] = self._get_color_scheme(traits['element']) | |
| return traits | |
| def _generate_name(self) -> str: | |
| """Generate a random monster name""" | |
| prefixes = ['Pyro', 'Aqua', 'Terra', 'Aero', 'Volt', 'Cryo', 'Flora', 'Shadow', 'Lumi', 'Neo'] | |
| suffixes = ['mon', 'beast', 'guard', 'wing', 'claw', 'fang', 'horn', 'tail', 'byte', 'spark'] | |
| return random.choice(prefixes) + random.choice(suffixes) | |
| def _extract_appearance(self, response: str) -> str: | |
| """Extract appearance description""" | |
| appearance_keywords = ['appearance', 'looks like', 'resembles', 'body', 'color', 'size'] | |
| for keyword in appearance_keywords: | |
| if keyword in response.lower(): | |
| start = response.lower().find(keyword) | |
| end = response.find('.', start) | |
| if end == -1: | |
| end = response.find('\n', start) | |
| if end == -1: | |
| end = len(response) | |
| return response[start:end].strip() | |
| # Fallback appearance | |
| body_type = random.choice(self.trait_categories['body_types']) | |
| size = random.choice(self.trait_categories['sizes']) | |
| feature = random.choice(self.trait_categories['special_features']) | |
| return f"A {size} {body_type} creature with {feature}" | |
| def _extract_abilities(self, response: str, element: str) -> List[str]: | |
| """Extract or generate abilities""" | |
| abilities = [] | |
| ability_keywords = ['ability', 'power', 'skill', 'can', 'capable'] | |
| for keyword in ability_keywords: | |
| if keyword in response.lower(): | |
| # Try to extract abilities from response | |
| start = response.lower().find(keyword) | |
| end = response.find('.', start) | |
| if end > start: | |
| ability_text = response[start:end] | |
| abilities.append(ability_text.strip()) | |
| # If no abilities found, generate based on element | |
| if not abilities: | |
| element_abilities = { | |
| 'fire': ['Flame Burst', 'Heat Wave', 'Ember Shield'], | |
| 'water': ['Aqua Jet', 'Bubble Shield', 'Tidal Wave'], | |
| 'earth': ['Rock Throw', 'Earthquake', 'Stone Armor'], | |
| 'wind': ['Gust', 'Tornado', 'Wind Shield'], | |
| 'electric': ['Thunder Shock', 'Static Field', 'Lightning Speed'], | |
| 'ice': ['Ice Beam', 'Frost Armor', 'Blizzard'], | |
| 'nature': ['Vine Whip', 'Healing Bloom', 'Nature\'s Guard'], | |
| 'dark': ['Shadow Strike', 'Dark Pulse', 'Void Shield'], | |
| 'light': ['Light Beam', 'Healing Light', 'Radiant Shield'], | |
| 'neutral': ['Tackle', 'Defense Curl', 'Focus'] | |
| } | |
| abilities = random.sample( | |
| element_abilities.get(element, element_abilities['neutral']), | |
| 2 | |
| ) | |
| return abilities | |
| def _get_color_scheme(self, element: str) -> str: | |
| """Get color scheme based on element""" | |
| color_schemes = { | |
| 'fire': 'red and orange with yellow accents', | |
| 'water': 'blue and cyan with white highlights', | |
| 'earth': 'brown and green with stone textures', | |
| 'wind': 'white and light blue with swirling patterns', | |
| 'electric': 'yellow and blue with sparking effects', | |
| 'ice': 'light blue and white with crystalline features', | |
| 'nature': 'green and brown with leaf patterns', | |
| 'dark': 'black and purple with shadow effects', | |
| 'light': 'white and gold with glowing aura', | |
| 'neutral': 'gray and silver with balanced tones' | |
| } | |
| return color_schemes.get(element, 'varied colors with unique patterns') | |
| def _generate_fallback_traits(self, description: str) -> Dict[str, Any]: | |
| """Generate fallback traits if model fails""" | |
| element = random.choice(self.trait_categories['elements']) | |
| personality = random.choice(self.trait_categories['personalities']) | |
| return { | |
| 'name': self._generate_name(), | |
| 'species': 'Digital Monster', | |
| 'element': element, | |
| 'personality': personality, | |
| 'appearance': f"A unique {random.choice(self.trait_categories['sizes'])} digital creature", | |
| 'color_scheme': self._get_color_scheme(element), | |
| 'abilities': self._extract_abilities("", element), | |
| 'description': description | |
| } | |
| def to(self, device: str): | |
| """Move model to specified device""" | |
| self.device = device | |
| if self.model: | |
| self.model.to(device) | |
| def __del__(self): | |
| """Cleanup when object is destroyed""" | |
| if self.model: | |
| del self.model | |
| if self.tokenizer: | |
| del self.tokenizer | |
| torch.cuda.empty_cache() |