Spaces:
Sleeping
Sleeping
| """ | |
| 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 | | |
| 🎁 <b>Free credits:</b> $15 on signup at | |
| <a href="https://fal.ai" target="_blank" style="color:#a5b4fc">fal.ai</a> | |
| (~55 free videos) | | |
| ⏱ <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()) |