NazishHasan commited on
Commit
d724578
·
verified ·
1 Parent(s): d3ca8a5

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +599 -0
app.py ADDED
@@ -0,0 +1,599 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+
4
+ # Define base paths for assets - IMPORTANT: You need to create these folders and place your files here
5
+ # For Hugging Face Spaces, these paths are relative to your app.py file
6
+ ASSETS_DIR = "./assets"
7
+ IMAGE_DIR = os.path.join(ASSETS_DIR, "images")
8
+ AUDIO_DIR = os.path.join(ASSETS_DIR, "audio")
9
+
10
+ # Create asset directories if they don't exist (for local testing, Hugging Face handles this with git lfs)
11
+ # On Hugging Face Spaces, you'll upload these folders with your files.
12
+ os.makedirs(IMAGE_DIR, exist_ok=True)
13
+ os.makedirs(AUDIO_DIR, exist_ok=True)
14
+
15
+
16
+ # Placeholder data for Arabic Letters
17
+ # YOU MUST REPLACE 'placeholder_image.png' and 'placeholder_audio.mp3'
18
+ # with actual paths to your images and audio files within the 'assets/' directory.
19
+ # Ensure your audio files are clear and appropriate for young children.
20
+ arabic_letters_data = [
21
+ {
22
+ "letter": "أ",
23
+ "image": os.path.join(IMAGE_DIR, "alif.png"), # e.g., 'assets/images/alif.png'
24
+ "audio": os.path.join(AUDIO_DIR, "alif.mp3"), # e.g., 'assets/audio/alif.mp3' (pronunciation of 'أ')
25
+ "word_example": "أرنب (Arnab - Rabbit)",
26
+ "word_image": os.path.join(IMAGE_DIR, "arnab.png"), # e.g., 'assets/images/arnab.png'
27
+ "word_audio": os.path.join(AUDIO_DIR, "arnab.mp3"), # e.g., 'assets/audio/arnab.mp3' (pronunciation of 'أرنب')
28
+ },
29
+ {
30
+ "letter": "ب",
31
+ "image": os.path.join(IMAGE_DIR, "baa.png"), # e.g., 'assets/images/baa.png'
32
+ "audio": os.path.join(AUDIO_DIR, "baa.mp3"), # e.g., 'assets/audio/baa.mp3'
33
+ "word_example": "بيت (Bayt - House)",
34
+ "word_image": os.path.join(IMAGE_DIR, "bayt.png"), # e.g., 'assets/images/bayt.png'
35
+ "word_audio": os.path.join(AUDIO_DIR, "bayt.mp3"), # e.g., 'assets/audio/bayt.mp3'
36
+ },
37
+ {
38
+ "letter": "ت",
39
+ "image": os.path.join(IMAGE_DIR, "taa.png"), # e.g., 'assets/images/taa.png'
40
+ "audio": os.path.join(AUDIO_DIR, "taa.mp3"), # e.g., 'assets/audio/taa.mp3'
41
+ "word_example": "تفاح (Tuffah - Apple)",
42
+ "word_image": os.path.join(IMAGE_DIR, "tuffah.png"), # e.g., 'assets/images/tuffah.png'
43
+ "word_audio": os.path.join(AUDIO_DIR, "tuffah.mp3"), # e.g., 'assets/audio/tuffah.mp3'
44
+ },
45
+ # Add more Arabic letters here following the same dictionary structure.
46
+ # Make sure to create corresponding image and audio files for each entry!
47
+ ]
48
+
49
+ # Placeholder data for Arabic Stories
50
+ # YOU MUST REPLACE 'placeholder_story_image_pageX.png' and 'placeholder_story_audio.mp3'
51
+ # with actual paths to your story images and audio files within the 'assets/' directory.
52
+ arabic_stories_data = [
53
+ {
54
+ "title": "قصة الأرنب الصغير (The Little Rabbit's Story)",
55
+ "pages": [
56
+ {
57
+ "text": "في يوم من الأيام، خرج أرنب صغير يلعب في الحديقة الخضراء. كان الجو جميلاً والشمس مشرقة.",
58
+ "image": os.path.join(IMAGE_DIR, "story1_page1.png"), # e.g., 'assets/images/story1_page1.png'
59
+ "audio": os.path.join(AUDIO_DIR, "story1_page1.mp3"), # e.g., 'assets/audio/story1_page1.mp3'
60
+ },
61
+ {
62
+ "text": "فجأة، رأى الأرنب زهرة حمراء جميلة. 'ما أجمل هذه الزهرة!' قال الأرنب بسعادة.",
63
+ "image": os.path.join(IMAGE_DIR, "story1_page2.png"), # e.g., 'assets/images/story1_page2.png'
64
+ "audio": os.path.join(AUDIO_DIR, "story1_page2.mp3"), # e.g., 'assets/audio/story1_page2.mp3'
65
+ },
66
+ # Add more pages for this story. Each page needs text, an image, and corresponding audio.
67
+ ]
68
+ },
69
+ {
70
+ "title": "الأسد والفأر (The Lion and the Mouse)",
71
+ "pages": [
72
+ {
73
+ "text": "كان أسد نائماً تحت شجرة كبيرة في الغابة. جاء فأر صغير وركض فوق الأسد.",
74
+ "image": os.path.join(IMAGE_DIR, "story2_page1.png"), # e.g., 'assets/images/story2_page1.png'
75
+ "audio": os.path.join(AUDIO_DIR, "story2_page1.mp3"), # e.g., 'assets/audio/story2_page1.mp3'
76
+ },
77
+ {
78
+ "text": "استيقظ الأسد غاضباً وأمسك بالفأر. توسل الفأر إليه ليتركه، ووعده بمساعدته يوماً ما.",
79
+ "image": os.path.join(IMAGE_DIR, "story2_page2.png"), # e.g., 'assets/images/story2_page2.png'
80
+ "audio": os.path.join(AUDIO_DIR, "story2_page2.mp3"), # e.g., 'assets/audio/story2_page2.mp3'
81
+ },
82
+ # Add more pages for this story.
83
+ ]
84
+ }
85
+ ]
86
+
87
+ # Global state variables for navigation (do not modify directly outside functions)
88
+ current_letter_idx = 0
89
+ current_story_idx = 0
90
+ current_story_page_idx = 0
91
+
92
+ # --- Functions for Learning Arabic Letters Section ---
93
+
94
+ def get_current_letter_content():
95
+ """
96
+ Retrieves the current letter's data (letter, image, audio, word example, word image, word audio)
97
+ based on the global `current_letter_idx`.
98
+ """
99
+ if not arabic_letters_data:
100
+ # Handle case where no letter data is available
101
+ return "لا توجد حروف متاحة", None, None, "لا توجد كلمات متاحة", None, None
102
+
103
+ data = arabic_letters_data[current_letter_idx]
104
+
105
+ # Return (letter_display, letter_image, letter_audio, word_example_display, word_image, word_audio)
106
+ return (
107
+ data["letter"],
108
+ data["image"],
109
+ data["audio"], # This audio is for the letter sound itself
110
+ data["word_example"],
111
+ data["word_image"],
112
+ data["word_audio"] # This audio is for the word example sound
113
+ )
114
+
115
+ def next_letter_func():
116
+ """
117
+ Advances to the next Arabic letter in the `arabic_letters_data` list.
118
+ If at the end, it loops back to the first letter.
119
+ """
120
+ global current_letter_idx
121
+ if current_letter_idx < len(arabic_letters_data) - 1:
122
+ current_letter_idx += 1
123
+ else:
124
+ current_letter_idx = 0 # Loop back to the beginning
125
+ return get_current_letter_content()
126
+
127
+ def prev_letter_func():
128
+ """
129
+ Goes back to the previous Arabic letter in the `arabic_letters_data` list.
130
+ If at the beginning, it loops to the last letter.
131
+ """
132
+ global current_letter_idx
133
+ if current_letter_idx > 0:
134
+ current_letter_idx -= 1
135
+ else:
136
+ current_letter_idx = len(arabic_letters_data) - 1 # Loop to the end
137
+ return get_current_letter_content()
138
+
139
+ def play_audio(audio_path):
140
+ """
141
+ Plays an audio file. This function is called by Gradio's Audio component.
142
+ It checks if the file exists before returning the path.
143
+ """
144
+ if os.path.exists(audio_path):
145
+ return audio_path
146
+ print(f"Error: Audio file not found at {audio_path}")
147
+ # Returning None for audio will result in an error message in Gradio's console
148
+ return None
149
+
150
+ def check_pronunciation(audio_input):
151
+ """
152
+ This is a placeholder function for checking pronunciation.
153
+
154
+ In a real-world application, you would integrate a Speech-to-Text (ASR) model
155
+ (e.g., from Hugging Face Transformers like Whisper) here.
156
+
157
+ Steps for a real implementation:
158
+ 1. Load an Arabic ASR model: `from transformers import pipeline; asr_pipeline = pipeline("automatic-speech-recognition", model="your_arabic_asr_model")`
159
+ 2. Transcribe the input audio: `transcribed_text = asr_pipeline(audio_input)["text"]`
160
+ 3. Compare `transcribed_text` with the `expected_letter` or `expected_word`
161
+ (e.g., using phonetic similarity, or a simple string match for initial development).
162
+ 4. Provide feedback based on the comparison.
163
+ """
164
+ if audio_input is None:
165
+ # If no audio was recorded, prompt the user.
166
+ return "من فضلك سجل صوتك أولاً. (Please record your voice first.)"
167
+
168
+ # The `audio_input` provided by Gradio will be a file path to the temporary recorded audio.
169
+ # You can use this path directly with an ASR model.
170
+
171
+ # Example of how you *would* get the expected text (if you had an ASR model):
172
+ # expected_text = arabic_letters_data[current_letter_idx]["letter"] # Or word_example
173
+
174
+ # Placeholder feedback:
175
+ return f"تم استلام تسجيلك! (Your recording has been received!)" \
176
+ f"\nوظيفة التحقق من النطق ستضاف لاحقًا. (Pronunciation check functionality will be added later.)"
177
+
178
+ # --- Functions for Arabic Storytelling Section ---
179
+
180
+ def get_story_titles():
181
+ """
182
+ Returns a list of story titles to populate the dropdown menu.
183
+ """
184
+ return [story["title"] for story in arabic_stories_data]
185
+
186
+ def select_story_func(title):
187
+ """
188
+ Selects a story based on its title from the dropdown.
189
+ Resets the page index to 0 for the newly selected story.
190
+ """
191
+ global current_story_idx, current_story_page_idx
192
+ for i, story in enumerate(arabic_stories_data):
193
+ if story["title"] == title:
194
+ current_story_idx = i
195
+ current_story_page_idx = 0 # Reset to the first page of the new story
196
+ break
197
+ # After selecting, return the content of the first page of the chosen story
198
+ return get_current_story_page_content()
199
+
200
+ def get_current_story_page_content():
201
+ """
202
+ Retrieves the content (title, text, image, audio) for the current page
203
+ of the currently selected story.
204
+ """
205
+ if not arabic_stories_data:
206
+ # Handle case where no story data is available
207
+ return "لا توجد قصص متاحة", None, None, None
208
+
209
+ story = arabic_stories_data[current_story_idx]
210
+
211
+ if current_story_page_idx < len(story["pages"]):
212
+ # Return content for the current page
213
+ page = story["pages"][current_story_page_idx]
214
+ return story["title"], page["text"], page["image"], page["audio"]
215
+ else:
216
+ # If past the last page, indicate the end of the story
217
+ return story["title"], "انتهت القصة! (End of story!)", None, None # No image/audio at end
218
+
219
+ def next_story_page_func():
220
+ """
221
+ Advances to the next page of the current story.
222
+ If at the end of the story, it stays on the last page.
223
+ """
224
+ global current_story_page_idx
225
+ story = arabic_stories_data[current_story_idx]
226
+ if current_story_page_idx < len(story["pages"]) - 1:
227
+ current_story_page_idx += 1
228
+ # If at the last page, do nothing (stay on the last page)
229
+ return get_current_story_page_content()
230
+
231
+ def prev_story_page_func():
232
+ """
233
+ Goes back to the previous page of the current story.
234
+ If at the beginning of the story, it stays on the first page.
235
+ """
236
+ global current_story_page_idx
237
+ if current_story_page_idx > 0:
238
+ current_story_page_idx -= 1
239
+ # If at the first page, do nothing (stay on the first page)
240
+ return get_current_story_page_content()
241
+
242
+
243
+ # --- Gradio UI Definition using gr.Blocks for advanced layout and styling ---
244
+
245
+ with gr.Blocks(
246
+ theme=gr.themes.Soft(), # A soft, pleasant theme
247
+ # Custom CSS for better aesthetics and responsiveness
248
+ css="""
249
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
250
+ body {
251
+ font-family: 'Inter', sans-serif;
252
+ }
253
+ .gradio-container {
254
+ font-family: 'Inter', sans-serif;
255
+ background-color: #f0f8ff; /* Light blue background for the entire app */
256
+ border-radius: 15px;
257
+ box-shadow: 0 8px 16px rgba(0,0,0,0.2); /* Enhanced shadow */
258
+ padding: 25px;
259
+ max-width: 950px; /* Slightly wider max-width */
260
+ margin: 30px auto;
261
+ border: 2px solid #aaddff; /* Light blue border */
262
+ }
263
+ h1, h2, h3 {
264
+ color: #2c3e50; /* Darker blue-grey for headings */
265
+ text-align: center;
266
+ margin-bottom: 25px;
267
+ font-weight: 700; /* Bold headings */
268
+ }
269
+ .gr-tab-item {
270
+ border-radius: 12px 12px 0 0 !important; /* Rounded top corners for tabs */
271
+ background-color: #e6f7ff; /* Lighter tab background */
272
+ font-size: 1.15em; /* Larger tab text */
273
+ padding: 12px 20px;
274
+ transition: background-color 0.3s ease;
275
+ }
276
+ .gr-tab-item.selected {
277
+ background-color: #ffffff !important;
278
+ border-bottom: none !important;
279
+ color: #007bff !important; /* Vibrant blue for selected tab */
280
+ font-weight: bold;
281
+ box-shadow: 0 -3px 8px rgba(0,0,0,0.1); /* Shadow for selected tab */
282
+ }
283
+ .gr-button {
284
+ background-color: #007bff; /* Primary blue button */
285
+ color: white;
286
+ border-radius: 12px; /* More rounded buttons */
287
+ padding: 12px 25px;
288
+ font-size: 1.15em; /* Larger button text */
289
+ margin: 8px;
290
+ box-shadow: 0 4px 8px rgba(0,0,0,0.25); /* More prominent shadow */
291
+ transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease;
292
+ border: none; /* No default border */
293
+ cursor: pointer;
294
+ }
295
+ .gr-button:hover {
296
+ background-color: #0056b3; /* Darker blue on hover */
297
+ transform: translateY(-3px); /* Lift effect on hover */
298
+ box-shadow: 0 6px 12px rgba(0,0,0,0.3); /* Larger shadow on hover */
299
+ }
300
+ .gr-image {
301
+ border-radius: 18px; /* More rounded images */
302
+ overflow: hidden;
303
+ border: 3px solid #aaddff; /* Thicker light blue border */
304
+ box-shadow: 0 4px 10px rgba(0,0,0,0.15);
305
+ background-color: #ffffff;
306
+ display: block; /* Ensure image is a block element for centering */
307
+ margin: 15px auto; /* Center images */
308
+ }
309
+ .gr-audio {
310
+ border-radius: 12px;
311
+ border: 2px solid #cceeff;
312
+ background-color: #ffffff;
313
+ padding: 10px;
314
+ margin: 10px 0;
315
+ }
316
+ .gr-markdown {
317
+ background-color: #ffffff;
318
+ border-radius: 12px;
319
+ padding: 20px;
320
+ margin-top: 15px;
321
+ border: 2px solid #cceeff;
322
+ text-align: center;
323
+ font-size: 1.3em; /* Larger text for readability for kids */
324
+ color: #333;
325
+ line-height: 1.6; /* Improve line spacing */
326
+ box-shadow: 0 2px 6px rgba(0,0,0,0.08);
327
+ }
328
+ .gr-dropdown {
329
+ border-radius: 12px;
330
+ padding: 8px;
331
+ font-size: 1.1em;
332
+ border: 1px solid #cceeff;
333
+ box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
334
+ }
335
+ .feedback-message {
336
+ color: #28a745; /* Green for success/positive feedback */
337
+ font-weight: bold;
338
+ text-align: center;
339
+ margin-top: 20px;
340
+ font-size: 1.2em;
341
+ }
342
+ .error-message {
343
+ color: #dc3545; /* Red for error messages */
344
+ font-weight: bold;
345
+ text-align: center;
346
+ margin-top: 20px;
347
+ font-size: 1.2em;
348
+ }
349
+ hr {
350
+ border: 0;
351
+ height: 1px;
352
+ background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 123, 255, 0.75), rgba(0, 0, 0, 0));
353
+ margin: 30px 0;
354
+ }
355
+ [dir="rtl"] .gr-button, [dir="rtl"] .gr-markdown, [dir="rtl"] .gr-dropdown {
356
+ text-align: right; /* Ensure RTL alignment for text within components */
357
+ }
358
+ """
359
+ ) as demo:
360
+ # Main application title and welcoming message
361
+ gr.Markdown("# <span style='color:#007bff;'>تطبيق تعلم العربية للأطفال</span>", rtl=True)
362
+ gr.Markdown("## <span style='color:#34495e;'>مرحباً يا أبطالنا الصغار! (Welcome, Little Heroes!)</span>", rtl=True)
363
+
364
+ # Tabs for different learning modes
365
+ with gr.Tabs():
366
+ # --- Arabic Letters Learning Tab ---
367
+ with gr.TabItem("تعلم الحروف العربية (Learn Arabic Letters)", rtl=True):
368
+ with gr.Column(
369
+ scale=1,
370
+ min_width=400,
371
+ elem_id="letter-learning-section", # Unique ID for CSS targeting
372
+ variant="panel" # Adds a subtle panel background
373
+ ):
374
+ gr.Markdown("### <span style='color:#28a745;'>تعلم حرفاً جديداً كل يوم! (Learn a new letter every day!)</span>", rtl=True)
375
+
376
+ # Display area for the Arabic letter
377
+ letter_display = gr.Markdown(
378
+ value="اضغط على 'الحرف التالي' للبدء", # Initial message
379
+ label="الحرف (Letter)",
380
+ rtl=True, # Right-to-Left text direction
381
+ elem_classes=["gr-markdown"] # Apply custom CSS class
382
+ )
383
+ # Display image for the letter
384
+ letter_image = gr.Image(
385
+ label="صورة الحرف (Letter Image)",
386
+ width=250, height=250,
387
+ interactive=False, # Image is for display only, not interactive input
388
+ elem_classes=["gr-image"] # Apply custom CSS class
389
+ )
390
+ # Hidden audio component to play the letter sound
391
+ letter_audio_output = gr.Audio(
392
+ label="صوت الحرف (Letter Sound)",
393
+ autoplay=False,
394
+ type="filepath", # Expects a local file path
395
+ visible=False # Keep it hidden, as we only trigger playback
396
+ )
397
+
398
+ # Navigation buttons for letters
399
+ with gr.Row():
400
+ prev_letter_btn = gr.Button("الحرف السابق (Previous Letter)", elem_classes=["gr-button"])
401
+ play_letter_btn = gr.Button("استمع للحرف (Listen to Letter)", elem_classes=["gr-button"])
402
+ next_letter_btn = gr.Button("الحرف التالي (Next Letter)", elem_classes=["gr-button"])
403
+
404
+ gr.Markdown("<hr>", elem_classes=["gr-markdown"]) # Horizontal rule for separation
405
+
406
+ # --- Word Example Section for the current letter ---
407
+ gr.Markdown("### <span style='color:#ffc107;'>كلمات تبدأ بهذا الحرف (Words starting with this letter)</span>", rtl=True)
408
+ # Display area for the example word
409
+ word_example_display = gr.Markdown(
410
+ value="",
411
+ label="كلمة (Word)",
412
+ rtl=True,
413
+ elem_classes=["gr-markdown"]
414
+ )
415
+ # Display image for the example word
416
+ word_image = gr.Image(
417
+ label="صورة الكلمة (Word Image)",
418
+ width=250, height=250,
419
+ interactive=False,
420
+ elem_classes=["gr-image"]
421
+ )
422
+ # Hidden audio component to play the word sound
423
+ word_audio_output = gr.Audio(
424
+ label="صوت الكلمة (Word Sound)",
425
+ autoplay=False,
426
+ type="filepath",
427
+ visible=False
428
+ )
429
+
430
+ # Button to play the word audio
431
+ play_word_btn = gr.Button("استمع للكلمة (Listen to Word)", elem_classes=["gr-button"])
432
+
433
+ gr.Markdown("<hr>", elem_classes=["gr-markdown"])
434
+
435
+ # --- Pronunciation Practice Section ---
436
+ gr.Markdown("### <span style='color:#17a2b8;'>تدرب على النطق (Practice Pronunciation)</span>", rtl=True)
437
+ # Microphone input for user to record pronunciation
438
+ pronunciation_input = gr.Audio(
439
+ sources=["microphone"], # Allows recording from microphone
440
+ type="filepath", # Output will be a path to a temporary audio file
441
+ label="سجل صوتك وأنت تنطق الحرف/الكلمة (Record your voice saying the letter/word)",
442
+ elem_classes=["gr-audio"]
443
+ )
444
+ # Markdown to display feedback on pronunciation
445
+ pronunciation_feedback = gr.Markdown(
446
+ label="التقييم (Feedback)",
447
+ rtl=True,
448
+ elem_classes=["gr-markdown"]
449
+ )
450
+ # Button to trigger the pronunciation check
451
+ check_pronunciation_btn = gr.Button("تحقق من النطق (Check Pronunciation)", elem_classes=["gr-button"])
452
+
453
+ # --- Arabic Storytelling Tab ---
454
+ with gr.TabItem("قصص عربية (Arabic Storytelling)", rtl=True):
455
+ with gr.Column(
456
+ scale=1,
457
+ min_width=400,
458
+ elem_id="storytelling-section", # Unique ID for CSS targeting
459
+ variant="panel"
460
+ ):
461
+ gr.Markdown("### <span style='color:#fd7e14;'>استمتع بقصصنا الشيقة! (Enjoy our exciting stories!)</span>", rtl=True)
462
+
463
+ # Dropdown to select a story
464
+ story_selection_dropdown = gr.Dropdown(
465
+ choices=get_story_titles(), # Populated by function
466
+ label="اختر قصة (Choose a Story)",
467
+ # Set initial value to the first story if available, otherwise None
468
+ value=get_story_titles()[0] if get_story_titles() else None,
469
+ elem_classes=["gr-dropdown"],
470
+ rtl=True
471
+ )
472
+
473
+ # Display area for story title
474
+ story_title_display = gr.Markdown(
475
+ value="",
476
+ label="عنوان القصة (Story Title)",
477
+ rtl=True,
478
+ elem_classes=["gr-markdown"]
479
+ )
480
+ # Display area for story text
481
+ story_text_display = gr.Markdown(
482
+ value="",
483
+ label="نص القصة (Story Text)",
484
+ rtl=True,
485
+ elem_classes=["gr-markdown"]
486
+ )
487
+ # Display image for the current story page
488
+ story_image_display = gr.Image(
489
+ label="صورة القصة (Story Image)",
490
+ width=450, height=350,
491
+ interactive=False,
492
+ elem_classes=["gr-image"]
493
+ )
494
+ # Hidden audio component to play story page audio
495
+ story_audio_output = gr.Audio(
496
+ label="صوت القصة (Story Audio)",
497
+ autoplay=False,
498
+ type="filepath",
499
+ visible=False
500
+ )
501
+
502
+ # Navigation buttons for story pages
503
+ with gr.Row():
504
+ prev_story_page_btn = gr.Button("الصفحة السابقة (Previous Page)", elem_classes=["gr-button"])
505
+ play_story_btn = gr.Button("استمع للقصة (Listen to Story)", elem_classes=["gr-button"])
506
+ next_story_page_btn = gr.Button("الصفحة التالية (Next Page)", elem_classes=["gr-button"])
507
+
508
+ # --- Event Handlers (Logic for button clicks and component changes) ---
509
+
510
+ # --- Event Handlers for Learning Arabic Letters Tab ---
511
+ # When the demo loads, display the content of the first letter
512
+ demo.load(
513
+ get_current_letter_content,
514
+ inputs=None,
515
+ outputs=[
516
+ letter_display,
517
+ letter_image,
518
+ letter_audio_output, # Output for the letter's audio file path
519
+ word_example_display,
520
+ word_image,
521
+ word_audio_output # Output for the word's audio file path
522
+ ],
523
+ queue=False # No need to queue initial load
524
+ )
525
+
526
+ # Connect navigation buttons to their respective functions
527
+ next_letter_btn.click(
528
+ next_letter_func,
529
+ inputs=None,
530
+ outputs=[
531
+ letter_display,
532
+ letter_image,
533
+ letter_audio_output,
534
+ word_example_display,
535
+ word_image,
536
+ word_audio_output
537
+ ]
538
+ )
539
+ prev_letter_btn.click(
540
+ prev_letter_func,
541
+ inputs=None,
542
+ outputs=[
543
+ letter_display,
544
+ letter_image,
545
+ letter_audio_output,
546
+ word_example_display,
547
+ word_image,
548
+ word_audio_output
549
+ ]
550
+ )
551
+
552
+ # Connect play audio buttons. Using lambda to pass the current audio path dynamically.
553
+ play_letter_btn.click(
554
+ lambda: play_audio(arabic_letters_data[current_letter_idx]["audio"]),
555
+ inputs=None,
556
+ outputs=letter_audio_output
557
+ )
558
+ play_word_btn.click(
559
+ lambda: play_audio(arabic_letters_data[current_letter_idx]["word_audio"]),
560
+ inputs=None,
561
+ outputs=word_audio_output
562
+ )
563
+
564
+ # Connect pronunciation check button
565
+ check_pronunciation_btn.click(
566
+ check_pronunciation,
567
+ inputs=pronunciation_input,
568
+ outputs=pronunciation_feedback
569
+ )
570
+
571
+ # --- Event Handlers for Arabic Storytelling Tab ---
572
+ # When a new story is selected from the dropdown
573
+ story_selection_dropdown.change(
574
+ select_story_func,
575
+ inputs=story_selection_dropdown,
576
+ outputs=[story_title_display, story_text_display, story_image_display, story_audio_output]
577
+ )
578
+
579
+ # Connect navigation buttons for story pages
580
+ next_story_page_btn.click(
581
+ next_story_page_func,
582
+ inputs=None,
583
+ outputs=[story_title_display, story_text_display, story_image_display, story_audio_output]
584
+ )
585
+ prev_story_page_btn.click(
586
+ prev_story_page_func,
587
+ inputs=None,
588
+ outputs=[story_title_display, story_text_display, story_image_display, story_audio_output]
589
+ )
590
+
591
+ # Connect button to play the current story page's audio
592
+ play_story_btn.click(
593
+ lambda: play_audio(arabic_stories_data[current_story_idx]["pages"][current_story_page_idx]["audio"]),
594
+ inputs=None,
595
+ outputs=story_audio_output
596
+ )
597
+
598
+ # Launch the Gradio application
599
+ demo.launch()