AISCienceLab / app.py
swapnakumbar12's picture
Update app.py
fb60697 verified
Raw
History Blame Contribute Delete
13.7 kB
"""
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())