walker11 commited on
Commit
7d52223
·
verified ·
1 Parent(s): e40f5d9

Upload 11 files

Browse files
Files changed (6) hide show
  1. Dockerfile +1 -3
  2. ai_service.py +115 -41
  3. main.py +1 -1
  4. models.py +8 -0
  5. prompts.py +124 -42
  6. requirements.txt +8 -8
Dockerfile CHANGED
@@ -16,7 +16,7 @@ RUN pip install --no-cache-dir -r requirements.txt
16
  COPY . .
17
 
18
  # Create directories for audio files
19
- RUN mkdir -p /tmp/audio_files && chmod 777 /tmp/audio_files
20
 
21
  # Expose port
22
  EXPOSE 7860
@@ -25,8 +25,6 @@ EXPOSE 7860
25
  ENV BACKEND_HOST=0.0.0.0
26
  ENV BACKEND_PORT=7860
27
  ENV BASE_URL=https://${SPACE_ID}.hf.space
28
- ENV AUDIO_STORAGE_PATH=/tmp/audio_files
29
-
30
 
31
  # Run the application
32
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
16
  COPY . .
17
 
18
  # Create directories for audio files
19
+ RUN mkdir -p audio_files
20
 
21
  # Expose port
22
  EXPOSE 7860
 
25
  ENV BACKEND_HOST=0.0.0.0
26
  ENV BACKEND_PORT=7860
27
  ENV BASE_URL=https://${SPACE_ID}.hf.space
 
 
28
 
29
  # Run the application
30
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
ai_service.py CHANGED
@@ -3,17 +3,19 @@ import json
3
  import re
4
  import time
5
  import asyncio
 
6
  from typing import Dict, List, Tuple, Optional
7
  import httpx
8
  from dotenv import load_dotenv
9
 
10
- from models import StoryConfig, StoryParagraph, StoryChoice, StoryResponse
11
  from prompts import (
12
  get_system_prompt,
13
  create_story_init_prompt,
14
  create_continuation_prompt,
15
  create_title_prompt,
16
- get_story_length_instructions
 
17
  )
18
 
19
  # تحميل المتغيرات البيئية
@@ -31,21 +33,21 @@ stories_metadata = {}
31
 
32
  async def generate_response(messages: List[Dict], retries=3, backoff_factor=1.5) -> str:
33
  """
34
- استدعاء DeepSeek API وتوليد استجابة بناءً على سلسلة الرسائل مع محاولة إعادة المحاولة في حالة الفشل
35
 
36
  Args:
37
- messages: قائمة برسائل المحادثة
38
- retries: عدد محاولات إعادة المحاولة في حالة فشل الاتصال (افتراضي: 3)
39
- backoff_factor: معامل التأخير للمحاولات المتتالية (افتراضي: 1.5)
40
 
41
  Returns:
42
- str: محتوى الاستجابة من API
43
 
44
  Raises:
45
- Exception: في حالة فشل جميع المحاولات
46
  """
47
  if not DEEPSEEK_API_KEY:
48
- raise Exception("مفتاح API غير متوفر. يرجى التحقق من إعداد المتغيرات البيئية.")
49
 
50
  headers = {
51
  "Content-Type": "application/json",
@@ -53,15 +55,15 @@ async def generate_response(messages: List[Dict], retries=3, backoff_factor=1.5)
53
  }
54
 
55
  payload = {
56
- "model": "deepseek-chat", # استبدل باسم النموذج المناسب من DeepSeek API
57
  "messages": messages,
58
- "temperature": 0.7,
59
- "max_tokens": 1500
60
  }
61
 
62
  last_exception = None
63
 
64
- # محاولات إعادة الاتصال في حالة فشل API
65
  for attempt in range(retries):
66
  try:
67
  async with httpx.AsyncClient() as client:
@@ -69,54 +71,54 @@ async def generate_response(messages: List[Dict], retries=3, backoff_factor=1.5)
69
  DEEPSEEK_API_URL,
70
  headers=headers,
71
  json=payload,
72
- timeout=60.0
73
  )
74
 
75
  if response.status_code == 429: # Rate Limit
76
- # انتظار فترة قبل المحاولة مرة أخرى
77
  wait_time = backoff_factor * (2 ** attempt)
78
- print(f"تجاوز حد معدل الطلبات، انتظار {wait_time} ثانية قبل المحاولة مرة أخرى.")
79
  await asyncio.sleep(wait_time)
80
  continue
81
 
82
  if response.status_code != 200:
83
- error_msg = f"فشل طلب DeepSeek API: {response.status_code} - {response.text}"
84
  print(error_msg)
85
  last_exception = Exception(error_msg)
86
 
