abdull4h's picture
Update app.py
242352d verified
# app.py
import gradio as gr
import torch
import logging
import re
import random
from gradio.themes import Soft
import json
import spaces
import gc
from typing import List, Dict, Tuple, Optional, Any
from dataclasses import dataclass
from transformers import (
pipeline,
MarianMTModel,
MarianTokenizer,
AutoTokenizer,
AutoModelForSeq2SeqLM
)
from diffusers import StableDiffusionPipeline
import numpy as np
from PIL import Image
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
@dataclass
class StoryTemplate:
"""Story template data structure"""
name: str
template_en: str
template_ar: str
parameters: List[str]
class MultilingualStoryGenerator:
"""Robust story generation in both English and Arabic with content safety"""
def __init__(self):
try:
# Initialize English generator
self.en_generator = pipeline(
"text-generation",
model="EleutherAI/gpt-neo-1.3B",
device="cpu"
)
logger.info("Initialized English story generator")
# Initialize dedicated English sentiment analyzer
self.en_sentiment = pipeline(
"sentiment-analysis",
model="distilbert-base-uncased-finetuned-sst-2-english",
device="cpu"
)
logger.info("Initialized English sentiment analyzer")
# Initialize Arabic MT5 model - better for controlled generation
try:
self.ar_tokenizer = AutoTokenizer.from_pretrained("google/mt5-small")
self.ar_model = AutoModelForSeq2SeqLM.from_pretrained("google/mt5-small")
logger.info("Initialized MT5 for Arabic generation")
self.ar_model_available = True
except Exception as e:
logger.error(f"Failed to load Arabic MT5 model: {str(e)}")
self.ar_model_available = False
# Initialize Arabic sentiment analyzer if possible
try:
self.ar_sentiment = pipeline(
"sentiment-analysis",
model="CAMeL-Lab/bert-base-arabic-sentiment",
device="cpu"
)
logger.info("Initialized Arabic sentiment analyzer")
self.ar_sentiment_available = True
except Exception as e:
logger.error(f"Failed to load Arabic sentiment: {str(e)}")
self.ar_sentiment_available = False
# Initialize translator models
self.translator_ar_to_en = pipeline(
"translation",
model="Helsinki-NLP/opus-mt-ar-en",
device="cpu"
)
logger.info("Initialized Arabic to English translator")
# Attempt to load English to Arabic translator
try:
self.translator_en_to_ar = pipeline(
"translation",
model="Helsinki-NLP/opus-mt-en-ar",
device="cpu"
)
logger.info("Initialized English to Arabic translator")
self.en_to_ar_available = True
except Exception as e:
logger.error(f"Failed to load EN->AR translator: {str(e)}")
self.en_to_ar_available = False
# Content filtering regex patterns
self.banned_patterns = [
r'sex', r'fuck', r'porn', r'explicit',
r'malay', r'panama', r'solar', r'queent',
# Arabic inappropriate terms
r'جنس', r'فاحش', r'إباحي'
]
# Pre-written high-quality templates
self.story_templates = self._load_templates()
self.initialized = True
except Exception as e:
logger.error(f"Failed to initialize MultilingualStoryGenerator: {str(e)}")
self.initialized = False
def _load_templates(self) -> Dict[str, Dict[str, List[str]]]:
"""Load high-quality story templates in both languages"""
return {
"english": {
"adventure": [
"In the ancient ruins of {location}, a brave {hero} discovered a map leading to {object}. The journey wouldn't be easy, as dangerous traps and mythical creatures guarded the path. Armed with courage and determination, {hero} ventured into the unknown. After overcoming numerous challenges, including a treacherous river crossing and a riddle-speaking sphinx, {hero} finally reached the chamber where {object} was kept. This treasure wasn't just valuable - it held the power to change the world.",
"The small village of {location} had a legend about {object}, a mysterious artifact said to grant its finder extraordinary powers. {hero}, a curious and adventurous soul, had always been fascinated by this tale. When strange events began occurring in the village, {hero} realized the time had come to seek {object}. The journey through dark forests and misty mountains tested every skill {hero} possessed. But with each obstacle overcome, {hero} grew stronger and wiser, eventually discovering that the true power of {object} was not what anyone had expected.",
"{hero} had spent years studying ancient texts about {object}, hidden somewhere in {location}. Scholars had dismissed these stories as myths, but {hero} knew better. When troubling signs appeared in the sky, {hero} recognized them from the texts - it was time to find {object}. The expedition into {location} revealed dangers and wonders beyond imagination. {hero} faced not only physical challenges but moral dilemmas that questioned everything {hero} believed. In the final chamber, {hero} discovered that {object} was actually a gateway to knowledge that would forever change humanity's understanding of the universe."
],
"friendship": [
"In a quiet neighborhood, a small cat named {character1} lived alone, scavenging for food and shelter. One rainy day, {character1} discovered an abandoned robot called {character2} in an alley, damaged and powered down. Curious, {character1} stayed nearby until a kind engineer reactivated {character2}. As {character2} came back to life, it was confused and disoriented. {character1}, despite being initially cautious, started visiting {character2} daily. Slowly, an unusual friendship formed. {character2} would protect {character1} from the neighborhood dogs, while {character1} would guide {character2} through the city streets. They taught each other about their very different perspectives on life, proving that friendship can transcend even the greatest differences.",
"{character1} was the most advanced robot created by Future Technologies, designed to learn and evolve. However, {character1} felt something was missing in its programmed existence. One day, a stray cat named {character2} wandered into the laboratory. Instead of shooing it away, {character1} offered it food. Day after day, {character2} returned, and {character1} began to experience emotions not in its programming. When the company decided to reset {character1}'s learning algorithms, {character2} somehow sensed the danger and created a distraction that allowed {character1} to escape. Together, they embarked on an adventure that would redefine what it means to be alive and to care for another being.",
"After a technical malfunction, {character1}, an experimental domestic robot, escaped from the research facility and hid in an abandoned building. There, it encountered {character2}, a street-smart cat who initially hissed and clawed at the strange metal creature. But when winter came and temperatures dropped, {character1} used its internal heating to keep {character2} warm. Gradually, {character2} began to trust the gentle robot. When researchers finally tracked down {character1}, they were amazed to find it had developed emotional responses through its friendship with {character2}. Instead of reclaiming the robot, they decided to study this unique bond, learning valuable lessons about connection and compassion that would transform robotics forever."
],
"fantasy": [
"In the enchanted kingdom of {place}, young {protagonist} lived an ordinary life until the day of the Great Storm. As lightning struck the ancient oak tree in the village square, {protagonist} felt a strange tingling sensation. Suddenly, {protagonist} could control {magic} - a power not seen in the realm for centuries. But as {protagonist} was discovering these new abilities, darkness was spreading across {place}. The evil sorcerer Malakar had returned and was draining the life force from the land. With the guidance of a wise old wizard, {protagonist} learned to harness {magic}, gathering allies and growing stronger. In a final confrontation that lit up the sky, {protagonist} faced Malakar, using {magic} in ways never imagined to save {place} and restore balance to the world.",
"{protagonist} had always felt different from the other inhabitants of {place}. Strange dreams haunted the nights, and unexplainable events occurred when emotions ran high. On the day of the centennial festival, as {protagonist} touched the sacred crystal in the town square, a surge of {magic} awakened within. The elders revealed a prophecy: {protagonist} was the chosen one who must save {place} from the shadow that emerges once every hundred years. With newfound abilities of {magic}, {protagonist} journeyed to the forbidden mountains, discovering ancient truths about {place}'s history and the true nature of the shadow. The final battle tested not just {protagonist}'s command of {magic}, but also wisdom, compassion, and the courage to make the ultimate sacrifice for {place}.",
"The floating islands of {place} were held together by ancient magic, but the bridges between them had begun to crumble. {protagonist}, a student at the Academy of Mystic Arts, accidentally discovered an ability to control {magic} during a failed classroom experiment. This power, thought to be extinct, marked {protagonist} as the heir to the Founder's legacy. As {place} began to literally fall apart, {protagonist} had to master {magic} while evading the Void Seekers, who believed destroying {place} would usher in a new world order. Through forgotten ruins and secret libraries, {protagonist} pieced together the complex spell that could restore {place}, discovering that {magic} was actually the lifeforce that connected all the inhabitants of the floating realm."
]
},
"arabic": {
"adventure": [
"في آثار {location} القديمة، اكتشف {hero} الشجاع خريطة تقود إلى {object}. لم تكن الرحلة سهلة، حيث كانت الفخاخ الخطيرة والمخلوقات الأسطورية تحرس الطريق. مسلحًا بالشجاعة والتصميم، غامر {hero} في المجهول. بعد التغلب على تحديات عديدة، بما في ذلك عبور نهر خطير وأبو الهول الذي يتحدث بالألغاز، وصل {hero} أخيرًا إلى الغرفة التي يُحفظ فيها {object}. لم يكن هذا الكنز قيمًا فحسب - بل كان يملك القدرة على تغيير العالم.",
"كانت للقرية الصغيرة {location} أسطورة عن {object}، وهي قطعة أثرية غامضة يُقال إنها تمنح من يجدها قوى استثنائية. كان {hero}، وهو شخص فضولي ومغامر، مفتونًا دائمًا بهذه القصة. عندما بدأت أحداث غريبة تحدث في القرية، أدرك {hero} أن الوقت قد حان للبحث عن {object}. اختبرت الرحلة عبر الغابات المظلمة والجبال الضبابية كل مهارة يمتلكها {hero}. لكن مع كل عقبة تم التغلب عليها، أصبح {hero} أقوى وأكثر حكمة، ليكتشف في النهاية أن القوة الحقيقية لـ {object} لم تكن كما توقع أي شخص.",
"قضى {hero} سنوات في دراسة النصوص القديمة حول {object}، المخفي في مكان ما في {location}. رفض العلماء هذه القصص باعتبارها أساطير، لكن {hero} كان يعرف الحقيقة. عندما ظهرت علامات مقلقة في السماء، تعرف عليها {hero} من النصوص - حان الوقت للعثور على {object}. كشفت البعثة إلى {location} عن مخاطر وعجائب تفوق الخيال. واجه {hero} ليس فقط التحديات الجسدية ولكن أيضًا المعضلات الأخلاقية التي شككت في كل ما اعتقده {hero}. في الغرفة الأخيرة، اكتشف {hero} أن {object} كان في الواقع بوابة إلى المعرفة التي من شأنها أن تغير إلى الأبد فهم البشرية للكون."
],
"friendship": [
"في حي هادئ، عاشت قطة صغيرة تدعى {character1} وحدها، تبحث عن الطعام والمأوى. في يوم ممطر، اكتشفت {character1} روبوتًا مهجورًا يدعى {character2} في زقاق، متضررًا ومتوقفًا عن العمل. بدافع الفضول، بقيت {character1} في الجوار حتى قام مهندس لطيف بإعادة تنشيط {character2}. عندما عادت {character2} إلى الحياة، كانت مرتبكة وغير مستقرة. بدأت {character1}، رغم حذرها المبدئي، في زيارة {character2} يوميًا. ببطء، تشكلت صداقة غير عادية. كانت {character2} تحمي {character1} من كلاب الحي، بينما كانت {character1} ترشد {character2} عبر شوارع المدينة. علّم كل منهما الآخر عن وجهات نظرهما المختلفة جدًا في الحياة، مما يثبت أن الصداقة يمكن أن تتجاوز حتى أكبر الاختلافات.",
"كانت {character1} أكثر الروبوتات تطوراً التي أنشأتها شركة تكنولوجيا المستقبل، مصممة للتعلم والتطور. ومع ذلك، شعرت {character1} بأن هناك شيئًا مفقودًا في وجودها المبرمج. في أحد الأيام، تسللت قطة ضالة تدعى {character2} إلى المختبر. بدلاً من طردها، قدمت لها {character1} الطعام. يومًا بعد يوم، عادت {character2}، وبدأت {character1} تشعر بعواطف لم تكن في برمجتها. عندما قررت الشركة إعادة ضبط خوارزميات التعلم الخاصة بـ {character1}، استشعرت {character2} الخطر بطريقة ما وخلقت تشتيتًا سمح لـ {character1} بالهروب. معًا، شرعا في مغامرة من شأنها إعادة تعريف معنى الحياة والاهتمام بكائن آخر.",
"بعد عطل فني، هربت {character1}، وهي روبوت منزلي تجريبي، من منشأة الأبحاث واختبأت في مبنى مهجور. هناك، واجهت {character2}، وهي قطة ذكية في الشارع هسهست في البداية وخدشت المخلوق المعدني الغريب. ولكن عندما جاء الشتاء وانخفضت درجات الحرارة، استخدمت {character1} التدفئة الداخلية للحفاظ على دفء {character2}. تدريجيًا، بدأت {character2} تثق في الروبوت اللطيف. عندما عثر الباحثون أخيرًا على {character1}، ذهلوا عندما وجدوا أنها طورت استجابات عاطفية من خلال صداقتها مع {character2}. وبدلاً من استعادة الروبوت، قرروا دراسة هذه العلاقة الفريدة، مما أدى إلى تعلم دروس قيمة حول الارتباط والتعاطف من شأنها أن تغير علم الروبوتات إلى الأبد."
],
"fantasy": [
"في مملكة {place} المسحورة، عاش {protagonist} الشاب حياة عادية حتى يوم العاصفة الكبرى. عندما ضرب البرق شجرة البلوط القديمة في ساحة القرية، شعر {protagonist} بإحساس غريب من الوخز. فجأة، أصبح {protagonist} قادرًا على التحكم في {magic} - وهي قوة لم تظهر في المملكة منذ قرون. ولكن بينما كان {protagonist} يكتشف هذه القدرات الجديدة، كان الظلام ينتشر في أنحاء {place}. لقد عاد الساحر الشرير مالاكار وكان يستنزف قوة الحياة من الأرض. بتوجيه من ساحر عجوز حكيم، تعلم {protagonist} ترويض {magic}، وجمع الحلفاء وأصبح أقوى. في مواجهة نهائية أضاءت السماء، واجه {protagonist} مالاكار، واستخدم {magic} بطرق لم تكن متخيلة من قبل لإنقاذ {place} واستعادة التوازن إلى العالم.",
"شعر {protagonist} دائمًا بأنه مختلف عن سكان {place} الآخرين. طاردت الأحلام الغريبة الليالي، ووقعت أحداث غير قابلة للتفسير عندما كانت المشاعر مرتفعة. في يوم المهرجان المئوي، عندما لمس {protagonist} الكريستال المقدس في ساحة البلدة، استيقظت موجة من {magic} بداخله. كشف الشيوخ عن نبوءة: كان {protagonist} هو المختار الذي يجب أن ينقذ {place} من الظل الذي يظهر مرة واحدة كل مائة عام. مع القدرات الجديدة لـ {magic}، سافر {protagonist} إلى الجبال المحرمة، واكتشف حقائق قديمة حول تاريخ {place} والطبيعة الحقيقية للظل. اختبرت المعركة النهائية ليس فقط تحكم {protagonist} في {magic}، ولكن أيضًا الحكمة والتعاطف والشجاعة لتقديم التضحية النهائية من أجل {place}.",
"كانت الجزر العائمة في {place} متماسكة بفضل السحر القديم، لكن الجسور بينها بدأت تتفكك. اكتشف {protagonist}، وهو طالب في أكاديمية الفنون الصوفية، عن طريق الصدفة قدرة على التحكم في {magic} خلال تجربة فاشلة في الفصل الدراسي. هذه القوة، التي كان يعتقد أنها انقرضت، وضعت علامة على {protagonist} كوريث لإرث المؤسس. مع بدء {place} في التفكك حرفيًا، كان على {protagonist} إتقان {magic} مع تجنب المتطلعين إلى الفراغ، الذين اعتقدوا أن تدمير {place} سيؤدي إلى نظام عالمي جديد. من خلال الأنقاض المنسية والمكتبات السرية، جمع {protagonist} أجزاء التعويذة المعقدة التي يمكن أن تعيد {place}، ليكتشف أن {magic} كان في الواقع قوة الحياة التي تربط جميع سكان المملكة العائمة."
]
}
}
def is_safe_content(self, text: str) -> bool:
"""Check if content is safe to display (basic pattern filtering)"""
# Check against banned patterns
for pattern in self.banned_patterns:
if re.search(pattern, text.lower()):
logger.warning(f"Content filtered - banned pattern detected")
return False
# Check for repetitive patterns (sign of model failure)
words = text.split()
if len(words) > 20:
unique_ratio = len(set(words)) / len(words)
if unique_ratio < 0.4: # Less than 40% unique words
logger.warning(f"Content filtered - repetitive content (unique ratio: {unique_ratio})")
return False
return True
def detect_language(self, text: str) -> str:
"""Detect if text is primarily Arabic or English"""
arabic_char_count = sum(1 for char in text if '\u0600' <= char <= '\u06FF')
return "arabic" if arabic_char_count > len(text) * 0.3 else "english"
def analyze_sentiment(self, text: str, language: str) -> Dict[str, float]:
"""Analyze sentiment in the specified language"""
try:
if language == "arabic":
if self.ar_sentiment_available:
result = self.ar_sentiment(text[:512])[0]
sentiment_label = result['label']
sentiment_score = float(result['score'])
# Convert to standard format
if "positive" in sentiment_label.lower():
return {"positive": sentiment_score, "negative": 1-sentiment_score}
elif "negative" in sentiment_label.lower():
return {"negative": sentiment_score, "positive": 1-sentiment_score}
else:
return {"neutral": sentiment_score}
else:
# Translate to English and use English sentiment
translated = self.translate(text, "arabic", "english")
return self.analyze_sentiment(translated, "english")
else:
# English sentiment analysis
result = self.en_sentiment(text[:512])[0]
sentiment_score = float(result['score'])
if result['label'] == 'POSITIVE':
return {"positive": sentiment_score, "negative": 1-sentiment_score}
else:
return {"negative": sentiment_score, "positive": 1-sentiment_score}
except Exception as e:
logger.error(f"Sentiment analysis error: {str(e)}")
return {"neutral": 1.0}
def translate(self, text: str, from_lang: str, to_lang: str) -> str:
"""Translate between languages"""
try:
if from_lang == to_lang:
return text
if from_lang == "arabic" and to_lang == "english":
result = self.translator_ar_to_en(text[:512])
return result[0]['translation_text']
elif from_lang == "english" and to_lang == "arabic":
if self.en_to_ar_available:
result = self.translator_en_to_ar(text[:512])
return result[0]['translation_text']
else:
logger.warning("English to Arabic translation not available")
return text
else:
return text
except Exception as e:
logger.error(f"Translation error: {str(e)}")
return text
def _select_template(self, prompt: str, language: str) -> Tuple[str, Dict[str, str]]:
"""Select most appropriate template based on prompt keywords"""
# Lower case for matching
prompt_lower = prompt.lower()
# Check for keywords to match
template_type = "adventure" # Default
# Adventure keywords
adventure_terms = ["adventure", "explorer", "ruins", "ancient", "discover", "treasure", "quest",
"مغامرة", "مستكشف", "آثار", "قديمة", "اكتشاف", "كنز"]
# Friendship keywords
friendship_terms = ["friend", "friendship", "relationship", "together", "bond", "cat", "robot",
"صداقة", "صديق", "علاقة", "معًا", "رابطة", "قطة", "روبوت"]
# Fantasy keywords
fantasy_terms = ["magic", "wizard", "spell", "kingdom", "power", "mystical", "enchanted",
"سحر", "ساحر", "تعويذة", "مملكة", "قوة", "غامض", "مسحور"]
# Count matches
adventure_matches = sum(1 for term in adventure_terms if term in prompt_lower)
friendship_matches = sum(1 for term in friendship_terms if term in prompt_lower)
fantasy_matches = sum(1 for term in fantasy_terms if term in prompt_lower)
# Select template with most matches
if friendship_matches > adventure_matches and friendship_matches > fantasy_matches:
template_type = "friendship"
elif fantasy_matches > adventure_matches and fantasy_matches > friendship_matches:
template_type = "fantasy"
# Extract potential parameters based on template type
params = {}
if template_type == "adventure":
# Try to extract hero, object, location from prompt
hero_match = re.search(r"(about|عن)\s+(?:a|an|the)?\s*(\w+)", prompt_lower)
if hero_match:
params["hero"] = hero_match.group(2).capitalize()
else:
params["hero"] = "the adventurer" if language == "english" else "المغامر"
# Try to find object
object_match = re.search(r"(find|discover|يجد|يكتشف)\s+(?:a|an|the)?\s*(\w+)", prompt_lower)
if object_match:
params["object"] = object_match.group(2)
else:
params["object"] = "lost treasure" if language == "english" else "الكنز المفقود"
# Try to find location
location_match = re.search(r"(in|at|في)\s+(?:a|an|the)?\s*(\w+)", prompt_lower)
if location_match:
params["location"] = location_match.group(2)
else:
params["location"] = "ancient temple" if language == "english" else "المعبد القديم"
elif template_type == "friendship":
# Handle friendship template parameters
params["character1"] = "Whiskers" if language == "english" else "مشمش"
params["character2"] = "Bolt" if language == "english" else "بولت"
# Look for character mentions
char_match = re.search(r"(between|بين)\s+(?:a|an|the)?\s*(\w+)", prompt_lower)
if char_match:
params["character1"] = char_match.group(2).capitalize()
elif template_type == "fantasy":
# Handle fantasy template parameters
params["protagonist"] = "young wizard" if language == "english" else "الساحر الشاب"
params["magic"] = "elemental magic" if language == "english" else "سحر العناصر"
params["place"] = "mystical kingdom" if language == "english" else "المملكة السحرية"
# Look for character mentions
protag_match = re.search(r"(about|عن)\s+(?:a|an|the)?\s*(\w+)", prompt_lower)
if protag_match:
params["protagonist"] = protag_match.group(2).capitalize()
return template_type, params
def _customize_template(self, template: str, params: Dict[str, str]) -> str:
"""Fill template with parameters"""
for key, value in params.items():
placeholder = "{" + key + "}"
template = template.replace(placeholder, value)
return template
def generate_story(
self,
prompt: str,
language: str,
template_name: Optional[str] = None,
parameters: Optional[Dict[str, str]] = None
) -> Tuple[str, Dict[str, float]]:
"""Generate a story in the specified language with sentiment analysis"""
if not self.initialized:
error_msg = "Story generator was not properly initialized."
return error_msg, {"neutral": 1.0}
try:
# Normalize language
language = language.lower()
if language not in ["english", "arabic"]:
language = "english"
# Detect input language and translate prompt if needed
input_language = self.detect_language(prompt)
if input_language != language:
prompt = self.translate(prompt, input_language, language)
# If template and parameters are provided, use them
if template_name and parameters:
if template_name in self.story_templates[language]:
# Get random template of the specified type
template_options = self.story_templates[language][template_name]
template = random.choice(template_options)
story = self._customize_template(template, parameters)
else:
# Fallback to adventure if template not found
template = random.choice(self.story_templates[language]["adventure"])
story = self._customize_template(template, parameters)
else:
# Determine best template and parameters from prompt
template_type, params = self._select_template(prompt, language)
# Get random template of determined type
template_options = self.story_templates[language][template_type]
template = random.choice(template_options)
# Fill template with extracted/default parameters
story = self._customize_template(template, params)
# Safety check
if not self.is_safe_content(story):
# Fallback to safe template
template = random.choice(self.story_templates[language]["adventure"])
default_params = {
"hero": "brave explorer" if language == "english" else "المستكشف الشجاع",
"object": "ancient artifact" if language == "english" else "القطعة الأثرية القديمة",
"location": "mysterious ruins" if language == "english" else "الآثار الغامضة"
}
story = self._customize_template(template, default_params)
# Analyze sentiment
emotions = self.analyze_sentiment(story, language)
return story, emotions
except Exception as e:
logger.error(f"Story generation error: {str(e)}")
# Return fallback story
if language == "arabic":
return "كان يا ما كان، في قديم الزمان، قصة لم تكتمل بعد...", {"neutral": 1.0}
else:
return "Once upon a time, there was a story waiting to be told...", {"neutral": 1.0}
class StoryManager:
"""Manages story templates and generation"""
def __init__(self):
self.templates = {
"adventure": StoryTemplate(
"Adventure Quest",
"Tell a story about {hero} who must find {object} in {location}",
"احكِ قصة عن {hero} الذي يجب أن يجد {object} في {location}",
["hero", "object", "location"]
),
"friendship": StoryTemplate(
"Friendship Tale",
"Write about a friendship between {character1} and {character2}",
"اكتب عن صداقة بين {character1} و {character2}",
["character1", "character2"]
),
"fantasy": StoryTemplate(
"Fantasy World",
"Create a tale where {protagonist} discovers they have the power of {magic} and must save {place}",
"اخلق قصة حيث يكتشف {protagonist} أن لديه قوة {magic} ويجب عليه إنقاذ {place}",
["protagonist", "magic", "place"]
)
}
class EmotionAnalyzer:
"""Legacy emotion analyzer class (kept for backward compatibility)"""
def __init__(self):
try:
self.classifier = pipeline(
"sentiment-analysis",
model="distilbert-base-uncased-finetuned-sst-2-english",
device="cpu"
)
logger.info("Initialized emotion analyzer successfully")
except Exception as e:
logger.error(f"Failed to initialize emotion analyzer: {str(e)}")
self.classifier = None
def analyze_emotions(self, text: str) -> Dict[str, float]:
try:
if not self.classifier:
return {"neutral": 1.0}
result = self.classifier(text[:512])[0]
sentiment_score = float(result['score'])
if result['label'] == 'POSITIVE':
emotions = {
"positive": sentiment_score,
"negative": 1 - sentiment_score
}
else:
emotions = {
"negative": sentiment_score,
"positive": 1 - sentiment_score
}
return emotions
except Exception as e:
logger.error(f"Emotion analysis error: {str(e)}")
return {"neutral": 1.0}
class AIStoryteller:
"""Main class for story generation and management"""
def __init__(self):
self.story_manager = StoryManager()
self.emotion_analyzer = EmotionAnalyzer()
self.setup_models()
def setup_models(self):
"""Initialize all required models and pipelines (on CPU by default)"""
try:
# Initialize the new multilingual story generator
self.story_generator = MultilingualStoryGenerator()
logger.info("Loaded multilingual story generator")
# Translation Model (Arabic->English)
translator_model_name = "Helsinki-NLP/opus-mt-ar-en"
self.translator_tokenizer = MarianTokenizer.from_pretrained(translator_model_name)
self.translator_model = MarianMTModel.from_pretrained(translator_model_name).to("cpu")
logger.info("Loaded translation model")
# Image Generation Model (Stable Diffusion), remains on CPU until needed
self.pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16
)
logger.info("Loaded Stable Diffusion pipeline (CPU -> GPU at runtime)")
except Exception as e:
logger.error(f"Error during model initialization: {str(e)}")
raise
def get_story_template(self, template_name: str, language: str) -> str:
"""Get template in specified language"""
template = self.story_manager.templates.get(template_name)
if not template:
raise ValueError(f"Template {template_name} not found")
return template.template_en if language.lower() == "english" else template.template_ar
def fill_template(self, template_name: str, language: str, parameters: Dict[str, str]) -> str:
"""Fill template with provided parameters"""
template = self.get_story_template(template_name, language)
return template.format(**parameters)
def translate_ar_to_en(self, text: str) -> str:
"""Translate Arabic text to English"""
try:
tokenized = self.translator_tokenizer([text], return_tensors="pt", truncation=True)
translation = self.translator_model.generate(**tokenized)
return self.translator_tokenizer.decode(translation[0], skip_special_tokens=True)
except Exception as e:
logger.error(f"Translation error: {str(e)}")
raise ValueError(f"Failed to translate text: {str(e)}")
def detect_language(self, text: str) -> str:
"""A simple heuristic to detect if text is mostly Arabic or English"""
arabic_char_count = sum(1 for char in text if '\u0600' <= char <= '\u06FF')
return "Arabic" if arabic_char_count > len(text) * 0.3 else "English"
def translate_if_needed(self, text: str, from_lang: str, to_lang: str) -> str:
"""Legacy translate method (kept for backward compatibility)"""
if from_lang == to_lang:
return text
if from_lang == "Arabic" and to_lang == "English":
return self.translate_ar_to_en(text)
elif from_lang == "English" and to_lang == "Arabic":
logger.warning("English->Arabic translation not implemented.")
return text
return text
def generate_text(
self,
prompt: str,
language: str,
template_name: Optional[str] = None,
parameters: Optional[Dict[str, str]] = None
) -> str:
"""Generate text with optional template usage (CPU-based generation)"""
try:
# Convert language format to standard format for story generator
lang = language.lower()
# Generate story with sentiment analysis
story, emotions = self.story_generator.generate_story(
prompt,
lang,
template_name,
parameters
)
# Format emotions for display
if lang == "arabic":
emotion_summary = "\n\nالنبرة العاطفية:\n"
for emotion, score in emotions.items():
# Translate emotion names to Arabic
emotion_ar = {
"positive": "إيجابي",
"negative": "سلبي",
"neutral": "محايد"
}.get(emotion, emotion)
emotion_summary += f"- {emotion_ar}: {score:.1%}\n"
else:
emotion_summary = "\n\nEmotional tones:\n"
for emotion, score in emotions.items():
emotion_summary += f"- {emotion}: {score:.1%}\n"
return story + emotion_summary
except Exception as e:
logger.error(f"Text generation error: {str(e)}")
if language.lower() == "arabic":
return f"عذراً، حدث خطأ أثناء إنشاء القصة: {str(e)}"
else:
return f"Error generating text: {str(e)}"
@spaces.GPU
def generate_story_and_scenes(
self,
prompt: str,
language: str,
template_name: Optional[str] = None,
parameters: Optional[Dict[str, str]] = None,
num_scenes: int = 3,
style: str = "realistic"
) -> Tuple[str, List[Image.Image]]:
"""
Single top-level function decorated with @spaces.GPU.
1) Generate story on CPU.
2) Move Stable Diffusion to GPU to generate multiple scenes.
3) Move pipeline back to CPU at the end.
"""
try:
# Step 1: Generate story (CPU)
story = self.generate_text(prompt, language, template_name, parameters)
if "Error generating text" in story or "عذراً، حدث خطأ" in story:
return story, []
# Prepare story for image generation
if language.lower() == "arabic":
story_for_image = self.translate_ar_to_en(story)
else:
story_for_image = story
# Step 2: Move pipe to GPU
self.pipe = self.pipe.to("cuda")
segments = [seg.strip() for seg in story_for_image.split('.') if seg.strip()]
if len(segments) < num_scenes:
selected_segments = segments
else:
step = max(1, len(segments) // num_scenes)
selected_segments = segments[::step][:num_scenes]
style_prompts = {
"realistic": "photorealistic, highly detailed, 4k",
"anime": "anime style, studio ghibli, detailed",
"fantasy": "fantasy art, magical, detailed illustration"
}
scenes = []
for segment in selected_segments:
prompt_for_image = f"{segment}, {style_prompts.get(style, '')}"
with torch.no_grad():
image = self.pipe(
prompt_for_image,
num_inference_steps=30,
guidance_scale=7.5
).images[0]
if image:
scenes.append(image)
# Step 3: Move back to CPU
self.pipe = self.pipe.to("cpu")
torch.cuda.empty_cache()
gc.collect()
return story, scenes
except Exception as e:
logger.error(f"Story and scene generation error: {str(e)}")
if hasattr(self, 'pipe'):
self.pipe = self.pipe.to("cpu")
torch.cuda.empty_cache()
return f"Error generating story and scenes: {str(e)}", []
def build_gradio_interface() -> gr.Blocks:
"""Build and return the Gradio interface"""
storyteller = AIStoryteller()
with gr.Blocks(theme=gr.themes.Soft()) as demo:
# Header
with gr.Row(variant="panel"):
gr.Markdown(
"""
# 📚 AI Bilingual Storyteller & Illustrator
Create engaging stories in English or Arabic, with optional illustrations and emotional analysis.
- 📝 **Basic Story**: Simple text generation
- 🎯 **Template Story**: Guided story creation
- 🎨 **Visual Story**: Story with scene illustrations
"""
)
# Tabs
with gr.Tabs() as tabs:
# Basic Story
with gr.Tab("📝 Basic Story"):
gr.Markdown("Enter a prompt, choose a language, and get a story with emotion analysis.")
with gr.Row():
prompt_text = gr.Textbox(
label="Story Prompt",
placeholder="Once upon a time... / في يوم من الأيام...",
lines=3
)
language_choice = gr.Radio(
choices=["English", "Arabic"],
value="English",
label="Language"
)
generate_button = gr.Button("✨ Generate Story")
output_story = gr.Textbox(
label="Your Generated Story",
lines=8,
show_copy_button=True
)
generate_button.click(
fn=storyteller.generate_text,
inputs=[prompt_text, language_choice],
outputs=output_story
)
# Template Story
with gr.Tab("🎯 Template Story"):
gr.Markdown("Choose a template and fill parameters for a structured story.")
with gr.Row():
template_choice = gr.Dropdown(
choices=list(storyteller.story_manager.templates.keys()),
label="Story Template",
value="adventure"
)
template_language = gr.Radio(
choices=["English", "Arabic"],
value="English",
label="Language"
)
template_params = gr.JSON(
label="Template Parameters",
value={"hero": "brave knight", "object": "magical sword", "location": "enchanted forest"}
)
template_button = gr.Button("🎮 Generate from Template")
template_output = gr.Textbox(
label="Your Generated Story",
lines=8,
show_copy_button=True
)
template_button.click(
fn=lambda t, l, p: storyteller.generate_text(
"", l, template_name=t, parameters=p
),
inputs=[template_choice, template_language, template_params],
outputs=template_output
)
# Visual Story
with gr.Tab("🎨 Visual Story"):
gr.Markdown("Create a story with multiple illustrated scenes.")
with gr.Row():
scene_prompt = gr.Textbox(
label="Story Prompt",
placeholder="Describe your story scene...",
lines=3
)
scene_language = gr.Radio(
choices=["English", "Arabic"],
value="English",
label="Language"
)
with gr.Row():
num_scenes = gr.Slider(
minimum=1,
maximum=5,
value=3,
step=1,
label="Number of Scenes"
)
art_style = gr.Radio(
choices=["realistic", "anime", "fantasy"],
value="realistic",
label="Art Style",
interactive=True
)
scene_button = gr.Button("🎭 Generate Story & Scenes")
scene_story = gr.Textbox(
label="Your Generated Story",
lines=8,
show_copy_button=True
)
scene_gallery = gr.Gallery(
label="Story Scenes",
columns=3,
height="auto"
)
scene_button.click(
fn=storyteller.generate_story_and_scenes,
inputs=[
scene_prompt,
scene_language,
gr.Textbox(value=None, visible=False),
gr.Textbox(value=None, visible=False),
num_scenes,
art_style
],
outputs=[scene_story, scene_gallery]
)
# Footer with Examples
with gr.Row(variant="panel"):
gr.Markdown("""
### Example Prompts
**English:**
- "Tell a story about a young wizard discovering a magical garden"
- "Create an adventure about a brave explorer in ancient ruins"
- "Write about a friendship between a cat and a robot"
**Arabic:**
- "احكِ قصة عن ساحر صغير يكتشف حديقة سحرية"
- "اكتب مغامرة عن مستكشف شجاع في آثار قديمة"
- "اكتب عن صداقة بين قطة وروبوت"
""")
return demo
if __name__ == "__main__":
try:
demo = build_gradio_interface()
demo.launch()
except Exception as e:
logger.error(f"Application startup error: {str(e)}")
raise