File size: 13,749 Bytes
3343e73
 
fb60697
81e7a69
3343e73
 
05bf457
3343e73
 
81e7a69
fb60697
3343e73
05bf457
3343e73
81e7a69
3343e73
81e7a69
3343e73
 
 
81e7a69
3343e73
 
81e7a69
3343e73
 
 
81e7a69
 
 
 
3343e73
 
81e7a69
3343e73
 
 
81e7a69
3343e73
81e7a69
 
3343e73
fb60697
81e7a69
3343e73
 
81e7a69
 
fb60697
 
22baf23
fb60697
05bf457
81e7a69
fb60697
3343e73
81e7a69
 
 
 
 
 
fb60697
81e7a69
 
 
3343e73
 
 
 
 
 
 
81e7a69
3343e73
81e7a69
22baf23
3343e73
 
 
 
 
81e7a69
3343e73
81e7a69
3343e73
 
81e7a69
 
 
3343e73
81e7a69
05bf457
3343e73
 
fb60697
81e7a69
 
 
 
fb60697
 
81e7a69
05bf457
81e7a69
22baf23
05bf457
3343e73
 
05bf457
3343e73
81e7a69
 
05bf457
3343e73
81e7a69
3343e73
81e7a69
3343e73
81e7a69
3343e73
 
 
 
81e7a69
22baf23
3343e73
 
 
 
 
 
22baf23
3343e73
81e7a69
 
3343e73
 
81e7a69
 
fb60697
 
 
3343e73
 
 
 
 
05bf457
 
81e7a69
3343e73
565cc46
3343e73
 
 
 
fb60697
3343e73
81e7a69
 
fb60697
 
81e7a69
3343e73
 
 
 
 
81e7a69
 
 
 
3343e73
 
 
81e7a69
3343e73
81e7a69
3343e73
81e7a69
 
 
 
3343e73
81e7a69
 
 
fb60697
81e7a69
 
fb60697
 
 
 
 
 
 
 
 
81e7a69
 
fb60697
22baf23
81e7a69
 
 
fb60697
81e7a69
fb60697
81e7a69
 
 
 
 
 
3343e73
fb60697
 
 
81e7a69
3343e73
81e7a69
3343e73
fb60697
81e7a69
 
 
3343e73
81e7a69
 
3343e73
81e7a69
 
 
 
 
 
 
 
 
 
3343e73
22baf23
81e7a69
3343e73
81e7a69
3343e73
22baf23
 
3343e73
 
81e7a69
fb60697
 
3343e73
 
 
81e7a69
 
fb60697
81e7a69
3343e73
 
565cc46
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
"""
AI Science Lab – Hugging Face Space
NCERT Class 10 Science: 75 experiments with fal.ai AI videos,
audio narration, AI explanations and smart quizzes.
"""

import os
import gradio as gr
from experiment_data import EXPERIMENTS
from ai_engine import generate_experiment_explanation, generate_quiz_questions, text_to_speech_explanation
from fal_video_generator import generate_experiment_video

# ── Helpers ───────────────────────────────────────────────────────────────────
def get_chapter_labels():
    return [f"Chapter {c}" for c in sorted(set(e["chapter"] for e in EXPERIMENTS))]

def get_experiments_for_chapter(chapter_label):
    ch = int(chapter_label.split(" ")[1])
    return [e["title"] for e in EXPERIMENTS if e["chapter"] == ch]

def get_experiment(title):
    return next((e for e in EXPERIMENTS if e["title"] == title), None)

# ── Tab 1: Details ────────────────────────────────────────────────────────────
def load_experiment(title):
    exp = get_experiment(title)
    if not exp:
        return "### Select an experiment", "", "", "", ""
    mats  = "\n".join(f"β€’ {m.strip()}" for m in exp["materials"].split(","))
    parts = [s.strip() for s in exp["steps"].split(".") if s.strip()]
    steps = "\n".join(f"{i+1}. {s}" for i, s in enumerate(parts))
    return (
        f"### {exp['title']}\n*Page {exp['page']} Β· Chapter {exp['chapter']}*",
        mats, steps, exp["safety"], exp["outcome"]
    )