87
- # انتظار فترة قبل المحاولة مرة أخرى
88
  wait_time = backoff_factor * (2 ** attempt)
89
  await asyncio.sleep(wait_time)
90
  continue
91
 
92
  result = response.json()
93
  if "choices" not in result or not result["choices"]:
94
- raise Exception("تنسيق استجابة DeepSeek API غير صالح")
95
 
96
  return result["choices"][0]["message"]["content"]
97
 
98
  except httpx.HTTPError as e:
99
- error_msg = f"خطأ في اتصال HTTP: {str(e)}"
100
  print(error_msg)
101
  last_exception = Exception(error_msg)
102
 
103
- # انتظار فترة قبل المحاولة مرة أخرى
104
  wait_time = backoff_factor * (2 ** attempt)
105
  await asyncio.sleep(wait_time)
106
  continue
107
 
108
  except Exception as e:
109
- error_msg = f"خطأ غير متوقع: {str(e)}"
110
  print(error_msg)
111
  last_exception = Exception(error_msg)
112
 
113
- # انتظار فترة قبل المحاولة مرة أخرى
114
  wait_time = backoff_factor * (2 ** attempt)
115
  await asyncio.sleep(wait_time)
116
  continue
117
 
118
- # إذا وصلنا إلى هنا، فقد فشلت جميع المحاولات
119
- raise last_exception or Exception("فشل الاتصال بـ DeepSeek API بعد عدة محاولات")
120
 
121
 
122
  def parse_paragraph_and_choices(response_text: str) -> Tuple[str, Optional[List[StoryChoice]]]:
@@ -163,7 +165,6 @@ async def initialize_story(config: StoryConfig) -> StoryResponse:
163
  """
164
  بدء قصة جديدة باستخدام DeepSeek API
165
  """
166
- import uuid
167
  story_id = str(uuid.uuid4())
168
 
169
  # إنشاء البرومبت الأولي
@@ -319,33 +320,41 @@ async def continue_story_with_text(story_id: str, custom_text: str) -> StoryResp
319
 
320
  # Create a prompt for continuing with custom text
321
  custom_prompt = f"""
322
- لقد وصلنا إلى هذه النقطة في القصة:
323
 
324
  {story_context_text}
325
 
326
- المستخدم اختار أن يكتب رداً مخصصاً بدلاً من اختيار أحد الخيارات المقدمة. الرد المخصص للمستخدم هو:
327
 
328
  "{custom_text}"
329
 
330
- بناءً على هذا المدخل من المستخدم، استمر في القصة واكتب فقرة جديدة تأخذ بعين الاعتبار ما كتبه المستخدم.
331
- ثم قدم 3 خيارات جديدة للمستخدم ليختار منها للاستمرار في القصة.
332
 
333
- تذكر أن القصة الآن في:
334
- - الفقرة رقم: {current_paragraph} من {max_paragraphs}
335
- - إذا كانت هذه الفقرة الأخيرة أو قبل الأخيرة، قم بختم القصة بشكل مناسب.
336
 
337
- يجب أن يكون تنسيق ردك كما يلي:
 
 
 
 
 
338
 
339
- الفقرة: [نص الفقرة الجديدة من القصة]
 
 
 
340
 
341
  الخيارات:
342
- 1. [الخيار الأول]
343
- 2. [الخيار الثاني]
344
- 3. [الخيار الثالث]
345
 
346
- إذا كانت هذه الفقرة الأخيرة، أضف عنواناً للقصة:
347
 
348
- العنوان: [عنوان مناسب للقصة كاملة]
 
349
  """
350
 
351
  print(f"Custom prompt created, length: {len(custom_prompt)}")
