| | |
| |
|
| | 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 |