Spaces:
Sleeping
Sleeping
#!/usr/bin/env python3 | |
""" | |
Theorem Explanation Agent - Gradio Interface for Hugging Face Spaces | |
A web interface for generating educational videos explaining mathematical theorems and concepts. | |
""" | |
import os | |
import sys | |
import json | |
import traceback | |
import tempfile | |
import shutil | |
import asyncio | |
import threading | |
import time | |
import random | |
from typing import Optional, List, Dict, Any, Tuple | |
from pathlib import Path | |
from datetime import datetime | |
import gradio as gr | |
import zipfile | |
# Add the project root to Python path | |
project_root = Path(__file__).parent | |
sys.path.insert(0, str(project_root)) | |
# Environment setup for Hugging Face Spaces | |
SPACE_ID = os.getenv("SPACE_ID", "") | |
HF_TOKEN = os.getenv("HF_TOKEN", "") | |
DEMO_MODE = os.getenv("DEMO_MODE", "true").lower() == "true" | |
# Global variables | |
video_generator = None | |
generation_status = {} | |
CAN_IMPORT_DEPENDENCIES = True | |
def setup_environment(): | |
"""Setup environment variables and dependencies for Hugging Face Spaces.""" | |
print("🚀 Setting up Theorem Explanation Agent...") | |
# Check for API keys | |
gemini_keys = os.getenv("GEMINI_API_KEY", "") | |
if gemini_keys: | |
key_count = len([k.strip() for k in gemini_keys.split(',') if k.strip()]) | |
print(f"✅ Found {key_count} Gemini API key(s)") | |
else: | |
print("⚠️ No Gemini API keys found - running in demo mode") | |
# Check for optional environment variables | |
elevenlabs_key = os.getenv("ELEVENLABS_API_KEY", "") | |
if elevenlabs_key: | |
print("✅ ElevenLabs API key found") | |
else: | |
print("⚠️ No ElevenLabs API key found - TTS will be disabled") | |
return True | |
def initialize_video_generator(): | |
"""Initialize the video generator with error handling.""" | |
global video_generator, CAN_IMPORT_DEPENDENCIES | |
try: | |
if DEMO_MODE: | |
return "✅ Demo mode enabled - Video generation will be simulated" | |
# Try importing dependencies | |
from generate_video import VideoGenerator | |
from mllm_tools.litellm import LiteLLMWrapper | |
# Initialize models | |
planner_model = LiteLLMWrapper( | |
model_name="gemini/gemini-2.0-flash", | |
temperature=0.7, | |
print_cost=True, | |
verbose=False, | |
use_langfuse=False | |
) | |
helper_model = LiteLLMWrapper( | |
model_name="gemini/gemini-2.0-flash", | |
temperature=0.7, | |
print_cost=True, | |
verbose=False, | |
use_langfuse=False | |
) | |
video_generator = VideoGenerator( | |
planner_model=planner_model, | |
helper_model=helper_model, | |
scene_model=helper_model, | |
output_dir="output", | |
use_rag=False, | |
use_context_learning=False, | |
use_visual_fix_code=False, | |
verbose=False | |
) | |
return "✅ Video generator initialized successfully with Gemini models" | |
except ImportError as e: | |
CAN_IMPORT_DEPENDENCIES = False | |
print(f"Import error: {e}") | |
return f"⚠️ Missing dependencies - running in demo mode: {str(e)}" | |
except Exception as e: | |
CAN_IMPORT_DEPENDENCIES = False | |
print(f"Initialization error: {e}") | |
return f"⚠️ Failed to initialize - running in demo mode: {str(e)}" | |
def simulate_video_generation(topic: str, context: str, max_scenes: int, progress_callback=None) -> Dict[str, Any]: | |
"""Simulate video generation for demo purposes with progress updates.""" | |
stages = [ | |
("🔍 Analyzing topic and context", 10), | |
("📝 Planning video structure", 25), | |
("🎬 Generating scene outlines", 45), | |
("✨ Creating animations", 70), | |
("🎥 Rendering videos", 85), | |
("🔗 Combining scenes", 95), | |
("✅ Finalizing output", 100) | |
] | |
results = [] | |
for stage, progress in stages: | |
if progress_callback: | |
progress_callback(progress, stage) | |
time.sleep(random.uniform(0.2, 0.8)) # Simulate processing time | |
results.append(f"• {stage}") | |
return { | |
"success": True, | |
"message": f"Demo video generated for topic: {topic}", | |
"scenes_created": max_scenes, | |
"total_duration": f"{max_scenes * 45} seconds", | |
"processing_steps": results, | |
"output_files": [ | |
f"scene_{i+1}.mp4" for i in range(max_scenes) | |
] + ["combined_video.mp4"], | |
"demo_note": "This is a simulated result for demonstration purposes." | |
} | |
async def generate_video_async(topic: str, context: str, max_scenes: int, progress_callback=None): | |
"""Asynchronously generate video with progress updates.""" | |
global video_generator | |
if not topic.strip(): | |
return {"success": False, "error": "Please enter a topic to explain"} | |
try: | |
if DEMO_MODE or not CAN_IMPORT_DEPENDENCIES: | |
return simulate_video_generation(topic, context, max_scenes, progress_callback) | |
# Real video generation | |
if progress_callback: | |
progress_callback(10, "🚀 Starting video generation...") | |
result = await video_generator.generate_video_pipeline( | |
topic=topic, | |
description=context, | |
max_retries=3, | |
only_plan=False, | |
specific_scenes=list(range(1, max_scenes + 1)), | |
only_render=False, | |
only_combine=False | |
) | |
if progress_callback: | |
progress_callback(100, "✅ Video generation completed!") | |
return { | |
"success": True, | |
"message": f"Video generated successfully for topic: {topic}", | |
"result": result | |
} | |
except Exception as e: | |
error_msg = f"Error during generation: {str(e)}" | |
print(f"Generation error: {traceback.format_exc()}") | |
return {"success": False, "error": error_msg} | |
def generate_video_gradio(topic: str, context: str, max_scenes: int, progress=gr.Progress()) -> Tuple[str, str]: | |
"""Main function called by Gradio interface.""" | |
def progress_callback(percent, message): | |
progress(percent / 100, desc=message) | |
# Run async function in sync context | |
loop = asyncio.new_event_loop() | |
asyncio.set_event_loop(loop) | |
try: | |
result = loop.run_until_complete( | |
generate_video_async(topic, context, max_scenes, progress_callback) | |
) | |
finally: | |
loop.close() | |
if result["success"]: | |
output = f"""# 🎓 Video Generation Complete! | |
## 📋 Generation Details | |
- **Topic:** {topic} | |
- **Context:** {context if context else "None provided"} | |
- **Max Scenes:** {max_scenes} | |
## ✅ Results | |
{result["message"]} | |
""" | |
if "processing_steps" in result: | |
output += "## 🔄 Processing Steps\n" | |
for step in result["processing_steps"]: | |
output += f"{step}\n" | |
output += "\n" | |
if "output_files" in result: | |
output += "## 📁 Generated Files\n" | |
for file in result["output_files"]: | |
output += f"• {file}\n" | |
output += "\n" | |
if "demo_note" in result: | |
output += f"## ⚠️ Demo Mode\n{result['demo_note']}\n\n" | |
status = "🎮 Demo mode - Simulation completed successfully" if DEMO_MODE else "✅ Video generation completed successfully" | |
return output, status | |
else: | |
error_output = f"""# ❌ Generation Failed | |
## Error Details | |
{result.get("error", "Unknown error occurred")} | |
## 💡 Troubleshooting Tips | |
1. **Check your topic**: Make sure it's a valid mathematical or scientific concept | |
2. **Verify API keys**: Ensure your Gemini API keys are properly set | |
3. **Try simpler topics**: Start with basic concepts like "velocity" or "pythagorean theorem" | |
4. **Check context**: Make sure additional context is relevant and not too complex | |
## 🔧 Common Issues | |
- **API Rate Limits**: If using multiple API keys, the system will automatically rotate between them | |
- **Complex Topics**: Very advanced topics might require more specific context | |
- **Long Context**: Try shortening the additional context if it's very long | |
""" | |
return error_output, "❌ Generation failed - Check the output for details" | |
def get_example_topics(): | |
"""Get example topics with contexts for the interface.""" | |
return [ | |
["Velocity", "Explain velocity in physics with detailed examples and real-world applications"], | |
["Pythagorean Theorem", "Explain with visual proof and practical applications in construction and navigation"], | |
["Derivatives", "Explain derivatives in calculus with geometric interpretation and rate of change examples"], | |
["Newton's Laws", "Explain Newton's three laws of motion with everyday examples and demonstrations"], | |
["Quadratic Formula", "Derive the quadratic formula step by step and show how to apply it"], | |
["Logarithms", "Explain logarithms, their properties, and applications in science and engineering"], | |
["Probability", "Explain basic probability concepts with coin flips, dice, and card examples"], | |
["Trigonometry", "Explain sine, cosine, and tangent functions with unit circle visualization"], | |
["Limits", "Explain the concept of limits in calculus with graphical examples"], | |
["Chemical Bonding", "Explain ionic, covalent, and metallic bonding with molecular examples"] | |
] | |
def create_interface(): | |
"""Create and configure the Gradio interface.""" | |
setup_status = setup_environment() | |
init_status = initialize_video_generator() | |
custom_css = """ | |
.main-header { | |
text-align: center; | |
margin-bottom: 30px; | |
padding: 25px; | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
border-radius: 15px; | |
color: white; | |
box-shadow: 0 4px 15px rgba(0,0,0,0.1); | |
} | |
.status-box { | |
padding: 15px; | |
border-radius: 10px; | |
margin: 10px 0; | |
border-left: 4px solid #007bff; | |
background-color: #f8f9fa; | |
} | |
.demo-warning { | |
background: linear-gradient(135deg, #ffeaa7 0%, #fab1a0 100%); | |
border: none; | |
border-radius: 10px; | |
padding: 20px; | |
margin: 15px 0; | |
color: #2d3436; | |
font-weight: 500; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
} | |
""" | |
with gr.Blocks( | |
title="🎓 Theorem Explanation Agent", | |
theme=gr.themes.Soft(primary_hue="blue", secondary_hue="purple"), | |
css=custom_css | |
) as demo: | |
gr.HTML(f""" | |
<div class="main-header"> | |
<h1>🎓 Theorem Explanation Agent</h1> | |
<p style="font-size: 18px; margin: 10px 0;">Generate educational videos explaining mathematical theorems and concepts using AI</p> | |
<p style="font-size: 14px; opacity: 0.9;">Powered by Gemini 2.0 Flash with automatic API key rotation</p> | |
</div> | |
""") | |
if DEMO_MODE: | |
gr.HTML(""" | |
<div class="demo-warning"> | |
<h3>⚠️ Demo Mode Active</h3> | |
<p>This is a demonstration version. To enable full video generation:</p> | |
<ul> | |
<li>Set your <code>GEMINI_API_KEY</code> in the Secrets tab (supports comma-separated multiple keys)</li> | |
<li>Optionally set <code>ELEVENLABS_API_KEY</code> for voice narration</li> | |
<li>Set <code>DEMO_MODE=false</code> in environment variables</li> | |
</ul> | |
</div> | |
""") | |
with gr.Row(): | |
with gr.Column(scale=3): | |
gr.HTML("<h3>📝 Video Generation Settings</h3>") | |
topic_input = gr.Textbox( | |
label="🎯 Topic to Explain", | |
placeholder="Enter the topic you want to explain (e.g., 'velocity', 'pythagorean theorem')", | |
lines=1, | |
max_lines=2 | |
) | |
context_input = gr.Textbox( | |
label="📝 Additional Context (Optional)", | |
placeholder="Provide specific requirements, difficulty level, or focus areas for the explanation", | |
lines=3, | |
max_lines=5 | |
) | |
max_scenes_slider = gr.Slider( | |
label="🎬 Maximum Number of Scenes", | |
minimum=1, | |
maximum=6, | |
value=3, | |
step=1, | |
info="More scenes = longer video but higher cost" | |
) | |
generate_btn = gr.Button( | |
"🚀 Generate Video", | |
variant="primary", | |
size="lg" | |
) | |
with gr.Column(scale=2): | |
gr.HTML("<h3>📊 Status & Information</h3>") | |
status_display = gr.Textbox( | |
label="🔄 Current Status", | |
value=init_status, | |
interactive=False, | |
lines=2 | |
) | |
gr.HTML(""" | |
<div class="status-box"> | |
<h4>🔑 API Key Setup for Hugging Face Spaces</h4> | |
<p><strong>Multiple Gemini Keys (Recommended):</strong></p> | |
<code>GEMINI_API_KEY=key1,key2,key3,key4</code> | |
<p><strong>Single Key:</strong></p> | |
<code>GEMINI_API_KEY=your_single_api_key</code> | |
<p><strong>Optional TTS:</strong></p> | |
<code>ELEVENLABS_API_KEY=your_elevenlabs_key</code> | |
<br><br> | |
<small>💡 The system automatically rotates between multiple keys to avoid rate limits</small> | |
</div> | |
""") | |
examples = gr.Examples( | |
examples=get_example_topics(), | |
inputs=[topic_input, context_input], | |
label="📚 Example Topics & Contexts" | |
) | |
output_display = gr.Markdown( | |
label="📋 Generation Results", | |
value="Ready to generate your first video! Enter a topic above and click 'Generate Video'." | |
) | |
generate_btn.click( | |
fn=generate_video_gradio, | |
inputs=[topic_input, context_input, max_scenes_slider], | |
outputs=[output_display, status_display], | |
show_progress=True | |
) | |
gr.HTML(""" | |
<div style="text-align: center; padding: 20px; margin-top: 30px; border-top: 1px solid #eee;"> | |
<p>🎓 <strong>Theorem Explanation Agent</strong></p> | |
<p>Built with ❤️ using Gradio, Gemini 2.0 Flash, and Manim</p> | |
<p style="font-size: 12px; color: #666;"> | |
For support and updates, visit the project repository • | |
Rate limits automatically managed with multi-key rotation | |
</p> | |
</div> | |
""") | |
return demo | |
if __name__ == "__main__": | |
demo = create_interface() | |
demo.launch( | |
server_name="0.0.0.0", | |
server_port=7860, | |
share=False, | |
show_error=True, | |
show_tips=True, | |
enable_queue=True, | |
max_threads=10 | |
) |