File size: 6,745 Bytes
bdf8af1
36177f4
 
 
 
 
 
 
 
bdf8af1
 
 
 
 
36177f4
 
bdf8af1
36177f4
 
 
bdf8af1
36177f4
 
 
 
 
 
bdf8af1
 
36177f4
 
bdf8af1
36177f4
 
 
 
 
 
 
 
 
 
 
 
bdf8af1
 
 
 
 
 
36177f4
bdf8af1
 
 
 
 
36177f4
 
bdf8af1
 
36177f4
bdf8af1
 
 
 
 
 
 
 
 
 
36177f4
bdf8af1
 
 
 
 
36177f4
bdf8af1
 
 
36177f4
bdf8af1
36177f4
bdf8af1
36177f4
 
 
 
 
 
bdf8af1
 
36177f4
 
bdf8af1
36177f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bdf8af1
36177f4
bdf8af1
 
 
 
 
 
 
36177f4
bdf8af1
 
36177f4
bdf8af1
 
 
36177f4
bdf8af1
 
 
36177f4
bdf8af1
 
 
36177f4
bdf8af1
 
 
36177f4
bdf8af1
 
 
 
 
 
36177f4
 
 
bdf8af1
 
36177f4
 
bdf8af1
36177f4
 
bdf8af1
 
 
 
 
 
 
 
 
36177f4
 
bdf8af1
 
 
 
 
36177f4
bdf8af1
 
36177f4
bdf8af1
 
 
36177f4
bdf8af1
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
"""
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