def on_chapter_change(chapter_label):
    exps  = get_experiments_for_chapter(chapter_label)
    first = exps[0] if exps else None
    det   = load_experiment(first) if first else ("", "", "", "", "")
    return gr.Dropdown(choices=exps, value=first), *det

# ── Tab 2: fal.ai Video ───────────────────────────────────────────────────────
def generate_video(title, progress=gr.Progress()):
    exp = get_experiment(title)
    if not exp:
        return None, "⚠️ Please select an experiment first."

    progress(0.05, desc="Generating video prompt with Groq LLM...")
    progress(0.15, desc="Submitting to fal.ai MiniMax Hailuo-02...")

    video_path, status = generate_experiment_video(exp)

    progress(1.0, desc="Done!")
    return video_path, status

# ── Tab 3: Audio ──────────────────────────────────────────────────────────────
def get_audio(title):
    exp = get_experiment(title)
    if not exp:
        return None, "⚠️ Please select an experiment first."
    audio = text_to_speech_explanation(exp)
    status = f"πŸ”Š Audio ready for: **{exp['title']}**" if audio else "❌ Audio generation failed."
    return audio, status

# ── Tab 4: AI Explanation ─────────────────────────────────────────────────────
def get_explanation(title):
    exp = get_experiment(title)
    if not exp:
        return "⚠️ Please select an experiment first."
    try:
        return generate_experiment_explanation(exp)
    except Exception as e:
        return f"❌ Error: {e}"

# ── Tab 5: Quiz ───────────────────────────────────────────────────────────────
_quiz = {}

def start_quiz(title):
    exp = get_experiment(title)
    if not exp:
        empty = gr.update(visible=False)
        return "⚠️ Select an experiment first.", empty, empty, empty, empty, ""
    try:
        qs = generate_quiz_questions(exp)
    except Exception as e:
        empty = gr.update(visible=False)
        return f"❌ {e}", empty, empty, empty, empty, ""
    _quiz.update({"questions": qs, "current": 0, "score": 0})
    return _render_q()

def _render_q():
    qs  = _quiz.get("questions", [])
    idx = _quiz.get("current", 0)
    if idx >= len(qs):
        s   = _quiz.get("score", 0); t = len(qs)
        pct = int(s / t * 100) if t else 0
        em  = "πŸ†" if pct == 100 else ("πŸ‘" if pct >= 70 else "πŸ“š")
        msg = f"## {em} Quiz Complete!\n\n**Score: {s}/{t} ({pct}%)**\n\n"
        msg += "Perfect!" if pct==100 else ("Great job!" if pct>=70 else "Review and try again!")
        return msg, gr.update(visible=False), gr.update(visible=False), \
               gr.update(visible=False), gr.update(visible=False), ""
    q    = qs[idx]; opts = q["options"]
    hdr  = f"**Question {idx+1} of {len(qs)}**\n\n{q['question']}"
    btns = [gr.update(visible=True, value=opts[i]) if i < len(opts)
            else gr.update(visible=False) for i in range(4)]
    return hdr, *btns, ""

def answer(choice):
    qs  = _quiz.get("questions", [])
    idx = _quiz.get("current", 0)
    if idx >= len(qs): return _render_q()
    q = qs[idx]; correct = q["answer"].strip()
    if choice.strip() == correct:
        _quiz["score"] = _quiz.get("score", 0) + 1
        fb = f"βœ… **Correct!** {q.get('explanation','')}"
    else:
        fb = f"❌ **Incorrect.** Answer: **{correct}**\n\n{q.get('explanation','')}"
    _quiz["current"] = idx + 1
    q_text, b1, b2, b3, b4, _ = _render_q()
    return q_text, b1, b2, b3, b4, fb

