Spaces:
Sleeping
Sleeping
| """ | |
| 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 |