@@ -505,4 +514,69 @@ async def edit_story(story_id: str, edit_instructions: str) -> dict:
505
  return {
506
  "paragraphs": edited_paragraphs,
507
  "title": new_title or stories_metadata[story_id].get("title")
508
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import re
4
  import time
5
  import asyncio
6
+ import uuid
7
  from typing import Dict, List, Tuple, Optional
8
  import httpx
9
  from dotenv import load_dotenv
10
 
11
+ from models import StoryConfig, StoryParagraph, StoryChoice, StoryResponse, CompleteStoryResponse
12
  from prompts import (
13
  get_system_prompt,
14
  create_story_init_prompt,
15
  create_continuation_prompt,
16
  create_title_prompt,
17
+ get_story_length_instructions,
18
+ create_complete_story_prompt
19
  )
20
 
21
  # تحميل المتغيرات البيئية
 
33
 
34
  async def generate_response(messages: List[Dict], retries=3, backoff_factor=1.5) -> str:
35
  """
36
+ Call DeepSeek API and generate a response based on message chain with retry attempts in case of failure
37
 
38
  Args:
39
+ messages: List of conversation messages
40
+ retries: Number of retry attempts in case of connection failure (default: 3)
41
+ backoff_factor: Delay factor for consecutive attempts (default: 1.5)
42
 
43
  Returns:
44
+ str: Response content from API
45
 
46
  Raises:
47
+ Exception: In case all attempts fail
48
  """
49
  if not DEEPSEEK_API_KEY:
50
+ raise Exception("API key not available. Please check the environment variables setup.")
51
 
52
  headers = {
53
  "Content-Type": "application/json",
 
55
  }
56
 
57
  payload = {
58
+ "model": "deepseek-chat", # Replace with the appropriate model name from DeepSeek API
59
  "messages": messages,
60
+ "temperature": 0.75, # Increased for better creativity
61
+ "max_tokens": 4000 # Increased to allow for longer paragraphs
62
  }
63
 
64
  last_exception = None
65
 
66
+ # Retry connection attempts in case of API failure
67
  for attempt in range(retries):
68
  try:
69
  async with httpx.AsyncClient() as client:
 
71
  DEEPSEEK_API_URL,
72
  headers=headers,
73
  json=payload,
74
+ timeout=120.0 # Increased timeout for longer responses
75
  )
76
 
77
  if response.status_code == 429: # Rate Limit
78
+ # Wait before trying again
79
  wait_time = backoff_factor * (2 ** attempt)
80
+ print(f"Rate limit exceeded, waiting {wait_time} seconds before retrying.")
81
  await asyncio.sleep(wait_time)
82
  continue
83
 
84
  if response.status_code != 200:
85
+ error_msg = f"DeepSeek API request failed: {response.status_code} - {response.text}"
86
  print(error_msg)
87
  last_exception = Exception(error_msg)
88
 
89
+ # Wait before trying again
90
  wait_time = backoff_factor * (2 ** attempt)
91
  await asyncio.sleep(wait_time)
92
  continue
93
 
94
  result = response.json()
95
  if "choices" not in result or not result["choices"]:
96
+ raise Exception("Invalid DeepSeek API response format")
97
 
98
  return result["choices"][0]["message"]["content"]
99
 
100
  except httpx.HTTPError as e:
101
+ error_msg = f"HTTP connection error: {str(e)}"
102
  print(error_msg)
103
  last_exception = Exception(error_msg)
104
 
105
+ # Wait before trying again
106
  wait_time = backoff_factor * (2 ** attempt)
107
  await asyncio.sleep(wait_time)
108
  continue
109
 
110
  except Exception as e:
111
+ error_msg = f"Unexpected error: {str(e)}"
112
  print(error_msg)
113
  last_exception = Exception(error_msg)
114
 
115
+ # Wait before trying again
116
  wait_time = backoff_factor * (2 ** attempt)
117
  await asyncio.sleep(wait_time)
118
  continue
119
 
120
+ # If we got here, all attempts have failed
121
+ raise last_exception or Exception("Failed to connect to DeepSeek API after multiple attempts")
122
 
123
 
124
  def parse_paragraph_and_choices(response_text: str) -> Tuple[str, Optional[List[StoryChoice]]]:
 
165
  """
166
  بدء قصة جديدة باستخدام DeepSeek API
167
  """
 
168
  story_id = str(uuid.uuid4())
169
 
170
  # إنشاء البرومبت الأولي
 
320
 
321
  # Create a prompt for continuing with custom text
322
  custom_prompt = f"""
323
+ We have reached this point in the story:
324
 
325
  {story_context_text}
326
 
327
+ The user chose to write a custom response instead of selecting one of the provided options. The user's custom input is:
328
 
329
  "{custom_text}"
330
 
331
+ Based on this user input, continue the story and write a new paragraph that takes into account what the user wrote.
 
332
 
333
+ Remember that the story is currently at:
334
+ - Paragraph number: {current_paragraph} of {max_paragraphs}
335
+ - If this is the final or penultimate paragraph, conclude the story appropriately.
336
 
337
+ VERY IMPORTANT REQUIREMENTS:
338
+ 1. The paragraph MUST be 6-8 lines long with rich details and descriptive content.
339
+ 2. Include detailed descriptions of characters, settings, emotions, and events.
340
+ 3. Make sure your paragraph is substantial and richly detailed (minimum 6-8 lines).
341
+ 4. The entire response MUST be in Arabic language only.
342
+ 5. Then present 3 new options for the user to choose from to continue the story.
343
 
344
+ Your response format must be as follows:
345
+
346
+ الفقرة:
347
+ [Write the new paragraph of the story here in Arabic, ensuring it is 6-8 lines long with rich details]
348
 
349
  الخيارات:
350
+ 1. [First option - character name + action verb in Arabic, 3-5 words total]
351
+ 2. [Second option - character name + action verb in Arabic, 3-5 words total]
352
+ 3. [Third option - character name + action verb in Arabic, 3-5 words total]
353
 
354
+ If this is the final paragraph, add a title for the story:
355
 
356
+ العنوان:
357
+ [Write an appropriate title for the complete story in Arabic]
358
  """