# ── CSS ───────────────────────────────────────────────────────────────────────
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&display=swap');
*, body, .gradio-container { font-family: 'Space Grotesk', sans-serif !important; }
.banner {
    background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
    border-radius: 20px; padding: 2.5rem 2rem;
    text-align: center; margin-bottom: 1.5rem;
    border: 1px solid rgba(255,255,255,0.08);
}
.banner h1 { color: #fff; font-size: 2.4rem; font-weight: 700; margin: 0; }
.banner h1 span { color: #7c3aed; }
.banner p { color: #a5b4fc; margin: 0.5rem 0 0; }
.chip {
    display: inline-block; background: rgba(124,58,237,0.15);
    border: 1px solid rgba(124,58,237,0.4); border-radius: 999px;
    padding: 0.2rem 0.8rem; font-size: 0.82rem; color: #c4b5fd; margin: 0.4rem 0.2rem 0;
}
.info-box {
    background: rgba(124,58,237,0.08); border: 1px solid rgba(124,58,237,0.3);
    border-radius: 10px; padding: 0.8rem 1rem; margin-bottom: 1rem; font-size: 0.9rem;
}
"""

# ── Build UI ──────────────────────────────────────────────────────────────────
initial_chapter = "Chapter 1"
initial_exps    = get_experiments_for_chapter(initial_chapter)
initial_exp     = initial_exps[0]
init_det        = load_experiment(initial_exp)

with gr.Blocks(title="AI Science Lab") as demo:

    gr.HTML("""
    <div class="banner">
        <h1>πŸ”¬ AI <span>Science</span> Lab</h1>
        <p>NCERT Class 10 Β· fal.ai MiniMax Hailuo-02 Videos Β· Audio Narration Β· AI Quizzes</p>
        <div>
            <span class="chip">πŸ“š 10 Chapters</span>
            <span class="chip">πŸ§ͺ 75 Experiments</span>
            <span class="chip">🎬 fal.ai Hailuo-02</span>
            <span class="chip">πŸ€– Groq AI Prompts</span>
            <span class="chip">πŸ”Š gTTS Audio</span>
        </div>
    </div>
    """)

    with gr.Row():
        chapter_dd = gr.Dropdown(choices=get_chapter_labels(), value=initial_chapter,
                                  label="πŸ“š Chapter", scale=1, interactive=True)
        exp_dd     = gr.Dropdown(choices=initial_exps, value=initial_exp,
                                  label="πŸ§ͺ Experiment", scale=4, interactive=True)

    with gr.Tabs():

        # ── Tab 1: Details ────────────────────────────────────────────────────
        with gr.Tab("πŸ“‹ Experiment Details"):
            title_md = gr.Markdown(init_det[0])
            with gr.Row():
                mat_box  = gr.Textbox(label="🧰 Materials", value=init_det[1],
                                       lines=7, interactive=False)
                step_box = gr.Textbox(label="πŸ“ Steps",     value=init_det[2],
                                       lines=7, interactive=False)
            with gr.Row():
                safe_box = gr.Textbox(label="⚠️ Safety",  value=init_det[3], interactive=False)
                out_box  = gr.Textbox(label="βœ… Outcome",  value=init_det[4], interactive=False)

        # ── Tab 2: fal.ai Video ───────────────────────────────────────────────
        with gr.Tab("🎬 AI Experiment Video"):
            gr.HTML("""
            <div class="info-box">
                🎬 <b>How it works:</b>
                Groq AI <b>auto-generates a cinematic video prompt</b> from the experiment data β†’
                fal.ai <b>MiniMax Hailuo-02</b> renders a real 6-second HD experiment video.<br><br>
                πŸ’° <b>Cost:</b> ~$0.27 per video &nbsp;|&nbsp;
                🎁 <b>Free credits:</b> $15 on signup at
                <a href="https://fal.ai" target="_blank" style="color:#a5b4fc">fal.ai</a>
                (~55 free videos) &nbsp;|&nbsp;
                ⏱ <b>Wait time:</b> ~3-4 minutes
            </div>
            """)
            vid_btn    = gr.Button("🎬 Generate AI Experiment Video (~3-4 min)", variant="primary")
            vid_status = gr.Markdown()
            video_out  = gr.Video(label="🎬 AI Generated Experiment Video", interactive=False)
            vid_btn.click(generate_video, inputs=exp_dd, outputs=[video_out, vid_status])

        # ── Tab 3: Audio ──────────────────────────────────────────────────────
        with gr.Tab("πŸ”Š Audio Narration"):
            gr.Markdown("Generate a **spoken audio explanation** of the experiment (free, instant via gTTS).")
            audio_btn    = gr.Button("πŸ”Š Generate Audio Narration", variant="primary")
            audio_status = gr.Markdown()
            audio_out    = gr.Audio(label="πŸ”Š Audio Narration", type="filepath")
            audio_btn.click(get_audio, inputs=exp_dd, outputs=[audio_out, audio_status])

        # ── Tab 4: AI Explanation ─────────────────────────────────────────────
        with gr.Tab("πŸ€– AI Explanation"):
            gr.Markdown("Get a **detailed AI explanation** of the science behind this experiment (via Groq).")
            exp_btn = gr.Button("πŸ€– Explain This Experiment", variant="primary")
            exp_md  = gr.Markdown("Click the button above to generate an explanation.")
            exp_btn.click(get_explanation, inputs=exp_dd, outputs=exp_md)

        # ── Tab 5: Quiz ───────────────────────────────────────────────────────
        with gr.Tab("πŸ“ Quiz"):
            gr.Markdown("Test your understanding with **5 AI-generated MCQs** (via Groq).")
            quiz_btn = gr.Button("πŸš€ Start Quiz", variant="primary")
            q_md     = gr.Markdown("Click **Start Quiz** to begin!")
            fb_md    = gr.Markdown()
            with gr.Row():
                b_a = gr.Button(visible=False)
                b_b = gr.Button(visible=False)
            with gr.Row():
                b_c = gr.Button(visible=False)
                b_d = gr.Button(visible=False)
            qouts = [q_md, b_a, b_b, b_c, b_d, fb_md]
            quiz_btn.click(start_quiz, inputs=exp_dd, outputs=qouts)
            b_a.click(answer, inputs=b_a, outputs=qouts)
            b_b.click(answer, inputs=b_b, outputs=qouts)
            b_c.click(answer, inputs=b_c, outputs=qouts)
            b_d.click(answer, inputs=b_d, outputs=qouts)

        # ── Tab 6: All Experiments ────────────────────────────────────────────
        with gr.Tab("πŸ“Š All Experiments"):
            summary = ""
            for ch in sorted(set(e["chapter"] for e in EXPERIMENTS)):
                exps = [e for e in EXPERIMENTS if e["chapter"] == ch]
                summary += f"\n### Chapter {ch} β€” {len(exps)} experiments\n"
                for e in exps:
                    summary += f"- {e['title']} *(p.{e['page']})*\n"
            gr.Markdown(summary)

    gr.HTML("""
    <div style="text-align:center;color:#6b7280;font-size:0.82rem;padding:1.2rem 0 0.4rem">
        πŸ”¬ AI Science Lab Β· NCERT Class 10 Β· Videos by fal.ai + MiniMax Hailuo-02 Β·
        Prompts by Groq Llama 3.3-70B Β· Audio by gTTS
    </div>
    """)

    chapter_dd.change(on_chapter_change, inputs=chapter_dd,
                      outputs=[exp_dd, title_md, mat_box, step_box, safe_box, out_box])
    exp_dd.change(load_experiment, inputs=exp_dd,
                  outputs=[title_md, mat_box, step_box, safe_box, out_box])

if __name__ == "__main__":
    demo.launch(share=False, css=CSS, theme=gr.themes.Soft())