import gradio as gr import os from agent import ReadingCoachAgent # Create a single instance of the agent reading_coach = ReadingCoachAgent() session = {"story": "", "name": "", "grade": "", "progress": 0, "last_feedback": "", "practice_count": 0} # Define theme colors (Duolingo-inspired) PRIMARY_COLOR = "#58CC02" # Green SECONDARY_COLOR = "#FFC800" # Yellow ACCENT_COLOR = "#FF4B4B" # Red BG_COLOR = "#F7F7F7" # Light gray # Custom CSS for more professional styling custom_css = """ :root { --primary-color: #58CC02; --secondary-color: #FFC800; --accent-color: #FF4B4B; --neutral-color: #4B4B4B; --light-bg: #F7F7F7; --white: #FFFFFF; --border-radius: 16px; --shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .container { font-family: 'Nunito', sans-serif; max-width: 900px; margin: 0 auto; font-size: 0.95rem; } .header { text-align: center; margin-bottom: 2rem; color: var(--neutral-color); } .app-title { color: var(--primary-color); font-size: 2.2rem; font-weight: 800; margin: 0; } .card { background: var(--white); border-radius: var(--border-radius); padding: 1.5rem; box-shadow: var(--shadow); margin-bottom: 1.5rem; } .btn-primary { background: var(--primary-color) !important; color: var(--white) !important; font-weight: bold !important; border: none !important; padding: 0.75rem 1.5rem !important; border-radius: 50px !important; cursor: pointer !important; transition: transform 0.1s, box-shadow 0.1s, background-color 0.2s !important; box-shadow: 0 4px 0 #48a700 !important; } .btn-primary:hover { background: #62d40a !important; } .btn-primary:active { background: #4ab000 !important; transform: translateY(2px) !important; box-shadow: 0 2px 0 #3e9500 !important; } .btn-secondary { background: var(--secondary-color) !important; color: var(--neutral-color) !important; font-weight: bold !important; border: none !important; padding: 0.75rem 1.5rem !important; border-radius: 50px !important; cursor: pointer !important; transition: transform 0.1s, box-shadow 0.1s, background-color 0.2s !important; box-shadow: 0 4px 0 #e0b000 !important; } .btn-secondary:hover { background: #ffd119 !important; } .btn-secondary:active { background: #e0b000 !important; transform: translateY(2px) !important; box-shadow: 0 2px 0 #c69e00 !important; } .btn-practice { background: #9333ea !important; color: var(--white) !important; font-weight: bold !important; border: none !important; padding: 0.75rem 1.5rem !important; border-radius: 50px !important; cursor: pointer !important; transition: transform 0.1s, box-shadow 0.1s, background-color 0.2s !important; box-shadow: 0 4px 0 #7c2d9c !important; } .btn-practice:hover { background: #a346f5 !important; } .btn-practice:active { background: #7e2bd0 !important; transform: translateY(2px) !important; box-shadow: 0 2px 0 #6b228b !important; } .btn-clear { background: var(--accent-color) !important; color: var(--white) !important; font-weight: bold !important; border: none !important; padding: 0.5rem 1rem !important; border-radius: 25px !important; cursor: pointer !important; transition: transform 0.1s, box-shadow 0.1s, background-color 0.2s !important; box-shadow: 0 2px 0 #d93636 !important; } .btn-clear:hover { background: #ff5f5f !important; } .btn-clear:active { background: #e43a3a !important; transform: translateY(1px) !important; box-shadow: 0 1px 0 #c52e2e !important; } .progress-container { width: 100%; background-color: #e0e0e0; border-radius: 50px; margin: 1rem 0; height: 10px; } .progress-bar { background-color: var(--primary-color); height: 100%; border-radius: 50px; transition: width 0.3s ease; } .feedback-area { background: #f8f9fa; border: 2px solid #e9ecef; border-radius: 12px; padding: 1rem; margin: 1rem 0; font-family: 'Consolas', 'Monaco', monospace; font-size: 0.9rem; line-height: 1.6; white-space: pre-wrap; } .practice-info { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 1rem; border-radius: 12px; margin: 1rem 0; text-align: center; } input, textarea { border: 2px solid #e0e0e0 !important; border-radius: var(--border-radius) !important; padding: 10px !important; font-size: 14px !important; } input:focus, textarea:focus { border-color: var(--primary-color) !important; outline: none !important; } @media (max-width: 768px) { .card { padding: 1rem; } } """ def start_session(name, grade, topic): """Generate a new story based on name, grade, and topic""" if not name.strip() or not grade.strip() or not topic.strip(): return "Please fill in all fields", gr.update(visible=False), gr.update(visible=False) try: # Store session data session["name"] = name session["grade"] = grade session["practice_count"] = 0 # Clear previous session reading_coach.clear_session() # Generate the story using the correct method generated_story = reading_coach.generate_story_for_student(name, grade, topic) session["story"] = generated_story session["progress"] = 33 # Print for debugging print(f"Generated story: {session['story'][:50]}...") # Return the story and make practice card visible return session["story"], gr.update(visible=True), gr.update(visible=False) except Exception as e: print(f"Error in start_session: {e}") # Provide a fallback story if generation fails fallback = f"Once upon a time, {name} went on an adventure to learn about {topic}..." session["story"] = fallback return fallback, gr.update(visible=True), gr.update(visible=False) def generate_audio(): """Generate audio for the current story""" try: if not session.get("story"): return None print("Generating audio for story...") audio_path = reading_coach.create_audio_from_story(session["story"]) if audio_path: session["progress"] = 66 print(f"Audio generated successfully: {audio_path}") print(f"Audio path type: {type(audio_path)}") # Ensure the path exists and is accessible if os.path.exists(audio_path): print(f"Audio file exists at: {audio_path}") return audio_path else: print(f"Audio file does not exist at: {audio_path}") return None else: print("No audio path returned from TTS") # Instead of returning None, we could return a message or skip audio return None except Exception as e: print(f"Error in generate_audio: {e}") import traceback traceback.print_exc() return None def submit_reading(audio): """Process the student's reading and provide comprehensive agentic feedback""" try: # Handle various audio input formats if audio is None: return "Please record your reading first.", gr.update(visible=False) # Debug: Print what we received print(f"Received audio input: {type(audio)} - {str(audio)[:100]}...") # Pass audio directly to the agent - let STT handle the format conversion transcribed, feedback, accuracy = reading_coach.analyze_student_reading(audio) # Store feedback for potential story generation session["last_feedback"] = feedback session["progress"] = 100 session["practice_count"] += 1 # Always show practice story section so students can get more challenging stories show_practice = True return feedback, gr.update(visible=show_practice) except Exception as e: print(f"Error in submit_reading: {e}") import traceback traceback.print_exc() return "There was an error processing your reading. Please try again.", gr.update(visible=False) def generate_practice_story(): """Generate a new targeted story based on previous feedback""" try: if not session.get("name") or not session.get("grade"): return "Please complete a reading session first to get a personalized practice story.", "" # Generate targeted story using the correct method new_story = reading_coach.generate_practice_story(session["name"], session["grade"]) # Update session with new story session["story"] = new_story session["progress"] = 33 session["practice_count"] += 1 # Clear previous feedback session["last_feedback"] = "" # Create adaptive message based on practice count and performance if session["practice_count"] == 1: practice_msg = f"🌟 New Challenge Story Generated!\nThis story is tailored to help you continue growing as a reader." else: practice_msg = f"🚀 Challenge Story #{session['practice_count']} Ready!\nKeep pushing yourself - you're doing amazing!" return new_story, practice_msg except Exception as e: print(f"Error generating practice story: {e}") return "There was an error generating a new practice story. Please try again.", "" def clear_all_audio(): """Clear all audio components and reset audio session""" return None, None, "🔄 All audio cleared!" def reset_session(): """Reset the session to start over""" session.clear() session.update({"story": "", "name": "", "grade": "", "progress": 0, "last_feedback": "", "practice_count": 0}) reading_coach.reset_all_data() return ( gr.update(value=""), gr.update(value=""), gr.update(value=""), gr.update(value=""), gr.update(value=None), gr.update(value=""), gr.update(value=""), gr.update(visible=False), gr.update(visible=False), 0 ) def clear_recording(): """Clear the recorded audio""" return None def record_again(): """Reset recording for a new attempt""" return None def update_progress_bar(progress): """Update the progress bar width based on progress percentage""" return f"
" def launch_ui(): with gr.Blocks(css=custom_css, title="ReadRight - AI Reading Coach") as demo: with gr.Column(elem_classes="container"): # Header gr.HTML("""

