import gradio as gr import os from transformers import pipeline # Import for ASR (Speech-to-Text) and TTS (Text-to-Speech) import torchaudio # Required by some TTS models for audio handling # Define base paths for assets - IMPORTANT: You need to create these folders and place your files here # For Hugging Face Spaces, these paths are relative to your app.py file ASSETS_DIR = "./assets" IMAGE_DIR = os.path.join(ASSETS_DIR, "images") AUDIO_DIR = os.path.join(ASSETS_DIR, "audio") # This will also be used for dynamically generated audio # Create asset directories if they don't exist (for local testing, Hugging Face handles this with git lfs) # On Hugging Face Spaces, you'll upload these folders with your files. os.makedirs(IMAGE_DIR, exist_ok=True) os.makedirs(AUDIO_DIR, exist_ok=True) # Initialize ASR pipeline for pronunciation check # Using a small, multilingual Whisper model for demonstration. # For better Arabic-specific performance, consider a model fine-tuned on Arabic speech. # device=-1 will try to use GPU if available, otherwise CPU. try: asr_pipeline = pipeline("automatic-speech-recognition", model="openai/whisper-small", device=-1) except Exception as e: print(f"Warning: Could not load ASR model. Pronunciation check will be a placeholder. Error: {e}") asr_pipeline = None # Set to None if model loading fails # Initialize TTS pipeline for audio feedback # Using a specific Arabic TTS model. try: # Ensure this model is suitable for your Hugging Face Space's resources. # facebook/mms-tts-ara is a good general Arabic TTS model. tts_pipeline = pipeline("text-to-speech", model="facebook/mms-tts-ara", device=-1) except Exception as e: print(f"Warning: Could not load TTS model. Audio feedback will not be generated. Error: {e}") tts_pipeline = None # Set to None if model loading fails # Data for Arabic Letters # Updated paths for word_image and word_audio based on the provided "alphabets pictures names.JPG" arabic_letters_data = [ { "letter": "أ", "image": os.path.join(IMAGE_DIR, "alif.jpg"), "audio": os.path.join(AUDIO_DIR, "alif.wav"), "word_example": "أرنب (Arnab - Rabbit)", "word_image": os.path.join(IMAGE_DIR, "arnab.jpg"), "word_audio": os.path.join(AUDIO_DIR, "arnab.wav"), }, { "letter": "ب", "image": os.path.join(IMAGE_DIR, "ba.jpg"), "audio": os.path.join(AUDIO_DIR, "ba.wav"), "word_example": "بطة (Batta - Duck)", "word_image": os.path.join(IMAGE_DIR, "batta.jpg"), "word_audio": os.path.join(AUDIO_DIR, "batta.wav"), }, { "letter": "ت", "image": os.path.join(IMAGE_DIR, "taa.jpg"), "audio": os.path.join(AUDIO_DIR, "taa.wav"), "word_example": "تفاح (Tuffah - Apple)", "word_image": os.path.join(IMAGE_DIR, "tuffaha.jpg"), # Corrected from 'placeholder_word_taa.jpg' "word_audio": os.path.join(AUDIO_DIR, "tuffaha.wav"), # Corrected from 'placeholder_word_taa.wav' }, { "letter": "ث", "image": os.path.join(IMAGE_DIR, "tha.jpg"), "audio": os.path.join(AUDIO_DIR, "tha.wav"), "word_example": "ثعلب (Tha'lab - Fox)", "word_image": os.path.join(IMAGE_DIR, "thalab.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "thalab.wav"), # Corrected }, { "letter": "ج", "image": os.path.join(IMAGE_DIR, "jim.jpg"), "audio": os.path.join(AUDIO_DIR, "jim.wav"), "word_example": "جمل (Jamal - Camel)", "word_image": os.path.join(IMAGE_DIR, "jamal.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "jamal.wav"), # Corrected }, { "letter": "ح", "image": os.path.join(IMAGE_DIR, "ha.jpg"), "audio": os.path.join(AUDIO_DIR, "ha.wav"), "word_example": "حصان (Hisan - Horse)", "word_image": os.path.join(IMAGE_DIR, "hisan.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "hisan.wav"), # Corrected }, { "letter": "خ", "image": os.path.join(IMAGE_DIR, "kha.jpg"), "audio": os.path.join(AUDIO_DIR, "kha.wav"), "word_example": "خروف (Kharouf - Sheep)", "word_image": os.path.join(IMAGE_DIR, "kharouf.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "kharouf.wav"), # Corrected }, { "letter": "د", "image": os.path.join(IMAGE_DIR, "dal.jpg"), "audio": os.path.join(AUDIO_DIR, "dal.wav"), "word_example": "دجاجة (Dajaja - Chicken)", "word_image": os.path.join(IMAGE_DIR, "dajaja.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "dajaja.wav"), # Corrected }, { "letter": "ذ", "image": os.path.join(IMAGE_DIR, "dhal.jpg"), "audio": os.path.join(AUDIO_DIR, "dhal.wav"), "word_example": "ذرة (Dhurah - Corn)", "word_image": os.path.join(IMAGE_DIR, "dhurah.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "dhurah.wav"), # Corrected }, { "letter": "ر", "image": os.path.join(IMAGE_DIR, "ra.jpg"), "audio": os.path.join(AUDIO_DIR, "ra.wav"), "word_example": "رمان (Rumman - Pomegranate)", "word_image": os.path.join(IMAGE_DIR, "rumman.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "rumman.wav"), # Corrected }, { "letter": "ز", "image": os.path.join(IMAGE_DIR, "za.jpg"), "audio": os.path.join(AUDIO_DIR, "za.wav"), "word_example": "زرافة (Zarafa - Giraffe)", "word_image": os.path.join(IMAGE_DIR, "zarafa.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "zarafa.wav"), # Corrected }, { "letter": "س", "image": os.path.join(IMAGE_DIR, "sin.jpg"), "audio": os.path.join(AUDIO_DIR, "sin.wav"), "word_example": "سمكة (Samaka - Fish)", "word_image": os.path.join(IMAGE_DIR, "samaka.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "samaka.wav"), # Corrected }, { "letter": "ش", "image": os.path.join(IMAGE_DIR, "shin.jpg"), "audio": os.path.join(AUDIO_DIR, "shin.wav"), "word_example": "شجرة (Shajara - Tree)", "word_image": os.path.join(IMAGE_DIR, "shajara.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "shajara.wav"), # Corrected }, { "letter": "ص", "image": os.path.join(IMAGE_DIR, "sad.jpg"), "audio": os.path.join(AUDIO_DIR, "sad.wav"), "word_example": "صابون (Saboon - Soap)", "word_image": os.path.join(IMAGE_DIR, "saboon.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "saboon.wav"), # Corrected }, { "letter": "ض", "image": os.path.join(IMAGE_DIR, "dad.jpg"), "audio": os.path.join(AUDIO_DIR, "dad.wav"), "word_example": "ضفدع (Difda - Frog)", "word_image": os.path.join(IMAGE_DIR, "difda.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "difda.wav"), # Corrected }, { "letter": "ط", "image": os.path.join(IMAGE_DIR, "ta.jpg"), "audio": os.path.join(AUDIO_DIR, "ta.wav"), "word_example": "طائرة (Ta'ira - Airplane)", "word_image": os.path.join(IMAGE_DIR, "tairah.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "tairah.wav"), # Corrected }, { "letter": "ظ", "image": os.path.join(IMAGE_DIR, "zay.jpg"), # Using zay.jpg for ظ based on previous mapping, ensure this image is correct for ظ "audio": os.path.join(AUDIO_DIR, "zay.wav"), # Using zay.wav for ظ, ensure this audio is correct for ظ "word_example": "ظرف (Zarf - Envelope)", "word_image": os.path.join(IMAGE_DIR, "zarf.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "zarf.wav"), # Corrected }, { "letter": "ع", "image": os.path.join(IMAGE_DIR, "ayn.jpg"), "audio": os.path.join(AUDIO_DIR, "ayn.wav"), "word_example": "عين (Ayn - Eye)", "word_image": os.path.join(IMAGE_DIR, "ain.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "ain.wav"), # Corrected }, { "letter": "غ", "image": os.path.join(IMAGE_DIR, "ghayn.jpg"), "audio": os.path.join(AUDIO_DIR, "ghayn.wav"), "word_example": "غواصة (Ghawwasa - Submarine)", "word_image": os.path.join(IMAGE_DIR, "ghawwasa.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "ghawwasa.wav"), # Corrected }, { "letter": "ف", "image": os.path.join(IMAGE_DIR, "fa.jpg"), "audio": os.path.join(AUDIO_DIR, "fa.wav"), "word_example": "فراشة (Farasha - Butterfly)", "word_image": os.path.join(IMAGE_DIR, "farasha.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "farasha.wav"), # Corrected }, { "letter": "ق", "image": os.path.join(IMAGE_DIR, "qaf.jpg"), "audio": os.path.join(AUDIO_DIR, "qaf.wav"), "word_example": "قرد (Qird - Monkey)", "word_image": os.path.join(IMAGE_DIR, "qird.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "qird.wav"), # Corrected }, { "letter": "ك", "image": os.path.join(IMAGE_DIR, "kaf.jpg"), "audio": os.path.join(AUDIO_DIR, "kaf.wav"), "word_example": "كلب (Kalb - Dog)", "word_image": os.path.join(IMAGE_DIR, "kalb.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "kalb.wav"), # Corrected }, { "letter": "ل", "image": os.path.join(IMAGE_DIR, "lam.jpg"), "audio": os.path.join(AUDIO_DIR, "lam.wav"), "word_example": "ليمونة (Laymuna - Lemon)", "word_image": os.path.join(IMAGE_DIR, "laymuna.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "laymuna.wav"), # Corrected }, { "letter": "م", "image": os.path.join(IMAGE_DIR, "mim.jpg"), "audio": os.path.join(AUDIO_DIR, "mim.wav"), "word_example": "موزة (Mawzah - Banana)", "word_image": os.path.join(IMAGE_DIR, "mawzah.jpg"), # Corrected from 'mauzah.jpg' "word_audio": os.path.join(AUDIO_DIR, "mawzah.wav"), # Corrected from 'mauzah.wav' }, { "letter": "ن", "image": os.path.join(IMAGE_DIR, "nun.jpg"), "audio": os.path.join(AUDIO_DIR, "nun.wav"), "word_example": "نمر (Namir - Tiger)", "word_image": os.path.join(IMAGE_DIR, "namir.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "namir.wav"), # Corrected }, { "letter": "ه", "image": os.path.join(IMAGE_DIR, "ha.jpg"), "audio": os.path.join(AUDIO_DIR, "ha.wav"), "word_example": "هاتف (Hatif - Telephone)", "word_image": os.path.join(IMAGE_DIR, "hatif.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "hatif.wav"), # Corrected }, { "letter": "و", "image": os.path.join(IMAGE_DIR, "waw.jpg"), "audio": os.path.join(AUDIO_DIR, "waw.wav"), "word_example": "وردة (Warda - Rose)", "word_image": os.path.join(IMAGE_DIR, "warda.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "warda.wav"), # Corrected }, { "letter": "ي", "image": os.path.join(IMAGE_DIR, "ya.jpg"), "audio": os.path.join(AUDIO_DIR, "ya.wav"), "word_example": "يد (Yad - Hand)", "word_image": os.path.join(IMAGE_DIR, "yad.jpg"), # Corrected "word_audio": os.path.join(AUDIO_DIR, "yad.wav"), # Corrected } ] # Placeholder data for Arabic Stories # YOU MUST REPLACE 'placeholder_story_image_pageX.jpg' and 'placeholder_story_audio.wav' # with actual paths to your story images and audio files within the 'assets/' directory. arabic_stories_data = [ { "title": "The Gingerbread Man: رجل خبز الزنجبيل (Rajul Khubz Al-Zanjabeel)", "pages": [ { "text": "في يوم من الأيام، خرج أرنب صغير يلعب في الحديقة الخضراء. كان الجو جميلاً والشمس مشرقة.", "image": os.path.join(IMAGE_DIR, "story1_page1.JPG"), # e.g., 'assets/images/story1_page1.jpg' "audio": os.path.join(AUDIO_DIR, "story1_page1.wav"), # e.g., 'assets/audio/story1_page1.wav' }, { "text": "فجأة، رأى الأرنب زهرة حمراء جميلة. 'ما أجمل هذه الزهرة!' قال الأرنب بسعادة.", "image": os.path.join(IMAGE_DIR, "story1_page2.JPG"), # e.g., 'assets/images/story1_page2.jpg' "audio": os.path.join(AUDIO_DIR, "story1_page2.wav"), # e.g., 'assets/audio/story1_page2.wav' }, { "text": "فجأة، رأى الأرنب زهرة حمراء جميلة. 'ما أجمل هذه الزهرة!' قال الأرنب بسعادة.", "image": os.path.join(IMAGE_DIR, "story1_page3.JPG"), # e.g., 'assets/images/story1_page3.jpg' "audio": os.path.join(AUDIO_DIR, "story1_page3.wav"), # e.g., 'assets/audio/story1_page3.wav' }, # Add more pages for this story. Each page needs text, an image, and corresponding audio. ] }, { "title": "الأسد والفأر (The Lion and the Mouse)", "pages": [ { "text": "كان أسد نائماً تحت شجرة كبيرة في الغابة. جاء فأر صغير وركض فوق الأسد.", "image": os.path.join(IMAGE_DIR, "story2_page1.jpg"), # e.g., 'assets/images/story2_page1.jpg' "audio": os.path.join(AUDIO_DIR, "story2_page1.wav"), # e.g., 'assets/audio/story2_page1.wav' }, { "text": "استيقظ الأسد غاضباً وأمسك بالفأر. توسل الفأر إليه ليتركه، ووعده بمساعدته يوماً ما.", "image": os.path.join(IMAGE_DIR, "story2_page2.jpg"), # e.g., 'assets/images/story2_page2.jpg' "audio": os.path.join(AUDIO_DIR, "story2_page2.wav"), # e.g., 'assets/audio/story2_page2.wav' }, # Add more pages for this story. ] } ] # Global state variables for navigation (do not modify directly outside functions) current_letter_idx = 0 current_story_idx = 0 current_story_page_idx = 0 # --- Functions for Learning Arabic Letters Section --- def get_current_letter_content(): """ Retrieves the current letter's data (image, audio, word example, word image, word audio) based on the global `current_letter_idx`. The letter text is no longer returned directly. """ if not arabic_letters_data: # Handle case where no letter data is available return None, None, None, None # Adjusted return for consistency data = arabic_letters_data[current_letter_idx] # Return (letter_image, letter_audio, word_image, word_audio) # word_example is no longer returned as an output to Gradio components return ( data["image"], data["audio"], # This audio is for the letter sound itself data["word_image"], data["word_audio"] # This audio is for the word example sound ) def next_letter_func(): """ Advances to the next Arabic letter in the `arabic_letters_data` list. If at the end, it loops back to the first letter. """ global current_letter_idx if current_letter_idx < len(arabic_letters_data) - 1: current_letter_idx += 1 else: current_letter_idx = 0 # Loop back to the beginning return get_current_letter_content() def prev_letter_func(): """ Goes back to the previous Arabic letter in the `arabic_letters_data` list. If at the beginning, it loops to the last letter. """ global current_letter_idx if current_letter_idx > 0: current_letter_idx -= 1 else: current_letter_idx = len(arabic_letters_data) - 1 # Loop to the end return get_current_letter_content() def play_audio(audio_path): """ Plays an audio file. This function is called by Gradio's Audio component. It checks if the file exists before returning the path. """ if os.path.exists(audio_path): return audio_path print(f"Error: Audio file not found at {audio_path}") # Returning None for audio will result in an error message in Gradio's console return None def generate_tts_audio(text_to_speak, filename="temp_feedback_audio.wav"): """ Generates audio from text using the TTS pipeline and saves it to a file. Returns the file path. """ if tts_pipeline is None: print("TTS pipeline not loaded, cannot generate audio.") return None try: # Generate speech # For 'facebook/mms-tts-ara', the audio output might need specific handling. # This is a common pattern for Hugging Face TTS pipelines. output = tts_pipeline(text_to_speak) # Assuming output is a dictionary like {'audio': numpy_array, 'sampling_rate': int} audio_array = output['audio'] sampling_rate = output['sampling_rate'] # Save the audio to a temporary file output_path = os.path.join(AUDIO_DIR, filename) torchaudio.save(output_path, audio_array.unsqueeze(0), sampling_rate) return output_path except Exception as e: print(f"Error generating TTS audio for '{text_to_speak}': {e}") return None def check_pronunciation(audio_input): """ Performs pronunciation check using ASR. Compares transcribed text to the expected Arabic letter. Returns text feedback and an audio file for the feedback. """ feedback_text = "" feedback_audio_path = None if audio_input is None: feedback_text = "من فضلك سجل صوتك أولاً. (Please record your voice first.)" feedback_audio_path = generate_tts_audio("من فضلك سجل صوتك أولاً.") return feedback_text, feedback_audio_path if asr_pipeline is None: feedback_text = "وظيفة التحقق من النطق غير متوفرة. (Pronunciation check not available. ASR model failed to load.)" feedback_audio_path = generate_tts_audio("وظيفة التحقق من النطق غير متوفرة.") return feedback_text, feedback_audio_path try: # Transcribe the audio input # Ensure the language is explicitly set to Arabic for better results with Whisper transcription_result = asr_pipeline(audio_input, generate_kwargs={"language": "ar"}) transcribed_text = transcription_result["text"].strip().lower() # Get the expected Arabic letter for comparison expected_letter = arabic_letters_data[current_letter_idx]["letter"].lower() # Simple check: does the transcription contain the expected letter? if expected_letter in transcribed_text: feedback_text = f"أحسنت! (Excellent!) لقد قلت: '{transcribed_text}'" feedback_audio_path = generate_tts_audio("أحسنت!") else: feedback_text = f"حاول مرة أخرى. (Try again.) لقد قلت: '{transcribed_text}'" \ f" كان المتوقع: '{arabic_letters_data[current_letter_idx]['letter']}'" feedback_audio_path = generate_tts_audio("حاول مرة أخرى.") return feedback_text, feedback_audio_path except Exception as e: feedback_text = f"حدث خطأ أثناء التحقق من النطق: {e}. (An error occurred during pronunciation check.)" feedback_audio_path = generate_tts_audio("حدث خطأ أثناء التحقق من النطق.") # Generic error audio return feedback_text, feedback_audio_path # --- Functions for Arabic Storytelling Section --- def get_story_titles(): """ Returns a list of story titles to populate the dropdown menu. """ return [story["title"] for story in arabic_stories_data] def select_story_func(title): """ Selects a story based on its title from the dropdown. Resets the page index to 0 for the newly selected story. """ global current_story_idx, current_story_page_idx for i, story in enumerate(arabic_stories_data): if story["title"] == title: current_story_idx = i current_story_page_idx = 0 # Reset to the first page of the new story break # After selecting, return the content of the first page of the chosen story return get_current_story_page_content() def get_current_story_page_content(): """ Retrieves the content (title, text, image, audio) for the current page of the currently selected story. """ if not arabic_stories_data: # Handle case where no story data is available return "لا توجد قصص متاحة", None, None, None story = arabic_stories_data[current_story_idx] if current_story_page_idx < len(story["pages"]): # Return content for the current page page = story["pages"][current_story_page_idx] return story["title"], page["text"], page["image"], page["audio"] else: # If past the last page, indicate the end of the story return story["title"], "انتهت القصة! (End of story!)", None, None # No image/audio at end def next_story_page_func(): """ Advances to the next page of the current story. If at the end of the story, it stays on the last page. """ global current_story_page_idx story = arabic_stories_data[current_story_idx] if current_story_page_idx < len(story["pages"]) - 1: current_story_page_idx += 1 # If at the last page, do nothing (stay on the last page) return get_current_story_page_content() def prev_story_page_func(): """ Goes back to the previous page of the current story. If at the beginning of the story, it loops to the last letter. """ global current_story_page_idx if current_story_page_idx > 0: current_story_page_idx -= 1 # If at the first page, do nothing (stay on the first page) return get_current_story_page_content() # --- Gradio UI Definition using gr.Blocks for advanced layout and styling --- with gr.Blocks( theme=gr.themes.Soft(), # A soft, pleasant theme # Custom CSS for better aesthetics and responsiveness css=""" @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); body { font-family: 'Inter', sans-serif; } .gradio-container { font-family: 'Inter', sans-serif; background-color: #f0f8ff; /* Light blue background for the entire app */ border-radius: 15px; box-shadow: 0 8px 16px rgba(0,0,0,0.2); /* Enhanced shadow */ padding: 25px; max-width: 950px; /* Slightly wider max-width */ margin: 30px auto; border: 2px solid #aaddff; /* Light blue border */ } h1, h2, h3 { color: #2c3e50; /* Darker blue-grey for headings */ text-align: center; margin-bottom: 25px; font-weight: 700; /* Bold headings */ } .gr-tab-item { border-radius: 12px 12px 0 0 !important; /* Rounded top corners for tabs */ background-color: #e6f7ff; /* Lighter tab background */ font-size: 1.15em; /* Larger tab text */ padding: 12px 20px; transition: background-color 0.3s ease; } .gr-tab-item.selected { background-color: #ffffff !important; border-bottom: none !important; color: #007bff !important; /* Vibrant blue for selected tab */ font-weight: bold; box-shadow: 0 -3px 8px rgba(0,0,0,0.1); /* Shadow for selected tab */ } .gr-button { background-color: #007bff; /* Primary blue button */ color: white; border-radius: 12px; /* More rounded buttons */ padding: 12px 25px; font-size: 1.15em; /* Larger button text */ margin: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); /* More prominent shadow */ transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease; border: none; /* No default border */ cursor: pointer; } .gr-button:hover { background-color: #0056b3; /* Darker blue on hover */ transform: translateY(-3px); /* Lift effect on hover */ box-shadow: 0 6px 12px rgba(0,0,0,0.3); /* Larger shadow on hover */ } .gr-image { border-radius: 18px; /* More rounded images */ overflow: hidden; border: 3px solid #aaddff; /* Thicker light blue border */ box-shadow: 0 4px 10px rgba(0,0,0,0.15); background-color: #ffffff; display: block; /* Ensure image is a block element for centering */ margin: 15px auto; /* Center images */ } .gr-audio { border-radius: 12px; border: 2px solid #cceeff; background-color: #ffffff; padding: 10px; margin: 10px 0; } .gr-markdown { background-color: #ffffff; border-radius: 12px; padding: 20px; margin-top: 15px; border: 2px solid #cceeff; text-align: center; font-size: 1.3em; /* Larger text for readability for kids */ color: #333; line-height: 1.6; /* Improve line spacing */ box-shadow: 0 2px 6px rgba(0,0,0,0.08); } .gr-dropdown { border-radius: 12px; padding: 8px; font-size: 1.1em; border: 1px solid #cceeff; box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); } .feedback-message { color: #28a745; /* Green for success/positive feedback */ font-weight: bold; text-align: center; margin-top: 20px; font-size: 1.2em; } .error-message { color: #dc3545; /* Red for error messages */ font-weight: bold; text-align: center; margin-top: 20px; font-size: 1.2em; } hr { border: 0; height: 1px; background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 123, 255, 0.75), rgba(0, 0, 0, 0)); margin: 30px 0; } [dir="rtl"] .gr-button, [dir="rtl"] .gr-markdown, [dir="rtl"] .gr-dropdown { text-align: right; /* Ensure RTL alignment for text within components */ } """ ) as demo: # Main application title and welcoming message gr.Markdown("# تطبيق تعلم العربية للأطفال", rtl=True) # Add logo image gr.Image( value=os.path.join(IMAGE_DIR, "Applogo.png"), # Path to your logo image label="شعار التطبيق (App Logo)", width=300, height=150, interactive=False, elem_classes=["gr-image"] ) gr.Markdown("## مرحباً يا أبطالنا الصغار! (Welcome, Little Heroes!)", rtl=True) # Tabs for different learning modes with gr.Tabs(): # --- Arabic Letters Learning Tab --- with gr.TabItem("تعلم الحروف العربية (Learn Arabic Letters)"): with gr.Column( scale=1, min_width=400, elem_id="letter-learning-section", variant="panel" ): gr.Markdown("### تعلم حرفاً جديداً كل يوم! (Learn a new letter every day!)", rtl=True) # The letter image will now be the primary display for the letter. letter_image = gr.Image( label="صورة الحرف (Letter Image)", width=250, height=250, interactive=False, elem_classes=["gr-image"] ) # Hidden audio component to play the letter sound letter_audio_output = gr.Audio( label="صوت الحرف (Letter Sound)", autoplay=True, type="filepath", visible=False ) # Navigation buttons for letters with gr.Row(): prev_letter_btn = gr.Button("الحرف السابق (Previous Letter)", elem_classes=["gr-button"]) play_letter_btn = gr.Button("استمع للحرف (Listen to Letter)", elem_classes=["gr-button"]) next_letter_btn = gr.Button("الحرف التالي (Next Letter)", elem_classes=["gr-button"]) #gr.Markdown("
", elem_classes=["gr-markdown"]) # --- Word Example Section for the current letter --- gr.Markdown("### كلمات تبدأ بهذا الحرف (Words starting with this letter)", rtl=True) # Display image for the example word word_image = gr.Image( label="صورة الكلمة (Word Image)", width=250, height=250, interactive=False, elem_classes=["gr-image"] ) # Hidden audio component to play the word sound word_audio_output = gr.Audio( label="صوت الكلمة (Word Sound)", autoplay=True, type="filepath", visible=False ) # Button to play the word audio play_word_btn = gr.Button("استمع للكلمة (Listen to Word)", elem_classes=["gr-button"]) gr.Markdown("
", elem_classes=["gr-markdown"]) # --- Pronunciation Practice Section --- gr.Markdown("### تدرب على النطق (Practice Pronunciation)", rtl=True) # Microphone input for user to record pronunciation pronunciation_input = gr.Audio( sources=["microphone"], type="filepath", label="سجل صوتك وأنت تنطق الحرف/الكلمة (Record your voice saying the letter/word)", elem_classes=["gr-audio"] ) # Markdown to display feedback on pronunciation pronunciation_feedback = gr.Markdown( label="التقييم (Feedback)", rtl=True, elem_classes=["gr-markdown"] ) # New Audio component to play pronunciation feedback pronunciation_feedback_audio = gr.Audio( label="صوت التقييم (Feedback Audio)", autoplay=True, type="filepath", visible=False # Keep it hidden, as we only trigger playback ) # Button to trigger the pronunciation check check_pronunciation_btn = gr.Button("تحقق من النطق (Check Pronunciation)", elem_classes=["gr-button"]) # --- Arabic Storytelling Tab --- with gr.TabItem("قصص عربية (Arabic Storytelling)"): with gr.Column( scale=1, min_width=400, elem_id="storytelling-section", variant="panel" ): gr.Markdown("### استمتع بقصصنا الشيقة! (Enjoy our exciting stories!)", rtl=True) # Dropdown to select a story story_selection_dropdown = gr.Dropdown( choices=get_story_titles(), label="اختر قصة (Choose a Story)", value=get_story_titles()[0] if get_story_titles() else None, elem_classes=["gr-dropdown"] ) # Display area for story title story_title_display = gr.Markdown( value="", label="عنوان القصة (Story Title)", rtl=True, elem_classes=["gr-markdown"] ) # Display area for story text story_text_display = gr.Markdown( value="", label="نص القصة (Story Text)", rtl=True, elem_classes=["gr-markdown"] ) # Display image for the current story page story_image_display = gr.Image( label="صورة القصة (Story Image)", width=450, height=350, interactive=False, elem_classes=["gr-image"] ) # Hidden audio component to play story page audio story_audio_output = gr.Audio( label="صوت القصة (Story Audio)", autoplay=True, type="filepath", visible=False ) # Navigation buttons for story pages with gr.Row(): prev_story_page_btn = gr.Button("الصفحة السابقة (Previous Page)", elem_classes=["gr-button"]) play_story_btn = gr.Button("استمع للقصة (Listen to Story)", elem_classes=["gr-button"]) next_story_page_btn = gr.Button("الصفحة التالية (Next Page)", elem_classes=["gr-button"]) # --- Event Handlers (Logic for button clicks and component changes) --- # --- Event Handlers for Learning Arabic Letters Tab --- # When the demo loads, display the content of the first letter demo.load( get_current_letter_content, inputs=None, outputs=[ letter_image, letter_audio_output, word_image, word_audio_output ], queue=False ) # Connect navigation buttons to their respective functions next_letter_btn.click( next_letter_func, inputs=None, outputs=[ letter_image, letter_audio_output, word_image, word_audio_output ] ) prev_letter_btn.click( prev_letter_func, inputs=None, outputs=[ letter_image, letter_audio_output, word_image, word_audio_output ] ) # Connect play audio buttons. Using lambda to pass the current audio path dynamically. play_letter_btn.click( lambda: play_audio(arabic_letters_data[current_letter_idx]["audio"]), inputs=None, outputs=letter_audio_output ) play_word_btn.click( lambda: play_audio(arabic_letters_data[current_letter_idx]["word_audio"]), inputs=None, outputs=word_audio_output ) # Connect pronunciation check button check_pronunciation_btn.click( check_pronunciation, inputs=pronunciation_input, outputs=[pronunciation_feedback, pronunciation_feedback_audio] ) # --- Event Handlers for Arabic Storytelling Tab --- # When a new story is selected from the dropdown story_selection_dropdown.change( select_story_func, inputs=story_selection_dropdown, outputs=[story_title_display, story_text_display, story_image_display, story_audio_output] ) # Connect navigation buttons for story pages next_story_page_btn.click( next_story_page_func, inputs=None, outputs=[story_title_display, story_text_display, story_image_display, story_audio_output] ) prev_story_page_btn.click( prev_story_page_func, inputs=None, outputs=[story_title_display, story_text_display, story_image_display, story_audio_output] ) # Connect button to play the current story page's audio play_story_btn.click( lambda: play_audio(arabic_stories_data[current_story_idx]["pages"][current_story_page_idx]["audio"]), inputs=None, outputs=story_audio_output ) # Launch the Gradio application demo.launch()