ReadRight / ui.py
ParulPandey's picture
Update ui.py
d8c8d0c verified
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"<div class='progress-container'><div class='progress-bar' style='width: {progress}%'></div></div>"
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("""
<div class="header">
<h1 class="app-title">πŸ¦‰ ReadRight</h1>
<p>AI-powered reading coach for kids</p>
</div>
""")
# 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("<h2>🌟 Let's Create Your Personalized Reading Adventure!</h2>")
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("""
<div style="background: linear-gradient(135deg, var(--primary-color) 0%, #48a700 100%);
color: white; padding: 1.5rem; border-radius: var(--border-radius);
margin-left: 1rem; height: fit-content; box-shadow: var(--shadow);">
<h3 style="margin-top: 0; color: white; font-size: 1.1rem; font-weight: 700;">πŸ“š How to Use ReadRight</h3>
<div style="font-size: 0.85rem; line-height: 1.5;">
<p style="margin: 0.5rem 0;"><span style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-weight: 600;">Step 1:</span> Enter your name, grade, and choose a fun story topic</p>
<p style="margin: 0.5rem 0;"><span style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-weight: 600;">Step 2:</span> Click "Create My Story" to generate your personalized reading adventure</p>
<p style="margin: 0.5rem 0;"><span style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-weight: 600;">Step 3:</span> Listen to the story first by clicking "Listen to Story"</p>
<p style="margin: 0.5rem 0;"><span style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-weight: 600;">Step 4:</span> Record yourself reading the story aloud</p>
<p style="margin: 0.5rem 0;"><span style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-weight: 600;">Step 5:</span> Get personalized feedback from your AI reading coach</p>
<p style="margin: 0.5rem 0;"><span style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-weight: 600;">Step 6:</span> Practice with targeted stories if needed!</p>
</div>
</div>
""")
# Step 2: Reading Practice
with gr.Column(elem_classes="card", visible=False) as practice_card:
gr.HTML("<h2>πŸ“– Time to Practice Reading!</h2>")
# 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("<h3>🎀 Now Read the Story Aloud!</h3>")
# 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("""
<div class="practice-info">
<h2>πŸš€ Keep Learning & Growing!</h2>
<p>Ready for your next challenge? Get a new story that's perfect for your reading level!</p>
</div>
""")
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