🦉 ReadRight

AI-powered reading coach for kids

""") # Progress tracker progress_bar = gr.HTML(update_progress_bar(0), elem_id="progress-bar") # Step 1: Story Setup with gr.Column(elem_classes="card") as setup_card: gr.HTML("

🌟 Let's Create Your Personalized Reading Adventure!

") with gr.Row(): with gr.Column(scale=2): with gr.Row(): name = gr.Text(label="👤 Your Name", placeholder="Enter your name...") with gr.Row(): grade = gr.Dropdown( label="📚 Grade Level", choices=["Grade 1", "Grade 2", "Grade 3", "Grade 4", "Grade 5", "Grade 6", "Grade 7", "Grade 8", "Grade 9", "Grade 10"], value="Grade 1", allow_custom_value=True ) with gr.Row(): topic = gr.Text(label="🌟 Story Topic", placeholder="e.g., space adventure, friendly dinosaurs, ocean exploration...") with gr.Row(): btn_start = gr.Button("🚀 Create My Story", elem_classes="btn-primary") with gr.Column(scale=1): gr.HTML("""

📚 How to Use ReadRight

Step 1: Enter your name, grade, and choose a fun story topic

Step 2: Click "Create My Story" to generate your personalized reading adventure

Step 3: Listen to the story first by clicking "Listen to Story"

