|
|
|
|
|
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 |
|
|
|
|
|
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: |
|
|
|
self.en_generator = pipeline( |
|
"text-generation", |
|
model="EleutherAI/gpt-neo-1.3B", |
|
device="cpu" |
|
) |
|
logger.info("Initialized English story generator") |
|
|
|
|
|
self.en_sentiment = pipeline( |
|
"sentiment-analysis", |
|
model="distilbert-base-uncased-finetuned-sst-2-english", |
|
device="cpu" |
|
) |
|
logger.info("Initialized English sentiment analyzer") |
|
|
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
self.translator_ar_to_en = pipeline( |
|
"translation", |
|
model="Helsinki-NLP/opus-mt-ar-en", |
|
device="cpu" |
|
) |
|
logger.info("Initialized Arabic to English 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 |
|
|
|
|
|
self.banned_patterns = [ |
|
r'sex', r'fuck', r'porn', r'explicit', |
|
r'malay', r'panama', r'solar', r'queent', |
|
|
|
r'جنس', r'فاحش', r'إباحي' |
|
] |
|
|
|
|
|
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)""" |
|
|
|
for pattern in self.banned_patterns: |
|
if re.search(pattern, text.lower()): |
|
logger.warning(f"Content filtered - banned pattern detected") |
|
return False |
|
|
|
|
|
words = text.split() |
|
if len(words) > 20: |
|
unique_ratio = len(set(words)) / len(words) |
|
if unique_ratio < 0.4: |
|
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']) |
|
|
|
|
|
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: |
|
|
|
translated = self.translate(text, "arabic", "english") |
|
return self.analyze_sentiment(translated, "english") |
|
else: |
|
|
|
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""" |
|
|
|
prompt_lower = prompt.lower() |
|
|
|
|
|
template_type = "adventure" |
|
|
|
|
|
adventure_terms = ["adventure", "explorer", "ruins", "ancient", "discover", "treasure", "quest", |
|
"مغامرة", "مستكشف", "آثار", "قديمة", "اكتشاف", "كنز"] |
|
|
|
|
|
friendship_terms = ["friend", "friendship", "relationship", "together", "bond", "cat", "robot", |
|
"صداقة", "صديق", "علاقة", "معًا", "رابطة", "قطة", "روبوت"] |
|
|
|
|
|
fantasy_terms = ["magic", "wizard", "spell", "kingdom", "power", "mystical", "enchanted", |
|
"سحر", "ساحر", "تعويذة", "مملكة", "قوة", "غامض", "مسحور"] |
|
|
|
|
|
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) |
|
|
|
|
|
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" |
|
|
|
|
|
params = {} |
|
|
|
if template_type == "adventure": |
|
|
|
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 "المغامر" |
|
|
|
|
|
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 "الكنز المفقود" |
|
|
|
|
|
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": |
|
|
|
params["character1"] = "Whiskers" if language == "english" else "مشمش" |
|
params["character2"] = "Bolt" if language == "english" else "بولت" |
|
|
|
|
|
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": |
|
|
|
params["protagonist"] = "young wizard" if language == "english" else "الساحر الشاب" |
|
params["magic"] = "elemental magic" if language == "english" else "سحر العناصر" |
|
params["place"] = "mystical kingdom" if language == "english" else "المملكة السحرية" |
|
|
|
|
|
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: |
|
|
|
language = language.lower() |
|
if language not in ["english", "arabic"]: |
|
language = "english" |
|
|
|
|
|
input_language = self.detect_language(prompt) |
|
if input_language != language: |
|
prompt = self.translate(prompt, input_language, language) |
|
|
|
|
|
if template_name and parameters: |
|
if template_name in self.story_templates[language]: |
|
|
|
template_options = self.story_templates[language][template_name] |
|
template = random.choice(template_options) |
|
story = self._customize_template(template, parameters) |
|
else: |
|
|
|
template = random.choice(self.story_templates[language]["adventure"]) |
|
story = self._customize_template(template, parameters) |
|
else: |
|
|
|
template_type, params = self._select_template(prompt, language) |
|
|
|
|
|
template_options = self.story_templates[language][template_type] |
|
template = random.choice(template_options) |
|
|
|
|
|
story = self._customize_template(template, params) |
|
|
|
|
|
if not self.is_safe_content(story): |
|
|
|
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) |
|
|
|
|
|
emotions = self.analyze_sentiment(story, language) |
|
|
|
return story, emotions |
|
|
|
except Exception as e: |
|
logger.error(f"Story generation error: {str(e)}") |
|
|
|
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: |
|
|
|
self.story_generator = MultilingualStoryGenerator() |
|
logger.info("Loaded multilingual story generator") |
|
|
|
|
|
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") |
|
|
|
|
|
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: |
|
|
|
lang = language.lower() |
|
|
|
|
|
story, emotions = self.story_generator.generate_story( |
|
prompt, |
|
lang, |
|
template_name, |
|
parameters |
|
) |
|
|
|
|
|
if lang == "arabic": |
|
emotion_summary = "\n\nالنبرة العاطفية:\n" |
|
for emotion, score in emotions.items(): |
|
|
|
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: |
|
|
|
story = self.generate_text(prompt, language, template_name, parameters) |
|
if "Error generating text" in story or "عذراً، حدث خطأ" in story: |
|
return story, [] |
|
|
|
|
|
if language.lower() == "arabic": |
|
story_for_image = self.translate_ar_to_en(story) |
|
else: |
|
story_for_image = story |
|
|
|
|
|
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) |
|
|
|
|
|
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: |
|
|
|
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 |
|
""" |
|
) |
|
|
|
|
|
with gr.Tabs() as tabs: |
|
|
|
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 |
|
) |
|
|
|
|
|
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 |
|
) |
|
|
|
|
|
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] |
|
) |
|
|
|
|
|
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 |