ProfSule's picture
strip trailing spaces (#2)
a3aad20 verified
import gradio as gr
import openai
import json
import random
from datetime import datetime
import os
# Configure OpenRouter API
openai.api_base = "https://openrouter.ai/api/v1"
openai.api_key = os.environ.get("OPENROUTER_API_KEY", "").strip()
class ScienceTalkGame:
def __init__(self):
self.scenarios = self.load_all_scenarios()
self.reset_session()
def reset_session(self):
self.session = {
"engagement_scores": {},
"teaching_points": 0,
"choices_made": [],
"current_scenario": "grade2_plants",
"turn_count": 0
}
self.update_engagement_for_scenario()
def load_all_scenarios(self):
return {
"grade2_plants": {
"title": "🌱 Grade 2: Plant Investigation",
"ngss_standard": "2-LS2-1: Environmental Plant Needs",
"setup": "Your 2nd grade students are examining classroom plants. Some are healthy and green, others are yellow and droopy.",
"phenomenon": "Why do some plants in our classroom look different from others?",
"learning_goals": "Students observe plant differences, share ideas about plant needs, and connect to cultural knowledge about plants.",
"students": {
"Maya": {
"background": "Mexican-American, bilingual, family has vegetable garden",
"cultural_assets": "Grandmother's traditional gardening knowledge, Spanish plant names",
"communication_style": "Sometimes shy in English, mixes languages naturally",
"science_thinking": "Connects plants to family cooking and traditional remedies",
"base_engagement": 3
},
"Jamal": {
"background": "African-American, urban environment, curious observer",
"cultural_assets": "Community garden experience, street tree observations",
"communication_style": "Energetic, asks lots of 'what if' questions",
"science_thinking": "Makes connections to neighborhood observations",
"base_engagement": 4
},
"Aisha": {
"background": "Somali-American, family farming background from Somalia",
"cultural_assets": "Traditional ecological knowledge, seasonal farming wisdom",
"communication_style": "Thoughtful, needs processing time before sharing",
"science_thinking": "Compares to farming practices from home country",
"base_engagement": 2
},
"Emma": {
"background": "White suburban, science-enthusiastic family",
"cultural_assets": "Access to science books and nature documentaries",
"communication_style": "Eager to share, uses formal science vocabulary",
"science_thinking": "References books and shows, sometimes dominates",
"base_engagement": 4
}
}
},
"grade4_energy": {
"title": "⚑ Grade 4: Energy Transfer Investigation",
"ngss_standard": "4-PS3-2: Energy Transfer",
"setup": "Students tested different spoon materials in hot cocoa and noticed some got hot while others stayed cool.",
"phenomenon": "Why do metal spoons get hot in hot cocoa but wooden spoons don't?",
"learning_goals": "Students explore energy transfer, connect to family cooking knowledge, and investigate material properties.",
"students": {
"Diego": {
"background": "Mexican-American, father works in auto mechanics",
"cultural_assets": "Family knowledge about tools, metal work, cooking traditions",
"communication_style": "Enthusiastic, mixes Spanish and English",
"science_thinking": "Connects to mechanical work and traditional cooking methods",
"base_engagement": 4
},
"Keisha": {
"background": "African-American, practical problem-solver, urban environment",
"cultural_assets": "Family cooking traditions, observational skills from city life",
"communication_style": "Direct, logical, asks practical questions",
"science_thinking": "Focuses on real-world applications and problem-solving",
"base_engagement": 4
},
"Ryan": {
"background": "White rural, family farm and workshop experience",
"cultural_assets": "Traditional craftsmanship, agricultural knowledge, tool use",
"communication_style": "Methodical, references family work experiences",
"science_thinking": "Connects to farming and workshop applications",
"base_engagement": 3
},
"Zara": {
"background": "Somali-American, moved from Somalia, multilingual",
"cultural_assets": "International perspective, traditional cooking methods",
"communication_style": "Careful with English, thoughtful responses",
"science_thinking": "Compares to practices from different countries",
"base_engagement": 2
}
}
},
"kindergarten_weather": {
"title": "β˜€οΈ Kindergarten: Weather Watchers",
"ngss_standard": "K-ESS2-1: Weather Patterns",
"setup": "Students noticed that puddles from yesterday's rain have disappeared, and they're curious about where the water went.",
"phenomenon": "Why do puddles disappear on sunny days?",
"learning_goals": "Students observe weather changes, share family weather knowledge, and explore water in the environment.",
"students": {
"Aaliyah": {
"background": "African-American, urban, walks to school daily",
"cultural_assets": "Daily weather observations, neighborhood knowledge",
"communication_style": "Animated storyteller, uses gestures",
"science_thinking": "Makes connections between daily observations",
"base_engagement": 4
},
"JosΓ©": {
"background": "Mexican-American, recent immigrant, bilingual family",
"cultural_assets": "Traditional weather prediction methods from Mexico",
"communication_style": "Thoughtful, mixes Spanish and English",
"science_thinking": "Connects to traditional weather wisdom",
"base_engagement": 3
},
"Emma": {
"background": "White suburban, science-enthusiastic family",
"cultural_assets": "Science books and weather apps at home",
"communication_style": "Eager, uses weather vocabulary from books",
"science_thinking": "References weather facts from media",
"base_engagement": 4
},
"Kai": {
"background": "Mixed Asian/Pacific Islander, military family, moved frequently",
"cultural_assets": "Experience with different climates and weather patterns",
"communication_style": "Observant but hesitant to share",
"science_thinking": "Compares weather across different locations",
"base_engagement": 2
}
}
},
"grade5_ecosystems": {
"title": "🌍 Grade 5: Community Ecosystem Scientists",
"ngss_standard": "5-LS2-1: Environmental Matter Cycling",
"setup": "Students visited the community garden and noticed different areas have different plants, insects, and soil conditions.",
"phenomenon": "Why do different areas of the community garden have different living things?",
"learning_goals": "Students explore ecosystem interactions, connect to community knowledge, and investigate matter cycling.",
"students": {
"Amara": {
"background": "Somali-American, family farming background",
"cultural_assets": "Traditional ecological knowledge, crop rotation understanding",
"communication_style": "Thoughtful, connects to family farming wisdom",
"science_thinking": "Sees connections between traditional and scientific knowledge",
"base_engagement": 3
},
"Carlos": {
"background": "Mexican-American, grandmother has large garden",
"cultural_assets": "Traditional companion planting, medicinal plants knowledge",
"communication_style": "Enthusiastic about sharing family knowledge",
"science_thinking": "Integrates traditional ecological practices with science",
"base_engagement": 4
},
"Destiny": {
"background": "African-American, urban nature observer",
"cultural_assets": "City ecosystem observations, community garden participation",
"communication_style": "Curious, asks detailed questions",
"science_thinking": "Notices patterns in urban environments",
"base_engagement": 4
},
"Tyler": {
"background": "White rural, hunting and outdoor experience",
"cultural_assets": "Wildlife observation, forest ecology knowledge",
"communication_style": "Practical, references outdoor experiences",
"science_thinking": "Understands predator-prey relationships and cycles",
"base_engagement": 3
},
"Mei": {
"background": "Chinese-American, recently arrived, family gardening",
"cultural_assets": "Traditional Chinese gardening, vermiculture knowledge",
"communication_style": "Hesitant with English, rich knowledge to share",
"science_thinking": "Brings international gardening perspectives",
"base_engagement": 2
}
}
}
}
def update_engagement_for_scenario(self):
"""Set engagement scores based on current scenario"""
scenario = self.scenarios[self.session["current_scenario"]]
self.session["engagement_scores"] = {}
for student_name, student_data in scenario["students"].items():
self.session["engagement_scores"][student_name] = student_data["base_engagement"]
def get_current_scenario(self):
return self.scenarios[self.session["current_scenario"]]
def change_scenario(self, scenario_key):
"""Switch to a different scenario"""
if scenario_key in self.scenarios:
self.session["current_scenario"] = scenario_key
self.session["teaching_points"] = 0
self.session["choices_made"] = []
self.session["turn_count"] = 0
self.update_engagement_for_scenario()
return True
return False
def generate_student_responses(self, teacher_input):
scenario = self.get_current_scenario()
# Create detailed context for each student
student_contexts = []
for student_name, student_data in scenario["students"].items():
engagement_level = self.session["engagement_scores"][student_name]
if engagement_level >= 4:
engagement_desc = "highly engaged and eager to participate"
elif engagement_level >= 3:
engagement_desc = "moderately engaged and willing to share"
elif engagement_level >= 2:
engagement_desc = "somewhat hesitant but paying attention"
else:
engagement_desc = "disengaged and unlikely to participate"
context = f"""**{student_name}**: {student_data['background']}. {student_data['cultural_assets']}. {student_data['communication_style']}. Currently {engagement_desc}."""
student_contexts.append(context)
prompt = f"""You are role-playing elementary students in a science discussion. The scenario is: {scenario['title']} - {scenario['setup']}
The scientific phenomenon being explored: {scenario['phenomenon']}
Students in this discussion:
{chr(10).join(student_contexts)}
The teacher just said: "{teacher_input}"
Respond as each student would, showing their authentic thinking, cultural backgrounds, and current engagement levels. Make responses feel natural for their grade level. Format your response as:
**[Student Name]:** [their response]
For each student response, show their personality, cultural knowledge, communication style, and current engagement level. Some students might not respond if they're disengaged."""
try:
response = openai.ChatCompletion.create(
model="openai/gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0.8,
max_tokens=500
)
return response.choices[0].message.content
except Exception as e:
return f"**Error generating student responses:** {str(e)}\n\nPlease check your OpenRouter API key configuration."
def generate_teaching_choices(self, scenario_context=""):
"""Generate contextual teaching choices based on current scenario"""
scenario = self.get_current_scenario()
# Base equity-focused choices
base_choices = [
"🌟 Build on cultural knowledge: Ask students about family/community knowledge related to this topic",
"🀝 Create inclusive space: Make sure quieter students get a chance to share their thinking",
"πŸ”¬ Promote scientific investigation: Guide students to plan how they could test their ideas",
"βš–οΈ Manage participation: Redirect conversation to include less dominant voices"
]
# Scenario-specific choices
scenario_specific = {
"grade2_plants": [
"🌱 Connect to home gardens: Ask about plants students have at home",
"πŸ’­ Support language learners: Encourage students to share plant names in their home languages",
"πŸ—£οΈ Facilitate peer discussion: Have students share observations with a partner first",
"πŸ“Š Document thinking: Help students draw or record what they notice"
],
"grade4_energy": [
"πŸ”§ Connect to family work: Ask about tools and materials families use",
"🍳 Explore cooking connections: Discuss heat transfer in family cooking traditions",
"πŸ§ͺ Design investigations: Help students plan controlled tests of materials",
"🌑️ Make predictions: Ask students to predict what will happen with different materials"
],
"kindergarten_weather": [
"🌀️ Share weather observations: Ask about weather patterns students notice",
"🏠 Connect to daily life: Discuss how weather affects what families do",
"πŸ‘οΈ Use senses: Encourage students to describe what they see, feel, smell",
"πŸ“… Track patterns: Help students think about when they've seen this before"
],
"grade5_ecosystems": [
"🌿 Explore traditional knowledge: Ask about family farming or gardening practices",
"πŸ”„ Investigate cycles: Help students trace matter through the ecosystem",
"🀝 Community connections: Discuss how people are part of the ecosystem",
"πŸ“‹ Systematic observation: Guide students to organize their observations"
]
}
# Combine base and scenario-specific choices
current_specific = scenario_specific.get(self.session["current_scenario"], [])
all_choices = base_choices + current_specific
# Return 4 random choices to keep it fresh
return random.sample(all_choices, min(4, len(all_choices)))
def assess_teaching_choice(self, choice_text):
"""Enhanced assessment based on scenario context"""
feedback = ""
points = 0
engagement_impact = {}
choice_lower = choice_text.lower()
scenario = self.get_current_scenario()
# Assess different types of teaching moves
if any(keyword in choice_lower for keyword in ["cultural", "family", "community", "home"]):
feedback = "🌟 Excellent! You're building on students' cultural assets. This validates their family knowledge and shows that diverse perspectives are valued in science."
points = 3
# Boost engagement for students with strong cultural assets
for student_name, student_data in scenario["students"].items():
if "traditional" in student_data["cultural_assets"].lower() or "family" in student_data["cultural_assets"].lower():
engagement_impact[student_name] = engagement_impact.get(student_name, 0) + 2
elif any(keyword in choice_lower for keyword in ["inclusive", "quieter", "all students", "everyone"]):
feedback = "βœ… Great equity move! Creating space for all voices helps build a classroom community where everyone's thinking is valued."
points = 2
# Boost engagement for lower-engaged students
for student_name, engagement in self.session["engagement_scores"].items():
if engagement <= 3:
engagement_impact[student_name] = engagement_impact.get(student_name, 0) + 2
elif any(keyword in choice_lower for keyword in ["investigate", "test", "plan", "scientific"]):
feedback = "πŸ”¬ Good scientific thinking! You're helping students engage in authentic science practices and develop investigative skills."
points = 2
# Boost engagement for science-oriented students
for student_name, student_data in scenario["students"].items():
if "science" in student_data["science_thinking"].lower():
engagement_impact[student_name] = engagement_impact.get(student_name, 0) + 1
elif any(keyword in choice_lower for keyword in ["manage", "redirect", "less dominant"]):
feedback = "βš–οΈ Important facilitation move! Managing participation ensures equitable discourse and gives space for all students to contribute."
points = 2
# Reduce dominance, increase participation for others
for student_name, engagement in self.session["engagement_scores"].items():
if engagement >= 4:
engagement_impact[student_name] = engagement_impact.get(student_name, 0) - 1
else:
engagement_impact[student_name] = engagement_impact.get(student_name, 0) + 1
elif any(keyword in choice_lower for keyword in ["language", "home languages", "spanish", "multilingual"]):
feedback = "πŸ—£οΈ Wonderful! Supporting multilingual learners by honoring their home languages strengthens both their science learning and cultural identity."
points = 3
# Major boost for multilingual students
for student_name, student_data in scenario["students"].items():
if "bilingual" in student_data["background"].lower() or "language" in student_data["communication_style"].lower():
engagement_impact[student_name] = engagement_impact.get(student_name, 0) + 3
else:
feedback = "Consider how this choice supports equitable participation and builds on student thinking."
points = 1
return feedback, points, engagement_impact
def update_engagement(self, impact_dict):
"""Update student engagement based on teaching choices"""
for student, change in impact_dict.items():
if student in self.session["engagement_scores"]:
current = self.session["engagement_scores"][student]
new_score = max(1, min(5, current + change))
self.session["engagement_scores"][student] = new_score
def get_engagement_display(self):
"""Create visual representation of student engagement"""
scenario = self.get_current_scenario()
display = "## πŸ“Š Student Engagement Levels\n\n"
for student_name, score in self.session["engagement_scores"].items():
student_info = scenario["students"][student_name]
stars = "⭐" * score + "β˜†" * (5-score)
display += f"**{student_name}** ({student_info['background'].split(',')[0]}): {stars} ({score}/5)\n\n"
return display
def get_scenario_info(self):
scenario = self.get_current_scenario()
info = f"""## {scenario['title']}
**NGSS Standard:** {scenario['ngss_standard']}
**Situation:** {scenario['setup']}
**Phenomenon:** {scenario['phenomenon']}
**Learning Goals:** {scenario['learning_goals']}
**Your Students:**"""
for student_name, student_data in scenario["students"].items():
info += f"\n- **{student_name}:** {student_data['background']} - {student_data['cultural_assets']}"
return info
def get_available_scenarios(self):
"""Return list of available scenarios for selection"""
return [(key, data["title"]) for key, data in self.scenarios.items()]
# Initialize game
game = ScienceTalkGame()
def change_scenario(scenario_choice):
"""Handle scenario change"""
# Extract scenario key from the choice (format: "key: title")
scenario_key = scenario_choice.split(":")[0].strip()
if game.change_scenario(scenario_key):
scenario_info = game.get_scenario_info()
engagement_display = game.get_engagement_display()
points_display = f"**Teaching Points:** {game.session['teaching_points']}"
welcome_msg = f"""## Welcome to {game.get_current_scenario()['title']}! 🌟
**Instructions:** Type what you would say to start this science discussion, then click 'Send Response' to see how your students react."""
return scenario_info, engagement_display, welcome_msg, points_display, "", ""
else:
return "Error changing scenario", "", "", "", "", ""
def start_new_session():
"""Reset the current scenario"""
game.reset_session()
scenario_info = game.get_scenario_info()
engagement_display = game.get_engagement_display()
welcome_msg = f"""## Welcome to {game.get_current_scenario()['title']}! 🌟
You're about to practice facilitating an equitable science discussion. Your goals:
1. **Include all student voices** - especially those who might be hesitant
2. **Build on cultural assets** - honor knowledge students bring from home
3. **Promote scientific thinking** - help students observe, question, investigate
4. **Manage participation** - ensure discussions are equitable
**To begin:** Type what you would say to start the science talk."""
return scenario_info, engagement_display, welcome_msg, f"**Teaching Points:** {game.session['teaching_points']}", ""
def process_teacher_input(teacher_input):
"""Process teacher's response and generate student reactions"""
if not teacher_input or not teacher_input.strip():
return "Please enter what you would say to your students.", "", ""
# Generate student responses
student_response = game.generate_student_responses(teacher_input)
# Generate teaching choice options
choices = game.generate_teaching_choices()
choice_display = "## 🎯 How do you respond? Choose your next move:\n\n"
for i, choice in enumerate(choices, 1):
choice_display += f"**Option {i}:** {choice}\n\n"
game.session["turn_count"] += 1
return student_response, choice_display, ""
def handle_teaching_choice(choice_num):
"""Process the selected teaching choice"""
if choice_num < 1 or choice_num > 4:
return "Please select a valid choice.", "", ""
choices = game.generate_teaching_choices()
if choice_num <= len(choices):
selected_choice = choices[choice_num - 1]
feedback, points, engagement_impact = game.assess_teaching_choice(selected_choice)
game.session["teaching_points"] += points
game.update_engagement(engagement_impact)
game.session["choices_made"].append({
"choice": selected_choice,
"points": points,
"turn": game.session["turn_count"]
})
updated_engagement = game.get_engagement_display()
points_display = f"**Teaching Points:** {game.session['teaching_points']}"
return feedback, updated_engagement, points_display
return "Choice not found.", "", ""
# Create Gradio interface
def create_interface():
with gr.Blocks(title="Science Talk Adventure", theme=gr.themes.Soft()) as interface:
gr.Markdown("# 🌱 Science Talk Adventure: Multi-Scenario Practice")
gr.Markdown("*Practice equitable science discussions across multiple grade levels and topics*")
with gr.Row():
with gr.Column(scale=2):
# Scenario selection
scenario_choices = [f"{key}: {data['title']}" for key, data in game.scenarios.items()]
scenario_selector = gr.Dropdown(
choices=scenario_choices,
value=f"{game.session['current_scenario']}: {game.get_current_scenario()['title']}",
label="🎭 Choose Your Scenario",
interactive=True
)
scenario_info = gr.Markdown(game.get_scenario_info())
conversation_area = gr.Markdown("*Students will respond here after you speak...*")
teacher_input = gr.Textbox(
label="πŸ’¬ What do you say to your students?",
placeholder="Type your response here...",
lines=3
)
send_btn = gr.Button("Send Response", variant="primary", size="lg")
choices_display = gr.Markdown("")
with gr.Row():
choice1_btn = gr.Button("Choice 1", variant="secondary")
choice2_btn = gr.Button("Choice 2", variant="secondary")
choice3_btn = gr.Button("Choice 3", variant="secondary")
choice4_btn = gr.Button("Choice 4", variant="secondary")
feedback_display = gr.Markdown("")
with gr.Column(scale=1):
engagement_display = gr.Markdown(game.get_engagement_display())
points_display = gr.Markdown(f"**Teaching Points:** {game.session['teaching_points']}")
gr.Markdown("---")
restart_btn = gr.Button("πŸ”„ Restart Current Scenario", variant="outline")
gr.Markdown("""
### πŸ’‘ Tips for Success:
- **Listen for cultural knowledge** students share
- **Create wait time** for all students to think
- **Build on student ideas** rather than correcting immediately
- **Notice participation patterns** and include all voices
- **Connect to students' lived experiences**
### 🎯 Available Scenarios:
- **Kindergarten:** Weather patterns and observations
- **Grade 2:** Plant growth and needs
- **Grade 4:** Energy transfer in everyday materials
- **Grade 5:** Community ecosystem interactions
""")
# Event handlers
scenario_selector.change(
change_scenario,
inputs=[scenario_selector],
outputs=[scenario_info, engagement_display, conversation_area, points_display, feedback_display, choices_display]
)
restart_btn.click(
start_new_session,
outputs=[scenario_info, engagement_display, conversation_area, points_display, feedback_display]
)
send_btn.click(
process_teacher_input,
inputs=[teacher_input],
outputs=[conversation_area, choices_display, feedback_display]
)
choice1_btn.click(lambda: handle_teaching_choice(1), outputs=[feedback_display, engagement_display, points_display])
choice2_btn.click(lambda: handle_teaching_choice(2), outputs=[feedback_display, engagement_display, points_display])
choice3_btn.click(lambda: handle_teaching_choice(3), outputs=[feedback_display, engagement_display, points_display])
choice4_btn.click(lambda: handle_teaching_choice(4), outputs=[feedback_display, engagement_display, points_display])
return interface
# Launch the app
if __name__ == "__main__":
demo = create_interface()
demo.launch()