359
 
360
  print(f"Custom prompt created, length: {len(custom_prompt)}")
 
514
  return {
515
  "paragraphs": edited_paragraphs,
516
  "title": new_title or stories_metadata[story_id].get("title")
517
+ }
518
+
519
+ async def generate_complete_story(config: StoryConfig) -> CompleteStoryResponse:
520
+ """
521
+ إنشاء قصة كاملة دون تفاعل من المستخدم
522
+
523
+ Args:
524
+ config: تكوين القصة المطلوبة
525
+
526
+ Returns:
527
+ CompleteStoryResponse: استجابة تحتوي على القصة الكاملة والعنوان
528
+ """
529
+ story_id = str(uuid.uuid4())
530
+
531
+ # إنشاء سياق أولي للقصة
532
+ stories_context[story_id] = {
533
+ "paragraphs": [],
534
+ "current_paragraph": 0
535
+ }
536
+
537
+ # حفظ معلومات القصة
538
+ length_info = get_story_length_instructions(config.length)
539
+ stories_metadata[story_id] = {
540
+ "config": config.dict(),
541
+ "max_paragraphs": length_info["paragraphs"],
542
+ "title": None
543
+ }
544
+
545
+ # إنشاء البرومبت للقصة الكاملة
546
+ system_prompt = get_system_prompt()
547
+ user_prompt = create_complete_story_prompt(config)
548
+
549
+ messages = [
550
+ {"role": "system", "content": system_prompt},
551
+ {"role": "user", "content": user_prompt}
552
+ ]
553
+
554
+ # استدعاء DeepSeek API
555
+ response_text = await generate_response(messages)
556
+
557
+ # تحليل الاستجابة واستخراج الفقرات
558
+ paragraphs = [p.strip() for p in response_text.split("\n\n") if p.strip()]
559
+
560
+ # تحديث سياق القصة
561
+ stories_context[story_id]["paragraphs"] = paragraphs
562
+ stories_context[story_id]["current_paragraph"] = len(paragraphs)
563
+
564
+ # إنشاء عنوان للقصة
565
+ title_prompt = create_title_prompt("\n\n".join(paragraphs))
566
+ title_messages = [
567
+ {"role": "system", "content": system_prompt},
568
+ {"role": "user", "content": title_prompt}
569
+ ]
570
+
571
+ title_response = await generate_response(title_messages)
572
+ title = title_response.strip()
573
+
574
+ # تحديث عنوان القصة في البيانات الوصفية
575
+ stories_metadata[story_id]["title"] = title
576
+
577
+ # إنشاء كائن الاستجابة
578
+ return CompleteStoryResponse(
579
+ story_id=story_id,
580
+ paragraphs=paragraphs,
581
+ title=title
582
+ )
main.py CHANGED
@@ -62,7 +62,7 @@ app = FastAPI(
62
  # ======== Configure CORS ========
63
  app.add_middleware(
64
  CORSMiddleware,
65
- allow_origins=["*"], # In production, specify allowed origins instead of "*"
66
  allow_credentials=True,
67
  allow_methods=["*"],
68
  allow_headers=["*"],
 
62
  # ======== Configure CORS ========
63
  app.add_middleware(
64
  CORSMiddleware,
65
+ allow_origins=["*", "http://localhost:5000"], # Allow the local frontend
66
  allow_credentials=True,
67
  allow_methods=["*"],
68
  allow_headers=["*"],
models.py CHANGED
@@ -52,6 +52,7 @@ class StoryConfig(BaseModel):
52
  primary_type: StoryType = Field(..., description="النوع الأساسي للقصة")
53
  secondary_type: StoryType = Field(default=StoryType.NONE, description="النوع الثانوي للقصة")
54
  characters: List[Character] = Field(default=[], description="الشخصيات في القصة")
 
55
 
56
 
57
  class ChoiceRequest(BaseModel):
@@ -95,6 +96,13 @@ class StoryResponse(BaseModel):
95
  title: Optional[str] = Field(default=None, description="عنوان القصة (يتم إضافته عند اكتمال القصة)")
96
 
97
 
 
 
 
 
 
 
 
98
  class TTSResponse(BaseModel):
99
  """Response containing URL to audio file"""
100
  audio_url: str = Field(..., description="رابط ملف الصوت")
 
52
  primary_type: StoryType = Field(..., description="النوع الأساسي للقصة")
53
  secondary_type: StoryType = Field(default=StoryType.NONE, description="النوع الثانوي للقصة")
54
  characters: List[Character] = Field(default=[], description="الشخصيات في القصة")
55
+ interactive: bool = Field(default=True, description="هل القصة تفاعلية أم كاملة؟")
56
 
57
 
58
  class ChoiceRequest(BaseModel):
 
96
  title: Optional[str] = Field(default=None, description="عنوان القصة (يتم إضافته عند اكتمال القصة)")
97
 
98
 
99
+ class CompleteStoryResponse(BaseModel):
100
+ """Response containing a complete story generated without interaction"""
101
+ story_id: str = Field(..., description="معرف القصة")
102
+ paragraphs: List[str] = Field(..., description="فقرات القصة كاملة")
103
+ title: str = Field(..., description="عنوان القصة")
104
+
105
+
106
  class TTSResponse(BaseModel):
107
  """Response containing URL to audio file"""
108
  audio_url: str = Field(..., description="رابط ملف الصوت")
prompts.py CHANGED
@@ -4,7 +4,7 @@ from models import StoryLength, StoryType, Character, StoryConfig
4
 
5
  def get_system_prompt() -> str:
6
  """
7
- الحصول على برومبت النظام الأساسي الذي يحدد سلوك نموذج الذكاء الاصطناعي
8
  """
9
  return """
10
  You are a professional and creative Arabic story writer. Your task is to write original, engaging, and cohesive Arabic stories.
@@ -23,7 +23,7 @@ def get_system_prompt() -> str:
23
  10. Create a clear conflict that drives the story events and maintains reader interest.
24
 
25
  Each time you are asked to write a new paragraph of the story, you must:
26
- - Write a coherent and engaging narrative paragraph of 6-10 lines in Arabic.
27
  - Provide 3 distinctive and interesting options to develop the story's path.
28
 
29
  Important rules for options:
@@ -47,27 +47,39 @@ def get_system_prompt() -> str:
47
 
48
  def format_characters_info(characters: List[Character]) -> str:
49
  """
50
- تنسيق معلومات الشخصيات لتضمينها في البرومبت
51
  """
52
  if not characters:
53
- return "لا توجد شخصيات محددة، يمكنك إنشاء شخصيات مناسبة للقصة."
54
 
55
- characters_info = "معلومات الشخصيات:\n"
56
  for i, character in enumerate(characters, 1):
57
- gender_text = "ذكر" if character.gender.value == "ذكر" else "أنثى"
58
- characters_info += f"{i}. الشخصية: {character.name}، الجنس: {gender_text}، الوصف: {character.description}\n"
59
 
60
  return characters_info
61
 
62
 
63
  def get_story_length_instructions(length: StoryLength) -> Dict[str, Any]:
64
  """
65
- الحصول على تعليمات طول القصة وعدد الفقرات
66
  """
67
  length_mapping = {
68
- StoryLength.SHORT: {"paragraphs": 5, "description": "قصة قصيرة تتكون من 5 فقرات"},
69
- StoryLength.MEDIUM: {"paragraphs": 7, "description": "قصة متوسطة الطول تتكون من 7 فقرات"},
70
- StoryLength.LONG: {"paragraphs": 9, "description": "قصة طويلة تتكون من 9 فقرات"}
 
 
 
 
 
 
 
 
 
 
 
 
71
  }
72
 
73
  return length_mapping.get(length, length_mapping[StoryLength.MEDIUM])
@@ -75,17 +87,17 @@ def get_story_length_instructions(length: StoryLength) -> Dict[str, Any]:
75
 
76
  def get_story_type_description(primary_type: StoryType, secondary_type: StoryType) -> str:
77
  """
78
- الحصول على وصف نوع القصة
79
  """
80
  if secondary_type == StoryType.NONE:
81
- return f"قصة من نوع {primary_type.value}"
82
  else:
83
- return f"قصة تجمع بين نوعي {primary_type.value} و{secondary_type.value}"
84
 
85
 
86
  def create_story_init_prompt(config: StoryConfig) -> str:
87
  """
88
- إنشاء البرومبت الأولي لبدء القصة
89
  """
90
  length_info = get_story_length_instructions(config.length)
91
  story_type = get_story_type_description(config.primary_type, config.secondary_type)
@@ -97,19 +109,22 @@ def create_story_init_prompt(config: StoryConfig) -> str:
97
  {characters_info}
98
 
99
  Required from you:
100
- 1. Write the first paragraph of the story (4-6 lines) in Arabic.
101
- 2. Start the story with a strong and engaging beginning that captivates the reader from the first line.
102
- 3. Present the characters and setting (place and time) clearly and interestingly.
103
- 4. Establish a conflict, problem, or situation that drives the story events.
104
- 5. Present 3 short, exciting, and logical options for actions the protagonist can take.
105
- 6. Make the options very short (3-5 words only) and practical and direct.
106
- 7. ALWAYS include the character's name in each option before the action verb.
107
- 8. Format: "[Character name] + verb", like: "أحمد يتصل بالشرطة", "سارة تهرب من المكان".
 
 
 
108
 
109
  Present the first paragraph and options in the following format:
110
 
111
  الفقرة:
112
- [Write the first paragraph of the story here in Arabic]
113
 
114
  الخيارات:
115
  1. [Character name + action verb in Arabic, 3-5 words total]
@@ -122,7 +137,7 @@ def create_story_init_prompt(config: StoryConfig) -> str:
122
 
123
  def create_continuation_prompt(story_context: str, choice_id: int, choice_text: str, current_paragraph: int, max_paragraphs: int) -> str:
124
  """
125
- إنشاء برومبت لاستكمال القصة بناءً على اختيار المستخدم
126
  """
127
  is_final = current_paragraph >= max_paragraphs - 1
128
 
@@ -133,38 +148,42 @@ def create_continuation_prompt(story_context: str, choice_id: int, choice_text:
133
  The user chose path number {choice_id}: {choice_text}
134
 
135
  Required from you:
136
- 1. Continue writing the story with a new paragraph (4-6 lines) in Arabic that directly follows the choice made by the user.
137
- 2. Do not summarize the choice that the user made; instead, start directly with the events that result from this choice.
138
- 3. Add unexpected and exciting developments to engage the reader.
139
- 4. Maintain consistency in the story's characters and world.
 
 
 
140
  """
141
 
142
  if is_final:
143
  prompt += """
144
- 5. This is the final paragraph of the story, so end the story in a logical and satisfying way that closes all open paths.
145
- 6. Suggest an appropriate and deep title for the complete story.
 
146
 
147
  Present the final paragraph and title in the following format:
148
 
149
  الفقرة:
150
- [Write the final paragraph of the story here in Arabic]
151
 
152
  العنوان:
153
  [Write the suggested title for the story here in Arabic]
154
  """
155
  else:
156
  prompt += """
157
- 5. Present 3 short, logical, and practical options for continuing the story.
158
- 6. Make the options very short (3-5 words only) in Arabic.
159
- 7. ALWAYS include the character's name in each option before the action verb.
160
- 8. Format: "[Character name] + verb", like: "أحمد يتصل بالشرطة", "سارة تهرب من المكان".
161
- 9. Make it absolutely clear WHO is performing the action in each option.
162
- 10. Ensure each option will lead to a completely different path in the story.
163
 
164
  Present the next paragraph and options in the following format:
165
 
166
  الفقرة:
167
- [Write the next paragraph of the story here in Arabic]
168
 
169
  الخيارات:
170
  1. [Character name + action verb in Arabic, 3-5 words total]
@@ -177,7 +196,7 @@ def create_continuation_prompt(story_context: str, choice_id: int, choice_text:
177
 
178
  def create_title_prompt(complete_story: str) -> str:
179
  """
180
- إنشاء برومبت لتوليد عنوان مناسب للقصة المكتملة
181
  """
182
  return f"""
183
  Here is a complete story:
@@ -185,5 +204,68 @@ def create_title_prompt(complete_story: str) -> str:
185
  {complete_story}
186
 
187
  Suggest an appropriate and engaging title for this story that reflects its essence and content.
188
- Provide only the title without any additional explanation and without any story Characters names in Arabic and make it 3-5 words.
189
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  def get_system_prompt() -> str:
6
  """
7
+ Get the base system prompt that defines the AI model's behavior
8
  """
9
  return """
10
  You are a professional and creative Arabic story writer. Your task is to write original, engaging, and cohesive Arabic stories.
 
23
  10. Create a clear conflict that drives the story events and maintains reader interest.
24
 
25
  Each time you are asked to write a new paragraph of the story, you must:
26
+ - Write a coherent and engaging narrative paragraph of 6-8 lines in Arabic.
27
  - Provide 3 distinctive and interesting options to develop the story's path.
28
 
29
  Important rules for options:
 
47
 
48
  def format_characters_info(characters: List[Character]) -> str:
49
  """
50
+ Format character information to include in the prompt
51
  """
52
  if not characters:
53
+ return "No characters specified. You can create appropriate characters for the story."
54
 
55
+ characters_info = "Character information:\n"
56
  for i, character in enumerate(characters, 1):
57
+ gender_text = "Male" if character.gender.value == "ذكر" else "Female"
58
+ characters_info += f"{i}. Character: {character.name}, Gender: {gender_text}, Description: {character.description}\n"
59
 
60
  return characters_info
61
 
62
 
63
  def get_story_length_instructions(length: StoryLength) -> Dict[str, Any]:
64
  """
65
+ Get story length instructions and number of paragraphs
66
  """
67
  length_mapping = {
68
+ StoryLength.SHORT: {
69
+ "paragraphs": 5,
70
+ "description": "a short story consisting of 5 paragraphs",
71
+ "words": 1500
72
+ },
73
+ StoryLength.MEDIUM: {
74
+ "paragraphs": 7,
75
+ "description": "a medium-length story consisting of 7 paragraphs",
76
+ "words": 3000
77
+ },
78
+ StoryLength.LONG: {
79
+ "paragraphs": 9,
80
+ "description": "a long story consisting of 9 paragraphs",
81
+ "words": 5000
82
+ }
83
  }
84
 
85
  return length_mapping.get(length, length_mapping[StoryLength.MEDIUM])
 
87
 
88
  def get_story_type_description(primary_type: StoryType, secondary_type: StoryType) -> str:
89
  """
90
+ Get a description of the story type
91
  """
92
  if secondary_type == StoryType.NONE:
93
+ return f"a story of type {primary_type.value}"
94
  else:
95
+ return f"a story that combines {primary_type.value} and {secondary_type.value} genres"
96
 
97
 
98
  def create_story_init_prompt(config: StoryConfig) -> str:
99
  """
100
+ Create the initial prompt to start the story
101
  """
102
  length_info = get_story_length_instructions(config.length)
103
  story_type = get_story_type_description(config.primary_type, config.secondary_type)
 
109
  {characters_info}
110
 
111
  Required from you:
112
+ 1. Write the first paragraph of the story IN ARABIC LANGUAGE.
113
+ 2. VERY IMPORTANT: The paragraph MUST be 6-8 lines long with rich details and descriptive content.
114
+ 3. Start the story with a strong and engaging beginning that captivates the reader from the first line.
115
+ 4. Present the characters and setting (place and time) clearly and interestingly.
116
+ 5. Establish a conflict, problem, or situation that drives the story events.
117
+ 6. Include detailed descriptions of the environment, character emotions, and sensory details.
118
+ 7. Make sure your paragraph is substantial and richly detailed (minimum 6-8 lines).
119
+ 8. Present 3 short, exciting, and logical options for actions the protagonist can take.
120
+ 9. Make the options very short (3-5 words only) and practical and direct.
121
+ 10. ALWAYS include the character's name in each option before the action verb.
122
+ 11. Format: "[Character name] + verb", like: "أحمد يتصل بالشرطة", "سارة تهرب من المكان".
123
 
124
  Present the first paragraph and options in the following format:
125
 
126
  الفقرة:
127
+ [Write the first paragraph of the story here in Arabic, ensuring it is 6-8 lines long with rich details]
128
 
129
  الخيارات:
130
  1. [Character name + action verb in Arabic, 3-5 words total]
 
137
 
138
  def create_continuation_prompt(story_context: str, choice_id: int, choice_text: str, current_paragraph: int, max_paragraphs: int) -> str:
139
  """
140
+ Create a prompt to continue the story based on the user's choice
141
  """
142
  is_final = current_paragraph >= max_paragraphs - 1
143
 
 
148
  The user chose path number {choice_id}: {choice_text}
149
 
150
  Required from you:
151
+ 1. Continue writing the story with a new paragraph IN ARABIC LANGUAGE that directly follows the choice made by the user.
152
+ 2. VERY IMPORTANT: The paragraph MUST be 6-8 lines long with rich details and descriptive content.
153
+ 3. Include detailed descriptions of characters, settings, emotions, and events.
154
+ 4. Do not summarize the choice that the user made; instead, start directly with the events that result from this choice.
155
+ 5. Add unexpected and exciting developments to engage the reader.
156
+ 6. Maintain consistency in the story's characters and world.
157
+ 7. Make sure your paragraph is substantial and richly detailed (minimum 6-8 lines).
158
  """
159
 
160
  if is_final:
161
  prompt += """
162
+ 8. This is the final paragraph of the story, so end the story in a logical and satisfying way that closes all open paths.
163
+ 9. Make this final paragraph detailed and substantial (6-8 lines) with a proper conclusion.
164
+ 10. Suggest an appropriate and deep title for the complete story.
165
 
166
  Present the final paragraph and title in the following format:
167
 
168
  الفقرة:
169
+ [Write the final paragraph of the story here in Arabic, ensuring it is 6-8 lines long]
170
 
171
  العنوان:
172
  [Write the suggested title for the story here in Arabic]
173
  """
174
  else:
175
  prompt += """
176
+ 8. Present 3 short, logical, and practical options for continuing the story.
177
+ 9. Make the options very short (3-5 words only) in Arabic.
178
+ 10. ALWAYS include the character's name in each option before the action verb.
179
+ 11. Format: "[Character name] + verb", like: "أحمد يتصل بالشرطة", "سارة تهرب من المكان".
180
+ 12. Make it absolutely clear WHO is performing the action in each option.
181
+ 13. Ensure each option will lead to a completely different path in the story.
182
 
183
  Present the next paragraph and options in the following format:
184
 
185
  الفقرة:
186
+ [Write the next paragraph of the story here in Arabic, ensuring it is 6-8 lines long]
187
 
188
  الخيارات:
189
  1. [Character name + action verb in Arabic, 3-5 words total]
 
196
 
197
  def create_title_prompt(complete_story: str) -> str:
198
  """
199
+ Create a prompt to generate an appropriate title for the completed story
200
  """
201
  return f"""
202
  Here is a complete story:
 
204
  {complete_story}
205
 
206
  Suggest an appropriate and engaging title for this story that reflects its essence and content.
207
+ Provide only the title without any additional explanation and without any story Characters names in Arabic.
208
+ """
209
+
210
+
211
+ def create_complete_story_prompt(config):
212
+ """
213
+ Create prompt to generate a complete story without interaction
214
+
215
+ Args:
216
+ config: Story configuration
217
+
218
+ Returns:
219
+ str: Prompt used to generate the complete story
220
+ """
221
+ # Get story length instructions
222
+ length_instructions = get_story_length_instructions(config.length)
223
+ paragraph_count = length_instructions["paragraphs"]
224
+ word_count = length_instructions.get("words", 1500) # Default value if key doesn't exist
225
+
226
+ # Create character descriptions
227
+ characters_description = ""
228
+ for i, character in enumerate(config.characters, 1):
229
+ gender_text = "Male" if character.gender.value == "ذكر" else "Female"
230
+ characters_description += f"{i}. {character.name}: {gender_text}, {character.description}\n"
231
+
232
+ # Determine story types
233
+ primary_genre = config.primary_type.value
234
+ secondary_genre = config.secondary_type.value if config.secondary_type != StoryType.NONE else "None"
235
+
236
+ # Create the prompt
237
+ prompt = f"""
238
+ I want you to create a complete Arabic story in one go without user interaction.
239
+
240
+ Story Information:
241
+ - Primary Genre: {primary_genre}
242
+ - Secondary Genre: {secondary_genre}
243
+ - Required Paragraphs: approximately {paragraph_count} paragraphs
244
+ - Average Word Count: approximately {word_count} words
245
+
246
+ Characters:
247
+ {characters_description}
248
+
249
+ Additional Requirements:
250
+ 1. Create a complete and coherent story from beginning to end.
251
+ 2. Separate paragraphs with an empty line.
252
+ 3. VERY IMPORTANT: Each paragraph MUST be 6-8 lines long with rich details and descriptions.
253
+ 4. Avoid short paragraphs - ensure each paragraph is substantial (6-8 lines minimum).
254
+ 5. Make the story engaging with a developing plot.
255
+ 6. Use classical Arabic language with an appropriate balance of colloquial language in dialogues if appropriate.
256
+ 7. Consider the characteristics of the requested literary genres.
257
+ 8. Provide a clear and satisfying ending to the story.
258
+ 9. Do not use asterisk ** markers around any text in the story.
259
+ 10. Do not include headings like "Title" or "End" or "Introduction" within the story.
260
+ 11. Write the story directly without putting the title at the beginning of the text.
261
+ 12. Do not repeat the title at the beginning or end of the story.
262
+ 13. Each paragraph should contain detailed descriptions, character development, and events to ensure its length.
263
+
264
+ IMPORTANT:
265
+ - The entire story MUST be written in Arabic language only.
266
+ - Make sure EACH paragraph is substantial (6-8 lines) with rich content and descriptions.
267
+
268
+ Now, create the complete story:
269
+ """
270
+
271
+ return prompt.strip()
requirements.txt CHANGED
@@ -1,9 +1,9 @@
1
- fastapi==0.110.0
2
- uvicorn==0.28.0
3
- pydantic==2.6.1
4
- python-dotenv==1.0.1
5
- httpx==0.26.0
6
- gTTS==2.5.0
7
- python-multipart==0.0.9
8
- starlette==0.36.3
9
  aiofiles==23.2.1
 
1
+ fastapi==0.110.0
2
+ uvicorn==0.28.0
3
+ pydantic==2.6.1
4
+ python-dotenv==1.0.1
5
+ httpx==0.26.0
6
+ gTTS==2.5.0
7
+ python-multipart==0.0.9
8
+ starlette==0.36.3
9
  aiofiles==23.2.1