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()