Step 4: Record yourself reading the story aloud

Step 5: Get personalized feedback from your AI reading coach

Step 6: Practice with targeted stories if needed!

""") # Step 2: Reading Practice with gr.Column(elem_classes="card", visible=False) as practice_card: gr.HTML("

📖 Time to Practice Reading!

") # Story display story = gr.Markdown(label="📖 Your Personalized Story") with gr.Row(): btn_play = gr.Button("🔊 Listen to Story", elem_classes="btn-secondary") # Audio playback audio_out = gr.Audio(label="🎵 Story Audio - Listen and Follow Along", visible=True) gr.HTML("

🎤 Now Read the Story Aloud!

") # Recording record = gr.Audio(label="🎤 Record Your Reading", sources=["microphone"],type="filepath") with gr.Row(): btn_record_again = gr.Button("🎤 Record Again", elem_classes="btn-secondary") with gr.Row(): btn_submit = gr.Button("✨ Get AI Feedback", elem_classes="btn-primary") # Agentic Feedback area feedback = gr.TextArea( label="🤖 Your Personalized AI Reading Coach Feedback", interactive=False, elem_classes="feedback-area", lines=12 ) # Step 3: Continue Learning (appears after reading feedback) with gr.Column(elem_classes="card", visible=False) as practice_story_card: gr.HTML("""

🚀 Keep Learning & Growing!

Ready for your next challenge? Get a new story that's perfect for your reading level!

""") practice_info = gr.Text(label="Practice Information", interactive=False) with gr.Row(): btn_generate_practice = gr.Button("🌟 Get Next Challenge Story", elem_classes="btn-practice") btn_reset = gr.Button("🔄 Start Fresh Session", elem_classes="btn-clear") # Event handlers btn_start.click( start_session, inputs=[name, grade, topic], outputs=[story, practice_card, practice_story_card] ) btn_play.click( generate_audio, inputs=[], outputs=[audio_out] ) btn_submit.click( submit_reading, inputs=[record], outputs=[feedback, practice_story_card] ) btn_generate_practice.click( generate_practice_story, inputs=[], outputs=[story, practice_info] ) btn_record_again.click( record_again, inputs=[], outputs=[record] ) btn_reset.click( reset_session, inputs=[], outputs=[name, grade, topic, story, record, feedback, practice_info, practice_card, practice_story_card] ) return demo