Fatty509 commited on
Commit
8124d3e
·
verified ·
1 Parent(s): 37a7ebf

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +823 -0
app.py ADDED
@@ -0,0 +1,823 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+
3
+ import gradio as gr
4
+ import torch
5
+ import logging
6
+ import re
7
+ import random
8
+ from gradio.themes import Soft
9
+ import json
10
+ import spaces
11
+ import gc
12
+ from typing import List, Dict, Tuple, Optional, Any
13
+ from dataclasses import dataclass
14
+ from transformers import (
15
+ pipeline,
16
+ MarianMTModel,
17
+ MarianTokenizer,
18
+ AutoTokenizer,
19
+ AutoModelForSeq2SeqLM
20
+ )
21
+ from diffusers import StableDiffusionPipeline
22
+ import numpy as np
23
+ from PIL import Image
24
+
25
+ # Configure logging
26
+ logging.basicConfig(
27
+ level=logging.INFO,
28
+ format='%(asctime)s - %(levelname)s - %(message)s'
29
+ )
30
+ logger = logging.getLogger(__name__)
31
+
32
+ @dataclass
33
+ class StoryTemplate:
34
+ """Story template data structure"""
35
+ name: str
36
+ template_en: str
37
+ template_ar: str
38
+ parameters: List[str]
39
+
40
+ class MultilingualStoryGenerator:
41
+ """Robust story generation in both English and Arabic with content safety"""
42
+
43
+ def __init__(self):
44
+ try:
45
+ # Initialize English generator
46
+ self.en_generator = pipeline(
47
+ "text-generation",
48
+ model="EleutherAI/gpt-neo-1.3B",
49
+ device="cpu"
50
+ )
51
+ logger.info("Initialized English story generator")
52
+
53
+ # Initialize dedicated English sentiment analyzer
54
+ self.en_sentiment = pipeline(
55
+ "sentiment-analysis",
56
+ model="distilbert-base-uncased-finetuned-sst-2-english",
57
+ device="cpu"
58
+ )
59
+ logger.info("Initialized English sentiment analyzer")
60
+
61
+ # Initialize Arabic MT5 model - better for controlled generation
62
+ try:
63
+ self.ar_tokenizer = AutoTokenizer.from_pretrained("google/mt5-small")
64
+ self.ar_model = AutoModelForSeq2SeqLM.from_pretrained("google/mt5-small")
65
+ logger.info("Initialized MT5 for Arabic generation")
66
+ self.ar_model_available = True
67
+ except Exception as e:
68
+ logger.error(f"Failed to load Arabic MT5 model: {str(e)}")
69
+ self.ar_model_available = False
70
+
71
+ # Initialize Arabic sentiment analyzer if possible
72
+ try:
73
+ self.ar_sentiment = pipeline(
74
+ "sentiment-analysis",
75
+ model="CAMeL-Lab/bert-base-arabic-sentiment",
76
+ device="cpu"
77
+ )
78
+ logger.info("Initialized Arabic sentiment analyzer")
79
+ self.ar_sentiment_available = True
80
+ except Exception as e:
81
+ logger.error(f"Failed to load Arabic sentiment: {str(e)}")
82
+ self.ar_sentiment_available = False
83
+
84
+ # Initialize translator models
85
+ self.translator_ar_to_en = pipeline(
86
+ "translation",
87
+ model="Helsinki-NLP/opus-mt-ar-en",
88
+ device="cpu"
89
+ )
90
+ logger.info("Initialized Arabic to English translator")
91
+
92
+ # Attempt to load English to Arabic translator
93
+ try:
94
+ self.translator_en_to_ar = pipeline(
95
+ "translation",
96
+ model="Helsinki-NLP/opus-mt-en-ar",
97
+ device="cpu"
98
+ )
99
+ logger.info("Initialized English to Arabic translator")
100
+ self.en_to_ar_available = True
101
+ except Exception as e:
102
+ logger.error(f"Failed to load EN->AR translator: {str(e)}")
103
+ self.en_to_ar_available = False
104
+
105
+ # Content filtering regex patterns
106
+ self.banned_patterns = [
107
+ r'sex', r'fuck', r'porn', r'explicit',
108
+ r'malay', r'panama', r'solar', r'queent',
109
+ # Arabic inappropriate terms
110
+ r'جنس', r'فاحش', r'إباحي'
111
+ ]
112
+
113
+ # Pre-written high-quality templates
114
+ self.story_templates = self._load_templates()
115
+
116
+ self.initialized = True
117
+ except Exception as e:
118
+ logger.error(f"Failed to initialize MultilingualStoryGenerator: {str(e)}")
119
+ self.initialized = False
120
+
121
+ def _load_templates(self) -> Dict[str, Dict[str, List[str]]]:
122
+ """Load high-quality story templates in both languages"""
123
+ return {
124
+ "english": {
125
+ "adventure": [
126
+ "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.",
127
+
128
+ "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.",
129
+
130
+ "{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."
131
+ ],
132
+ "friendship": [
133
+ "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.",
134
+
135
+ "{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.",
136
+
137
+ "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."
138
+ ],
139
+ "fantasy": [
140
+ "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.",
141
+
142
+ "{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}.",
143
+
144
+ "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."
145
+ ]
146
+ },
147
+ "arabic": {
148
+ "adventure": [
149
+ "في آثار {location} القديمة، اكتشف {hero} الشجاع خريطة تقود إلى {object}. لم تكن الرحلة سهلة، حيث كانت الفخاخ الخطيرة والمخلوقات الأسطورية تحرس الطريق. مسلحًا بالشجاعة والتصميم، غامر {hero} في المجهول. بعد التغلب على تحديات عديدة، بما في ذلك عبور نهر خطير وأبو الهول الذي يتحدث بالألغاز، وصل {hero} أخيرًا إلى الغرفة التي يُحفظ فيها {object}. لم يكن هذا الكنز قيمًا فحسب - بل كان يملك القدرة على تغيير العالم.",
150
+
151
+ "كانت للقرية الصغيرة {location} أسطورة عن {object}، وهي قطعة أثرية غامضة يُقال إنها تمنح من يجدها قوى استثنائية. كان {hero}، وهو شخص فضولي ومغامر، مفتونًا دائمًا بهذه القصة. عندما بدأت أحداث غريبة تحدث في القرية، أدرك {hero} أن الوقت قد حان للبحث عن {object}. اختبرت الرحلة عبر الغابات المظلمة والجبال الضبابية كل مهارة يمتلكها {hero}. لكن مع كل عقبة تم التغلب عليها، أصبح {hero} أقوى وأكثر حكمة، ليكتشف في النهاية أن القوة الحقيقية لـ {object} لم تكن كما توقع أي شخص.",
152
+
153
+ "قضى {hero} سنوات في دراسة النصوص القديمة حول {object}، المخفي في مكان ما في {location}. رفض العلماء هذه القصص باعتبارها أساطير، لكن {hero} كان يعرف الحقيقة. عندما ظهرت علامات مقلقة في السماء، تعرف عليها {hero} من النصوص - حان الوقت للعثور على {object}. كشفت البعثة إلى {location} عن مخاطر وعجائب تفوق الخيال. واجه {hero} ليس فقط التحديات الجسدية ولكن أيضًا المعضلات الأخلاقية التي شككت في كل ما اعتقده {hero}. في الغرفة الأخيرة، اكتشف {hero} أن {object} كان في الواقع بوابة إلى المعرفة التي من شأنها أن تغير إلى الأبد فهم البشرية للكون."
154
+ ],
155
+ "friendship": [
156
+ "في حي هادئ، عاشت قطة صغيرة تدعى {character1} وحدها، تبحث عن الطعام والمأوى. في يوم ممطر، اكتشفت {character1} روبوتًا مهجورًا يدعى {character2} في زقاق، متضررًا ومتوقفًا عن العمل. بدافع الفضول، بقيت {character1} في الجوار حتى قام مهندس لطيف بإعادة تنشيط {character2}. عندما عادت {character2} إلى الحياة، كانت مرتبكة وغير مستقرة. بدأت {character1}، رغم حذرها المبدئي، في زيارة {character2} يوميًا. ببطء، تشكلت صداقة غير عادية. كانت {character2} تحمي {character1} من كلاب الحي، بينما كانت {character1} ترشد {character2} عبر شوارع المدينة. علّم كل منهما الآخر عن وجهات نظرهما المختلفة جدًا في الحياة، مما يثبت أن الصداقة يمكن أن تتجاوز حتى أكبر الاختلافات.",
157
+
158
+ "كانت {character1} أكثر الروبوتات تطوراً التي أنشأتها شركة تكنولوجيا المستقبل، مصممة للتعلم والتطور. ومع ذلك، شعرت {character1} بأن هناك شيئًا مفقودًا في وجودها المبرمج. في أحد الأيام، تسللت قطة ضالة تد��ى {character2} إلى المختبر. بدلاً من طردها، قدمت لها {character1} الطعام. يومًا بعد يوم، عادت {character2}، وبدأت {character1} تشعر بعواطف لم تكن في برمجتها. عندما قررت الشركة إعادة ضبط خوارزميات التعلم الخاصة بـ {character1}، استشعرت {character2} الخطر بطريقة ما وخلقت تشتيتًا سمح لـ {character1} بالهروب. معًا، شرعا في مغامرة من شأنها إعادة تعريف معنى الحياة والاهتمام بكائن آخر.",
159
+
160
+ "بعد عطل فني، هربت {character1}، وهي روبوت منزلي تجريبي، من منشأة الأبحاث واختبأت في مبنى مهجور. هناك، واجهت {character2}، وهي قطة ذكية في الشارع هسهست في البداية وخدشت المخلوق المعدني الغريب. ولكن عندما جاء الشتاء وانخفضت درجات الحرارة، استخدمت {character1} التدفئة الداخلية للحفاظ على دفء {character2}. تدريجيًا، بدأت {character2} تثق في الروبوت اللطيف. عندما عثر الباحثون أخيرًا على {character1}، ذهلوا عندما وجدوا أنها طورت استجابات عاطفية من خلال صداقتها مع {character2}. وبدلاً من استعادة الروبوت، قرروا دراسة هذه العلاقة الفريدة، مما أدى إلى تعلم دروس قيمة حول الارتباط والتعاطف من شأنها أن تغير علم الروبوتات إلى الأبد."
161
+ ],
162
+ "fantasy": [
163
+ "في مملكة {place} المسحورة، عاش {protagonist} الشاب حياة عادية حتى يوم العاصفة الكبرى. عندما ضرب البرق شجرة البلوط القديمة في ساحة القرية، شعر {protagonist} بإحساس غريب من الوخز. فجأة، أصبح {protagonist} قادرًا على التحكم في {magic} - وهي قوة لم تظهر في المملكة منذ قرون. ولكن بينما كان {protagonist} يكتشف هذه القدرات الجديدة، كان الظلام ينتشر في أنحاء {place}. لقد عاد الساحر الشرير مالاكار وكان يستنزف قوة الحياة من الأرض. بتوجيه من ساحر عجوز حكيم، تعلم {protagonist} ترويض {magic}، وجمع الحلفاء وأصبح أقوى. في مواجهة نهائية أضاءت السماء، واجه {protagonist} مالاكار، واستخدم {magic} بطرق لم تكن متخيلة من قبل لإنقاذ {place} واستعادة التوازن إلى العالم.",
164
+
165
+ "شعر {protagonist} دائمًا بأنه مختلف عن سكان {place} الآخرين. طاردت الأحلام الغريبة الليالي، ووقعت أحداث غير قابلة للتفسير عندما كانت المشاعر مرتفعة. في يوم المهرجان المئوي، عندما لمس {protagonist} الكريستال المقدس في ساحة البلدة، استيقظت موجة من {magic} بداخله. كشف الشيوخ عن نبوءة: كان {protagonist} هو المختار الذي يجب أن ينقذ {place} من الظل الذي يظهر مرة واحدة كل مائة عام. مع القدرات الجديدة لـ {magic}، سافر {protagonist} إلى الجبال المحرمة، واكتشف حقائق قديمة حول تاريخ {place} والطبيعة الحقيقية للظل. اختبرت المعركة النهائية ليس فقط تحكم {protagonist} في {magic}، ولكن أيضًا الحكمة والتعاطف والشجاعة لتقديم التضحية النهائية من أجل {place}.",
166
+
167
+ "كانت الجزر العائمة في {place} متماسكة بفضل السحر القديم، لكن الجسور بينها بدأت تتفكك. اكتشف {protagonist}، وهو طالب في أكاديمية الفنون الصوفية، عن طريق الصدفة قدرة على التحكم في {magic} خلال تجربة فاشلة في الفصل الدراسي. هذه القوة، التي كان يعتقد أنها انقرضت، وضعت علامة على {protagonist} كوريث لإرث المؤسس. مع بدء {place} في التفكك حرفيًا، كان على {protagonist} إتقان {magic} مع تجنب المتطلعين إلى الفراغ، الذين اعتقدوا أن تدمير {place} سيؤدي إلى نظام عالمي جديد. من خلال الأنقاض المنسية والمكتبات السرية، جمع {protagonist} أجزاء التعويذة المعقدة التي يمكن أن تعيد {place}، ليكتشف أن {magic} كان في الواقع قوة الحياة التي تربط جميع سكان ال��ملكة العائمة."
168
+ ]
169
+ }
170
+ }
171
+
172
+ def is_safe_content(self, text: str) -> bool:
173
+ """Check if content is safe to display (basic pattern filtering)"""
174
+ # Check against banned patterns
175
+ for pattern in self.banned_patterns:
176
+ if re.search(pattern, text.lower()):
177
+ logger.warning(f"Content filtered - banned pattern detected")
178
+ return False
179
+
180
+ # Check for repetitive patterns (sign of model failure)
181
+ words = text.split()
182
+ if len(words) > 20:
183
+ unique_ratio = len(set(words)) / len(words)
184
+ if unique_ratio < 0.4: # Less than 40% unique words
185
+ logger.warning(f"Content filtered - repetitive content (unique ratio: {unique_ratio})")
186
+ return False
187
+
188
+ return True
189
+
190
+ def detect_language(self, text: str) -> str:
191
+ """Detect if text is primarily Arabic or English"""
192
+ arabic_char_count = sum(1 for char in text if '\u0600' <= char <= '\u06FF')
193
+ return "arabic" if arabic_char_count > len(text) * 0.3 else "english"
194
+
195
+ def analyze_sentiment(self, text: str, language: str) -> Dict[str, float]:
196
+ """Analyze sentiment in the specified language"""
197
+ try:
198
+ if language == "arabic":
199
+ if self.ar_sentiment_available:
200
+ result = self.ar_sentiment(text[:512])[0]
201
+ sentiment_label = result['label']
202
+ sentiment_score = float(result['score'])
203
+
204
+ # Convert to standard format
205
+ if "positive" in sentiment_label.lower():
206
+ return {"positive": sentiment_score, "negative": 1-sentiment_score}
207
+ elif "negative" in sentiment_label.lower():
208
+ return {"negative": sentiment_score, "positive": 1-sentiment_score}
209
+ else:
210
+ return {"neutral": sentiment_score}
211
+ else:
212
+ # Translate to English and use English sentiment
213
+ translated = self.translate(text, "arabic", "english")
214
+ return self.analyze_sentiment(translated, "english")
215
+ else:
216
+ # English sentiment analysis
217
+ result = self.en_sentiment(text[:512])[0]
218
+ sentiment_score = float(result['score'])
219
+
220
+ if result['label'] == 'POSITIVE':
221
+ return {"positive": sentiment_score, "negative": 1-sentiment_score}
222
+ else:
223
+ return {"negative": sentiment_score, "positive": 1-sentiment_score}
224
+
225
+ except Exception as e:
226
+ logger.error(f"Sentiment analysis error: {str(e)}")
227
+ return {"neutral": 1.0}
228
+
229
+ def translate(self, text: str, from_lang: str, to_lang: str) -> str:
230
+ """Translate between languages"""
231
+ try:
232
+ if from_lang == to_lang:
233
+ return text
234
+
235
+ if from_lang == "arabic" and to_lang == "english":
236
+ result = self.translator_ar_to_en(text[:512])
237
+ return result[0]['translation_text']
238
+ elif from_lang == "english" and to_lang == "arabic":
239
+ if self.en_to_ar_available:
240
+ result = self.translator_en_to_ar(text[:512])
241
+ return result[0]['translation_text']
242
+ else:
243
+ logger.warning("English to Arabic translation not available")
244
+ return text
245
+ else:
246
+ return text
247
+ except Exception as e:
248
+ logger.error(f"Translation error: {str(e)}")
249
+ return text
250
+
251
+ def _select_template(self, prompt: str, language: str) -> Tuple[str, Dict[str, str]]:
252
+ """Select most appropriate template based on prompt keywords"""
253
+ # Lower case for matching
254
+ prompt_lower = prompt.lower()
255
+
256
+ # Check for keywords to match
257
+ template_type = "adventure" # Default
258
+
259
+ # Adventure keywords
260
+ adventure_terms = ["adventure", "explorer", "ruins", "ancient", "discover", "treasure", "quest",
261
+ "مغامرة", "مستكشف", "آثار", "قديمة", "اكتشاف", "كنز"]
262
+
263
+ # Friendship keywords
264
+ friendship_terms = ["friend", "friendship", "relationship", "together", "bond", "cat", "robot",
265
+ "صداقة", "صديق", "علاقة", "معًا", "رابطة", "قطة", "روبوت"]
266
+
267
+ # Fantasy keywords
268
+ fantasy_terms = ["magic", "wizard", "spell", "kingdom", "power", "mystical", "enchanted",
269
+ "سحر", "ساحر", "تعويذة", "مملكة", "قوة", "غامض", "مسحور"]
270
+
271
+ # Count matches
272
+ adventure_matches = sum(1 for term in adventure_terms if term in prompt_lower)
273
+ friendship_matches = sum(1 for term in friendship_terms if term in prompt_lower)
274
+ fantasy_matches = sum(1 for term in fantasy_terms if term in prompt_lower)
275
+
276
+ # Select template with most matches
277
+ if friendship_matches > adventure_matches and friendship_matches > fantasy_matches:
278
+ template_type = "friendship"
279
+ elif fantasy_matches > adventure_matches and fantasy_matches > friendship_matches:
280
+ template_type = "fantasy"
281
+
282
+ # Extract potential parameters based on template type
283
+ params = {}
284
+
285
+ if template_type == "adventure":
286
+ # Try to extract hero, object, location from prompt
287
+ hero_match = re.search(r"(about|عن)\s+(?:a|an|the)?\s*(\w+)", prompt_lower)
288
+ if hero_match:
289
+ params["hero"] = hero_match.group(2).capitalize()
290
+ else:
291
+ params["hero"] = "the adventurer" if language == "english" else "المغامر"
292
+
293
+ # Try to find object
294
+ object_match = re.search(r"(find|discover|يجد|يكتشف)\s+(?:a|an|the)?\s*(\w+)", prompt_lower)
295
+ if object_match:
296
+ params["object"] = object_match.group(2)
297
+ else:
298
+ params["object"] = "lost treasure" if language == "english" else "الكنز المفقود"
299
+
300
+ # Try to find location
301
+ location_match = re.search(r"(in|at|في)\s+(?:a|an|the)?\s*(\w+)", prompt_lower)
302
+ if location_match:
303
+ params["location"] = location_match.group(2)
304
+ else:
305
+ params["location"] = "ancient temple" if language == "english" else "المعبد القديم"
306
+
307
+ elif template_type == "friendship":
308
+ # Handle friendship template parameters
309
+ params["character1"] = "Whiskers" if language == "english" else "مشمش"
310
+ params["character2"] = "Bolt" if language == "english" else "بولت"
311
+
312
+ # Look for character mentions
313
+ char_match = re.search(r"(between|بين)\s+(?:a|an|the)?\s*(\w+)", prompt_lower)
314
+ if char_match:
315
+ params["character1"] = char_match.group(2).capitalize()
316
+
317
+ elif template_type == "fantasy":
318
+ # Handle fantasy template parameters
319
+ params["protagonist"] = "young wizard" if language == "english" else "الساحر الشاب"
320
+ params["magic"] = "elemental magic" if language == "english" else "سحر العناصر"
321
+ params["place"] = "mystical kingdom" if language == "english" else "المملكة السحرية"
322
+
323
+ # Look for character mentions
324
+ protag_match = re.search(r"(about|عن)\s+(?:a|an|the)?\s*(\w+)", prompt_lower)
325
+ if protag_match:
326
+ params["protagonist"] = protag_match.group(2).capitalize()
327
+
328
+ return template_type, params
329
+
330
+ def _customize_template(self, template: str, params: Dict[str, str]) -> str:
331
+ """Fill template with parameters"""
332
+ for key, value in params.items():
333
+ placeholder = "{" + key + "}"
334
+ template = template.replace(placeholder, value)
335
+ return template
336
+
337
+ def generate_story(
338
+ self,
339
+ prompt: str,
340
+ language: str,
341
+ template_name: Optional[str] = None,
342
+ parameters: Optional[Dict[str, str]] = None
343
+ ) -> Tuple[str, Dict[str, float]]:
344
+ """Generate a story in the specified language with sentiment analysis"""
345
+ if not self.initialized:
346
+ error_msg = "Story generator was not properly initialized."
347
+ return error_msg, {"neutral": 1.0}
348
+
349
+ try:
350
+ # Normalize language
351
+ language = language.lower()
352
+ if language not in ["english", "arabic"]:
353
+ language = "english"
354
+
355
+ # Detect input language and translate prompt if needed
356
+ input_language = self.detect_language(prompt)
357
+ if input_language != language:
358
+ prompt = self.translate(prompt, input_language, language)
359
+
360
+ # If template and parameters are provided, use them
361
+ if template_name and parameters:
362
+ if template_name in self.story_templates[language]:
363
+ # Get random template of the specified type
364
+ template_options = self.story_templates[language][template_name]
365
+ template = random.choice(template_options)
366
+ story = self._customize_template(template, parameters)
367
+ else:
368
+ # Fallback to adventure if template not found
369
+ template = random.choice(self.story_templates[language]["adventure"])
370
+ story = self._customize_template(template, parameters)
371
+ else:
372
+ # Determine best template and parameters from prompt
373
+ template_type, params = self._select_template(prompt, language)
374
+
375
+ # Get random template of determined type
376
+ template_options = self.story_templates[language][template_type]
377
+ template = random.choice(template_options)
378
+
379
+ # Fill template with extracted/default parameters
380
+ story = self._customize_template(template, params)
381
+
382
+ # Safety check
383
+ if not self.is_safe_content(story):
384
+ # Fallback to safe template
385
+ template = random.choice(self.story_templates[language]["adventure"])
386
+ default_params = {
387
+ "hero": "brave explorer" if language == "english" else "المستكشف الشجاع",
388
+ "object": "ancient artifact" if language == "english" else "القطعة الأثرية القديمة",
389
+ "location": "mysterious ruins" if language == "english" else "الآثار الغامضة"
390
+ }
391
+ story = self._customize_template(template, default_params)
392
+
393
+ # Analyze sentiment
394
+ emotions = self.analyze_sentiment(story, language)
395
+
396
+ return story, emotions
397
+
398
+ except Exception as e:
399
+ logger.error(f"Story generation error: {str(e)}")
400
+ # Return fallback story
401
+ if language == "arabic":
402
+ return "كان يا ما كان، في قديم الزمان، قصة لم تكتمل بعد...", {"neutral": 1.0}
403
+ else:
404
+ return "Once upon a time, there was a story waiting to be told...", {"neutral": 1.0}
405
+
406
+ class StoryManager:
407
+ """Manages story templates and generation"""
408
+
409
+ def __init__(self):
410
+ self.templates = {
411
+ "adventure": StoryTemplate(
412
+ "Adventure Quest",
413
+ "Tell a story about {hero} who must find {object} in {location}",
414
+ "احكِ قصة عن {hero} الذي يجب أن يجد {object} في {location}",
415
+ ["hero", "object", "location"]
416
+ ),
417
+ "friendship": StoryTemplate(
418
+ "Friendship Tale",
419
+ "Write about a friendship between {character1} and {character2}",
420
+ "اكتب عن صداقة بين {character1} و {character2}",
421
+ ["character1", "character2"]
422
+ ),
423
+ "fantasy": StoryTemplate(
424
+ "Fantasy World",
425
+ "Create a tale where {protagonist} discovers they have the power of {magic} and must save {place}",
426
+ "اخلق قصة حيث يكتشف {protagonist} أن لديه قوة {magic} ويجب عليه إنقاذ {place}",
427
+ ["protagonist", "magic", "place"]
428
+ )
429
+ }
430
+
431
+ class EmotionAnalyzer:
432
+ """Legacy emotion analyzer class (kept for backward compatibility)"""
433
+
434
+ def __init__(self):
435
+ try:
436
+ self.classifier = pipeline(
437
+ "sentiment-analysis",
438
+ model="distilbert-base-uncased-finetuned-sst-2-english",
439
+ device="cpu"
440
+ )
441
+ logger.info("Initialized emotion analyzer successfully")
442
+ except Exception as e:
443
+ logger.error(f"Failed to initialize emotion analyzer: {str(e)}")
444
+ self.classifier = None
445
+
446
+ def analyze_emotions(self, text: str) -> Dict[str, float]:
447
+ try:
448
+ if not self.classifier:
449
+ return {"neutral": 1.0}
450
+
451
+ result = self.classifier(text[:512])[0]
452
+ sentiment_score = float(result['score'])
453
+
454
+ if result['label'] == 'POSITIVE':
455
+ emotions = {
456
+ "positive": sentiment_score,
457
+ "negative": 1 - sentiment_score
458
+ }
459
+ else:
460
+ emotions = {
461
+ "negative": sentiment_score,
462
+ "positive": 1 - sentiment_score
463
+ }
464
+
465
+ return emotions
466
+
467
+ except Exception as e:
468
+ logger.error(f"Emotion analysis error: {str(e)}")
469
+ return {"neutral": 1.0}
470
+
471
+ class AIStoryteller:
472
+ """Main class for story generation and management"""
473
+
474
+ def __init__(self):
475
+ self.story_manager = StoryManager()
476
+ self.emotion_analyzer = EmotionAnalyzer()
477
+ self.setup_models()
478
+
479
+ def setup_models(self):
480
+ """Initialize all required models and pipelines (on CPU by default)"""
481
+ try:
482
+ # Initialize the new multilingual story generator
483
+ self.story_generator = MultilingualStoryGenerator()
484
+ logger.info("Loaded multilingual story generator")
485
+
486
+ # Translation Model (Arabic->English)
487
+ translator_model_name = "Helsinki-NLP/opus-mt-ar-en"
488
+ self.translator_tokenizer = MarianTokenizer.from_pretrained(translator_model_name)
489
+ self.translator_model = MarianMTModel.from_pretrained(translator_model_name).to("cpu")
490
+ logger.info("Loaded translation model")
491
+
492
+ # Image Generation Model (Stable Diffusion), remains on CPU until needed
493
+ self.pipe = StableDiffusionPipeline.from_pretrained(
494
+ "runwayml/stable-diffusion-v1-5",
495
+ torch_dtype=torch.float16
496
+ )
497
+ logger.info("Loaded Stable Diffusion pipeline (CPU -> GPU at runtime)")
498
+
499
+ except Exception as e:
500
+ logger.error(f"Error during model initialization: {str(e)}")
501
+ raise
502
+
503
+ def get_story_template(self, template_name: str, language: str) -> str:
504
+ """Get template in specified language"""
505
+ template = self.story_manager.templates.get(template_name)
506
+ if not template:
507
+ raise ValueError(f"Template {template_name} not found")
508
+ return template.template_en if language.lower() == "english" else template.template_ar
509
+
510
+ def fill_template(self, template_name: str, language: str, parameters: Dict[str, str]) -> str:
511
+ """Fill template with provided parameters"""
512
+ template = self.get_story_template(template_name, language)
513
+ return template.format(**parameters)
514
+
515
+ def translate_ar_to_en(self, text: str) -> str:
516
+ """Translate Arabic text to English"""
517
+ try:
518
+ tokenized = self.translator_tokenizer([text], return_tensors="pt", truncation=True)
519
+ translation = self.translator_model.generate(**tokenized)
520
+ return self.translator_tokenizer.decode(translation[0], skip_special_tokens=True)
521
+ except Exception as e:
522
+ logger.error(f"Translation error: {str(e)}")
523
+ raise ValueError(f"Failed to translate text: {str(e)}")
524
+
525
+ def detect_language(self, text: str) -> str:
526
+ """A simple heuristic to detect if text is mostly Arabic or English"""
527
+ arabic_char_count = sum(1 for char in text if '\u0600' <= char <= '\u06FF')
528
+ return "Arabic" if arabic_char_count > len(text) * 0.3 else "English"
529
+
530
+ def translate_if_needed(self, text: str, from_lang: str, to_lang: str) -> str:
531
+ """Legacy translate method (kept for backward compatibility)"""
532
+ if from_lang == to_lang:
533
+ return text
534
+
535
+ if from_lang == "Arabic" and to_lang == "English":
536
+ return self.translate_ar_to_en(text)
537
+ elif from_lang == "English" and to_lang == "Arabic":
538
+ logger.warning("English->Arabic translation not implemented.")
539
+ return text
540
+ return text
541
+
542
+ def generate_text(
543
+ self,
544
+ prompt: str,
545
+ language: str,
546
+ template_name: Optional[str] = None,
547
+ parameters: Optional[Dict[str, str]] = None
548
+ ) -> str:
549
+ """Generate text with optional template usage (CPU-based generation)"""
550
+ try:
551
+ # Convert language format to standard format for story generator
552
+ lang = language.lower()
553
+
554
+ # Generate story with sentiment analysis
555
+ story, emotions = self.story_generator.generate_story(
556
+ prompt,
557
+ lang,
558
+ template_name,
559
+ parameters
560
+ )
561
+
562
+ # Format emotions for display
563
+ if lang == "arabic":
564
+ emotion_summary = "\n\nالنبرة العاطفية:\n"
565
+ for emotion, score in emotions.items():
566
+ # Translate emotion names to Arabic
567
+ emotion_ar = {
568
+ "positive": "إيجابي",
569
+ "negative": "سلبي",
570
+ "neutral": "محايد"
571
+ }.get(emotion, emotion)
572
+ emotion_summary += f"- {emotion_ar}: {score:.1%}\n"
573
+ else:
574
+ emotion_summary = "\n\nEmotional tones:\n"
575
+ for emotion, score in emotions.items():
576
+ emotion_summary += f"- {emotion}: {score:.1%}\n"
577
+
578
+ return story + emotion_summary
579
+
580
+ except Exception as e:
581
+ logger.error(f"Text generation error: {str(e)}")
582
+ if language.lower() == "arabic":
583
+ return f"عذراً، حدث خطأ أثناء إنشاء القصة: {str(e)}"
584
+ else:
585
+ return f"Error generating text: {str(e)}"
586
+
587
+ @spaces.GPU
588
+ def generate_story_and_scenes(
589
+ self,
590
+ prompt: str,
591
+ language: str,
592
+ template_name: Optional[str] = None,
593
+ parameters: Optional[Dict[str, str]] = None,
594
+ num_scenes: int = 3,
595
+ style: str = "realistic"
596
+ ) -> Tuple[str, List[Image.Image]]:
597
+ """
598
+ Single top-level function decorated with @spaces.GPU.
599
+ 1) Generate story on CPU.
600
+ 2) Move Stable Diffusion to GPU to generate multiple scenes.
601
+ 3) Move pipeline back to CPU at the end.
602
+ """
603
+ try:
604
+ # Step 1: Generate story (CPU)
605
+ story = self.generate_text(prompt, language, template_name, parameters)
606
+ if "Error generating text" in story or "عذراً، حدث خطأ" in story:
607
+ return story, []
608
+
609
+ # Prepare story for image generation
610
+ if language.lower() == "arabic":
611
+ story_for_image = self.translate_ar_to_en(story)
612
+ else:
613
+ story_for_image = story
614
+
615
+ # Step 2: Move pipe to GPU
616
+ self.pipe = self.pipe.to("cuda")
617
+
618
+ segments = [seg.strip() for seg in story_for_image.split('.') if seg.strip()]
619
+ if len(segments) < num_scenes:
620
+ selected_segments = segments
621
+ else:
622
+ step = max(1, len(segments) // num_scenes)
623
+ selected_segments = segments[::step][:num_scenes]
624
+
625
+ style_prompts = {
626
+ "realistic": "photorealistic, highly detailed, 4k",
627
+ "anime": "anime style, studio ghibli, detailed",
628
+ "fantasy": "fantasy art, magical, detailed illustration"
629
+ }
630
+
631
+ scenes = []
632
+ for segment in selected_segments:
633
+ prompt_for_image = f"{segment}, {style_prompts.get(style, '')}"
634
+ with torch.no_grad():
635
+ image = self.pipe(
636
+ prompt_for_image,
637
+ num_inference_steps=30,
638
+ guidance_scale=7.5
639
+ ).images[0]
640
+ if image:
641
+ scenes.append(image)
642
+
643
+ # Step 3: Move back to CPU
644
+ self.pipe = self.pipe.to("cpu")
645
+ torch.cuda.empty_cache()
646
+ gc.collect()
647
+
648
+ return story, scenes
649
+
650
+ except Exception as e:
651
+ logger.error(f"Story and scene generation error: {str(e)}")
652
+ if hasattr(self, 'pipe'):
653
+ self.pipe = self.pipe.to("cpu")
654
+ torch.cuda.empty_cache()
655
+ return f"Error generating story and scenes: {str(e)}", []
656
+
657
+ def build_gradio_interface() -> gr.Blocks:
658
+ """Build and return the Gradio interface"""
659
+ storyteller = AIStoryteller()
660
+
661
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
662
+ # Header
663
+ with gr.Row(variant="panel"):
664
+ gr.Markdown(
665
+ """
666
+ # 📚 AI Bilingual Storyteller & Illustrator
667
+
668
+ Create engaging stories in English or Arabic, with optional illustrations and emotional analysis.
669
+
670
+ - 📝 **Basic Story**: Simple text generation
671
+ - 🎯 **Template Story**: Guided story creation
672
+ - 🎨 **Visual Story**: Story with scene illustrations
673
+ """
674
+ )
675
+
676
+ # Tabs
677
+ with gr.Tabs() as tabs:
678
+ # Basic Story
679
+ with gr.Tab("📝 Basic Story"):
680
+ gr.Markdown("Enter a prompt, choose a language, and get a story with emotion analysis.")
681
+
682
+ with gr.Row():
683
+ prompt_text = gr.Textbox(
684
+ label="Story Prompt",
685
+ placeholder="Once upon a time... / في يوم من الأيام...",
686
+ lines=3
687
+ )
688
+ language_choice = gr.Radio(
689
+ choices=["English", "Arabic"],
690
+ value="English",
691
+ label="Language"
692
+ )
693
+
694
+ generate_button = gr.Button("✨ Generate Story")
695
+ output_story = gr.Textbox(
696
+ label="Your Generated Story",
697
+ lines=8,
698
+ show_copy_button=True
699
+ )
700
+
701
+ generate_button.click(
702
+ fn=storyteller.generate_text,
703
+ inputs=[prompt_text, language_choice],
704
+ outputs=output_story
705
+ )
706
+
707
+ # Template Story
708
+ with gr.Tab("🎯 Template Story"):
709
+ gr.Markdown("Choose a template and fill parameters for a structured story.")
710
+
711
+ with gr.Row():
712
+ template_choice = gr.Dropdown(
713
+ choices=list(storyteller.story_manager.templates.keys()),
714
+ label="Story Template",
715
+ value="adventure"
716
+ )
717
+ template_language = gr.Radio(
718
+ choices=["English", "Arabic"],
719
+ value="English",
720
+ label="Language"
721
+ )
722
+
723
+ template_params = gr.JSON(
724
+ label="Template Parameters",
725
+ value={"hero": "brave knight", "object": "magical sword", "location": "enchanted forest"}
726
+ )
727
+
728
+ template_button = gr.Button("🎮 Generate from Template")
729
+ template_output = gr.Textbox(
730
+ label="Your Generated Story",
731
+ lines=8,
732
+ show_copy_button=True
733
+ )
734
+
735
+ template_button.click(
736
+ fn=lambda t, l, p: storyteller.generate_text(
737
+ "", l, template_name=t, parameters=p
738
+ ),
739
+ inputs=[template_choice, template_language, template_params],
740
+ outputs=template_output
741
+ )
742
+
743
+ # Visual Story
744
+ with gr.Tab("🎨 Visual Story"):
745
+ gr.Markdown("Create a story with multiple illustrated scenes.")
746
+
747
+ with gr.Row():
748
+ scene_prompt = gr.Textbox(
749
+ label="Story Prompt",
750
+ placeholder="Describe your story scene...",
751
+ lines=3
752
+ )
753
+ scene_language = gr.Radio(
754
+ choices=["English", "Arabic"],
755
+ value="English",
756
+ label="Language"
757
+ )
758
+
759
+ with gr.Row():
760
+ num_scenes = gr.Slider(
761
+ minimum=1,
762
+ maximum=5,
763
+ value=3,
764
+ step=1,
765
+ label="Number of Scenes"
766
+ )
767
+ art_style = gr.Radio(
768
+ choices=["realistic", "anime", "fantasy"],
769
+ value="realistic",
770
+ label="Art Style",
771
+ interactive=True
772
+ )
773
+
774
+ scene_button = gr.Button("🎭 Generate Story & Scenes")
775
+ scene_story = gr.Textbox(
776
+ label="Your Generated Story",
777
+ lines=8,
778
+ show_copy_button=True
779
+ )
780
+ scene_gallery = gr.Gallery(
781
+ label="Story Scenes",
782
+ columns=3,
783
+ height="auto"
784
+ )
785
+
786
+ scene_button.click(
787
+ fn=storyteller.generate_story_and_scenes,
788
+ inputs=[
789
+ scene_prompt,
790
+ scene_language,
791
+ gr.Textbox(value=None, visible=False),
792
+ gr.Textbox(value=None, visible=False),
793
+ num_scenes,
794
+ art_style
795
+ ],
796
+ outputs=[scene_story, scene_gallery]
797
+ )
798
+
799
+ # Footer with Examples
800
+ with gr.Row(variant="panel"):
801
+ gr.Markdown("""
802
+ ### Example Prompts
803
+
804
+ **English:**
805
+ - "Tell a story about a young wizard discovering a magical garden"
806
+ - "Create an adventure about a brave explorer in ancient ruins"
807
+ - "Write about a friendship between a cat and a robot"
808
+
809
+ **Arabic:**
810
+ - "احكِ قصة عن ساحر صغير يكتشف حديقة سحرية"
811
+ - "اكتب مغامرة عن مستكشف شجاع في آثار قديمة"
812
+ - "اكتب عن صداقة بين قطة وروبوت"
813
+ """)
814
+
815
+ return demo
816
+
817
+ if __name__ == "__main__":
818
+ try:
819
+ demo = build_gradio_interface()
820
+ demo.launch()
821
+ except Exception as e:
822
+ logger.error(f"Application startup error: {str(e)}")
823
+ raise