""" AI Engine – uses Groq (free tier) for LLM calls + gTTS for audio. Setup on Hugging Face Spaces: Settings → Repository secrets → Add: Name : GROQ_API_KEY Value: your key from https://console.groq.com (free, no credit card) Free Groq model used: llama-3.3-70b-versatile """ import os import json import re from groq import Groq # ── Client ─────────────────────────────────────────────────────────────────── def get_client() -> Groq: api_key = os.environ.get("GROQ_API_KEY", "") if not api_key: raise ValueError( "GROQ_API_KEY secret not set.\n" "Go to: Space Settings → Repository secrets → add GROQ_API_KEY\n" "Get a free key at: https://console.groq.com" ) return Groq(api_key=api_key) def _chat(prompt: str, max_tokens: int = 1024) -> str: """Single-turn chat with llama-3.3-70b on Groq.""" client = get_client() resp = client.chat.completions.create( model="llama-3.3-70b-versatile", messages=[{"role": "user", "content": prompt}], max_tokens=max_tokens, temperature=0.7, ) return resp.choices[0].message.content # ── Experiment Explanation ──────────────────────────────────────────────────── def generate_experiment_explanation(exp: dict) -> str: prompt = f"""You are an enthusiastic science teacher for Class 10 students (age ~15). Experiment: {exp['title']} Materials: {exp['materials']} Steps: {exp['steps']} Outcome: {exp['outcome']} Write a clear, engaging explanation (250–350 words) covering: 1. **What is happening scientifically** – the core concept 2. **Why it works** – the chemistry/biology/physics behind it 3. **Real-world connection** – where students see this in daily life 4. **Key formula or equation** if applicable (use simple notation) Use friendly, enthusiastic language suitable for a 15-year-old.""" return _chat(prompt, max_tokens=700) # ── Quiz Generator ──────────────────────────────────────────────────────────── def generate_quiz_questions(exp: dict) -> list: prompt = f"""You are a science quiz creator for Class 10 students. Experiment: {exp['title']} Materials: {exp['materials']} Steps: {exp['steps']} Outcome: {exp['outcome']} Generate exactly 5 multiple-choice questions testing understanding of this experiment. Return ONLY a valid JSON array — no markdown fences, no extra text — in this exact format: [ {{ "question": "Question text here?", "options": ["A) Option 1", "B) Option 2", "C) Option 3", "D) Option 4"], "answer": "A) Option 1", "explanation": "Brief reason why this answer is correct." }} ] Cover: observation, concept, reasoning, safety, and real-world application.""" raw = _chat(prompt, max_tokens=1400) raw = re.sub(r"```json|```", "", raw).strip() # Find the JSON array inside the response match = re.search(r"\[.*\]", raw, re.DOTALL) if match: raw = match.group(0) try: questions = json.loads(raw) if isinstance(questions, list) and questions: return questions except json.JSONDecodeError: pass # Fallback single question return [{ "question": f"What is the main observation in '{exp['title']}'?", "options": [ f"A) {exp['outcome'][:70]}", "B) No reaction takes place", "C) The mixture turns blue", "D) Heat is always absorbed", ], "answer": f"A) {exp['outcome'][:70]}", "explanation": exp["outcome"], }] # ── Video Script Generator ──────────────────────────────────────────────────── def generate_video_script(exp: dict) -> str: prompt = f"""You are a scriptwriter creating a 2-minute science video for Class 10 students. Experiment: {exp['title']} Materials: {exp['materials']} Procedure: {exp['steps']} Safety: {exp['safety']} Outcome: {exp['outcome']} Write a complete VIDEO SCRIPT with these six scenes: 🎬 SCENE 1 – HOOK (0:00–0:15) [Camera/action] Narrator: "..." 🎬 SCENE 2 – MATERIALS (0:15–0:30) [Visual: each material shown] Narrator: "..." 🎬 SCENE 3 – SAFETY BRIEFING (0:30–0:40) [Safety icons / lab coat] Narrator: "..." 🎬 SCENE 4 – STEP-BY-STEP PROCEDURE (0:40–1:20) [Camera angle + action per step] Narrator: "..." 🎬 SCENE 5 – OBSERVATION & RESULT (1:20–1:45) [Close-up of the change/outcome] Narrator: "..." 🎬 SCENE 6 – SCIENCE EXPLANATION (1:45–2:00) [Animation or diagram] Narrator: "..." Include [ANIMATION: ...], [CLOSE-UP: ...], and [TEXT ON SCREEN: ...] cues. Narrator text must be enthusiastic and clear for 15-year-old students.""" return _chat(prompt, max_tokens=1200) # ── Text-to-Speech ──────────────────────────────────────────────────────────── def text_to_speech_explanation(exp: dict, script: str = None): """ Converts narrator lines from the script to MP3 using gTTS (free, no API key). Returns the path to the MP3 file, or None on failure. """ try: from gtts import gTTS import tempfile if script: lines = [] for line in script.split("\n"): line = line.strip() if line.lower().startswith("narrator:"): text = line[9:].strip().strip('"').strip("'") if text: lines.append(text) audio_text = " ".join(lines) if lines else script else: audio_text = ( f"Welcome to the AI Science Lab. Today we explore: {exp['title']}. " f"Materials needed: {exp['materials']}. " f"Procedure: {exp['steps']}. " f"Safety: {exp['safety']}. " f"Expected outcome: {exp['outcome']}." ) audio_text = audio_text[:3000] # gTTS limit tts = gTTS(text=audio_text, lang="en", slow=False) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") tts.save(tmp.name) return tmp.name except ImportError: return None except Exception as e: print(f"TTS error: {e}") return None