Spaces:
Sleeping
Sleeping
#!/usr/bin/env python3 | |
""" | |
Theorem Explanation Agent - Hugging Face Spaces App | |
Generates educational videos using Gemini 2.0 Flash and Manim | |
""" | |
import os | |
import sys | |
import asyncio | |
import time | |
import random | |
from typing import Dict, Any, Tuple, Optional | |
from pathlib import Path | |
import gradio as gr | |
# Environment setup | |
DEMO_MODE = os.getenv("DEMO_MODE", "false").lower() == "true" | |
video_generator = None | |
CAN_IMPORT_DEPENDENCIES = False | |
GRADIO_OUTPUT_DIR = "gradio_outputs" | |
DEPENDENCY_ERROR = None | |
def check_dependencies(): | |
"""Check if required dependencies are available.""" | |
global CAN_IMPORT_DEPENDENCIES, DEPENDENCY_ERROR | |
missing_deps = [] | |
try: | |
import manim | |
except ImportError: | |
missing_deps.append("manim") | |
try: | |
from generate_video import VideoGenerator | |
except ImportError as e: | |
missing_deps.append("generate_video") | |
DEPENDENCY_ERROR = str(e) | |
try: | |
from mllm_tools.litellm import LiteLLMWrapper | |
except ImportError: | |
missing_deps.append("mllm_tools") | |
if missing_deps: | |
CAN_IMPORT_DEPENDENCIES = False | |
return f"Missing dependencies: {', '.join(missing_deps)}" | |
else: | |
CAN_IMPORT_DEPENDENCIES = True | |
return "All dependencies available" | |
def setup_environment(): | |
"""Setup environment for HF Spaces.""" | |
print("๐ Setting up Theorem Explanation Agent...") | |
# Create output directory | |
os.makedirs(GRADIO_OUTPUT_DIR, exist_ok=True) | |
# Check dependencies | |
dep_status = check_dependencies() | |
print(f"๐ฆ Dependencies: {dep_status}") | |
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)") | |
return True | |
else: | |
print("โ ๏ธ No Gemini API keys found") | |
return False | |
def initialize_video_generator(): | |
"""Initialize video generator with proper dependencies.""" | |
global video_generator, CAN_IMPORT_DEPENDENCIES, DEPENDENCY_ERROR | |
try: | |
if DEMO_MODE: | |
return "โ ๏ธ Demo mode enabled - No video generation" | |
if not CAN_IMPORT_DEPENDENCIES: | |
return f"โ ๏ธ Missing dependencies - {DEPENDENCY_ERROR or 'Video generation not available'}" | |
gemini_keys = os.getenv("GEMINI_API_KEY", "") | |
if not gemini_keys: | |
return "โ ๏ธ No API keys found - Set GEMINI_API_KEY environment variable" | |
# Import dependencies | |
try: | |
from generate_video import VideoGenerator | |
from mllm_tools.litellm import LiteLLMWrapper | |
print("โ Successfully imported video generation dependencies") | |
except ImportError as e: | |
return f"โ ๏ธ Import error: {str(e)}" | |
# Initialize models with comma-separated API key support | |
planner_model = LiteLLMWrapper( | |
model_name="gemini/gemini-2.0-flash-exp", | |
temperature=0.7, | |
print_cost=True, | |
verbose=False, | |
use_langfuse=False | |
) | |
# Initialize video generator | |
video_generator = VideoGenerator( | |
planner_model=planner_model, | |
helper_model=planner_model, | |
scene_model=planner_model, | |
output_dir=GRADIO_OUTPUT_DIR, | |
use_rag=False, | |
use_context_learning=False, | |
use_visual_fix_code=False, | |
verbose=True | |
) | |
return "โ Video generator initialized successfully" | |
except Exception as e: | |
CAN_IMPORT_DEPENDENCIES = False | |
print(f"โ Error initializing video generator: {e}") | |
return f"โ Initialization failed: {str(e)}" | |
def simulate_video_generation(topic: str, context: str, max_scenes: int, progress_callback=None): | |
"""Enhanced simulation for HF Spaces demo.""" | |
stages = [ | |
("๐ Analyzing educational topic", 15), | |
("๐ Planning curriculum structure", 30), | |
("๐ฏ Designing learning objectives", 45), | |
("๐ Creating content outline", 60), | |
("๐จ Generating visual concepts", 75), | |
("๐ฌ Simulating video production", 90), | |
("โ Demo completed", 100) | |
] | |
results = [] | |
for stage, progress in stages: | |
if progress_callback: | |
progress_callback(progress, stage) | |
time.sleep(random.uniform(0.8, 1.5)) | |
results.append(f"โข {stage}") | |
# Create demo information | |
demo_content = { | |
"success": True, | |
"message": f"Demo simulation completed for educational topic: {topic}", | |
"scenes_planned": max_scenes, | |
"processing_steps": results, | |
"demo_note": "๐ฎ This is a demonstration mode", | |
"limitations": [ | |
"Real video generation requires Manim system dependencies", | |
"HF Spaces has limited system library support", | |
"For full functionality, run locally with complete dependencies" | |
], | |
"capabilities": [ | |
"โ Gemini 2.0 Flash AI integration", | |
"โ Comma-separated API key support", | |
"โ Educational content planning", | |
"โ Video rendering (requires local setup)" | |
] | |
} | |
return demo_content | |
async def generate_video_async(topic: str, context: str, max_scenes: int, progress_callback=None): | |
"""Generate video asynchronously - handles both real and demo modes.""" | |
global video_generator | |
if not topic.strip(): | |
return {"success": False, "error": "Please enter an educational topic"} | |
try: | |
# Always use demo mode on HF Spaces due to dependency limitations | |
if DEMO_MODE or not CAN_IMPORT_DEPENDENCIES or video_generator is None: | |
return simulate_video_generation(topic, context, max_scenes, progress_callback) | |
# This code would run with full dependencies (local setup) | |
if progress_callback: | |
progress_callback(10, "๐ Starting video generation...") | |
result = await video_generator.generate_video_pipeline( | |
topic=topic, | |
description=context or f"Educational video about {topic}", | |
max_retries=3, | |
only_plan=False, | |
specific_scenes=list(range(1, max_scenes + 1)) if max_scenes > 0 else None | |
) | |
if progress_callback: | |
progress_callback(100, "โ Video generation completed!") | |
# Check for generated video files | |
file_prefix = topic.lower().replace(' ', '_') | |
file_prefix = ''.join(c for c in file_prefix if c.isalnum() or c == '_') | |
output_folder = os.path.join(GRADIO_OUTPUT_DIR, file_prefix) | |
video_files = [] | |
if os.path.exists(output_folder): | |
combined_video = os.path.join(output_folder, f"{file_prefix}_combined.mp4") | |
if os.path.exists(combined_video): | |
video_files.append(combined_video) | |
for i in range(1, max_scenes + 1): | |
scene_video = os.path.join(output_folder, f"scene{i}", f"{file_prefix}_scene{i}.mp4") | |
if os.path.exists(scene_video): | |
video_files.append(scene_video) | |
return { | |
"success": True, | |
"message": f"Video generated successfully for: {topic}", | |
"video_files": video_files, | |
"output_folder": output_folder, | |
"result": result | |
} | |
except Exception as e: | |
print(f"โ Error in video generation: {e}") | |
return {"success": False, "error": str(e)} | |
def generate_video_gradio(topic: str, context: str, max_scenes: int, progress=gr.Progress()) -> Tuple[str, str, Optional[str]]: | |
"""Main Gradio function that handles video generation and returns results.""" | |
def progress_callback(percent, message): | |
progress(percent / 100, desc=message) | |
# Create new event loop for this generation | |
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"""# ๐ Educational Content Generation | |
**Topic:** {topic} | |
**Context:** {context if context else "General educational content"} | |
**Planned Scenes:** {max_scenes} | |
## โ Generation Results | |
{result["message"]} | |
""" | |
# Add processing steps if available | |
if "processing_steps" in result: | |
output += "\n## ๐ Processing Steps\n" | |
for step in result["processing_steps"]: | |
output += f"{step}\n" | |
# Add capabilities info | |
if "capabilities" in result: | |
output += "\n## ๐ ๏ธ System Capabilities\n" | |
for cap in result["capabilities"]: | |
output += f"{cap}\n" | |
# Add limitations for demo mode | |
if "limitations" in result: | |
output += "\n## โ ๏ธ Current Limitations\n" | |
for limit in result["limitations"]: | |
output += f"โข {limit}\n" | |
# Add demo note if present | |
if "demo_note" in result: | |
output += f"\n## {result['demo_note']}\n" | |
output += "For full video generation capabilities, set up the system locally with all dependencies." | |
# Add video file information for real generation | |
video_path = None | |
if "video_files" in result and result["video_files"]: | |
output += f"\n## ๐ฅ Generated Videos\n" | |
for video_file in result["video_files"]: | |
output += f"โข {os.path.basename(video_file)}\n" | |
video_path = result["video_files"][0] | |
elif "output_folder" in result: | |
output += f"\n๐ **Output folder:** {result['output_folder']}\n" | |
status = "๐ฎ Demo mode active" if (DEMO_MODE or not CAN_IMPORT_DEPENDENCIES) else "โ Generation completed" | |
return output, status, video_path | |
else: | |
error_output = f"""# โ Generation Failed | |
**Error:** {result.get("error", "Unknown error")} | |
## ๐ก Troubleshooting Tips | |
### For Demo Mode Issues: | |
1. **Topic Clarity:** Use specific educational topics | |
2. **Context:** Provide clear learning objectives | |
3. **Scope:** Keep scenes reasonable (2-4 for demos) | |
### For Full Video Generation: | |
1. **Local Setup:** Clone the repository locally | |
2. **Dependencies:** Install all required packages including Manim | |
3. **API Keys:** Set GEMINI_API_KEY with valid keys | |
4. **System Requirements:** Ensure Manim system dependencies are installed | |
## ๐ง Local Development Setup | |
```bash | |
# Clone repository | |
git clone <repository-url> | |
cd TheoremExplainAgent | |
# Install full dependencies | |
pip install -r requirements.txt | |
# Set environment variables | |
export GEMINI_API_KEY="your-key-1,your-key-2" | |
export DEMO_MODE=false | |
# Run locally | |
python app.py | |
``` | |
""" | |
return error_output, "โ Failed", None | |
def get_examples(): | |
"""Educational example topics optimized for AI processing.""" | |
return [ | |
["Pythagorean Theorem", "Visual proof with geometric demonstrations for high school students"], | |
["Newton's Second Law", "F=ma explained with real-world examples and mathematical derivations"], | |
["Calculus Derivatives", "Rate of change concept with graphical interpretations and applications"], | |
["DNA Structure", "Double helix model with chemical bonds and biological significance"], | |
["Photosynthesis Process", "Step-by-step biochemical pathway with energy transformations"], | |
["Quadratic Formula", "Derivation, applications, and graphical representation"], | |
["Electromagnetic Waves", "Properties, spectrum, and everyday applications"], | |
["Cellular Respiration", "ATP production pathway with molecular details"] | |
] | |
# Initialize the system | |
has_api_keys = setup_environment() | |
init_status = initialize_video_generator() | |
# Create Gradio interface | |
with gr.Blocks( | |
title="๐ Theorem Explanation Agent", | |
theme=gr.themes.Soft(), | |
css="footer {visibility: hidden}" | |
) as demo: | |
gr.HTML(""" | |
<div style="text-align: center; padding: 25px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; color: white; margin-bottom: 25px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);"> | |
<h1 style="margin: 0; font-size: 2.5em;">๐ Theorem Explanation Agent</h1> | |
<p style="margin: 10px 0 0 0; font-size: 1.2em; opacity: 0.9;">AI-Powered Educational Content Generation</p> | |
<p style="margin: 5px 0 0 0; font-size: 0.9em; opacity: 0.8;">Powered by Gemini 2.0 Flash</p> | |
</div> | |
""") | |
# System status section | |
with gr.Row(): | |
with gr.Column(): | |
gr.HTML(f""" | |
<div style="background: {'#d4edda' if has_api_keys else '#fff3cd'}; padding: 15px; border-radius: 10px; margin-bottom: 15px; border-left: 4px solid {'#28a745' if has_api_keys else '#ffc107'};"> | |
<h4 style="margin: 0 0 8px 0;">๐ API Configuration</h4> | |
<p style="margin: 0;"><strong>Status:</strong> {"โ API keys configured" if has_api_keys else "โ ๏ธ No API keys found"}</p> | |
<p style="margin: 5px 0 0 0; font-size: 0.9em;">{"Ready for content generation" if has_api_keys else "Limited to demo capabilities"}</p> | |
</div> | |
""") | |
with gr.Column(): | |
system_status = gr.Textbox( | |
label="๐ง System Status", | |
value=init_status, | |
interactive=False, | |
lines=2 | |
) | |
# Dependency information | |
if not CAN_IMPORT_DEPENDENCIES: | |
gr.HTML(""" | |
<div style="background: #e3f2fd; padding: 20px; border-radius: 10px; margin: 15px 0; border: 1px solid #2196f3;"> | |
<h4 style="color: #1976d2; margin-top: 0;">๐ฎ Demo Mode Active</h4> | |
<p>This HF Spaces instance runs in demonstration mode due to system dependency limitations.</p> | |
<div style="background: #fff; padding: 15px; border-radius: 8px; margin: 15px 0;"> | |
<h5 style="color: #333; margin-top: 0;">โ Available Features:</h5> | |
<ul style="margin: 10px 0; color: #666;"> | |
<li>๐ค Gemini 2.0 Flash AI integration</li> | |
<li>๐ Comma-separated API key rotation</li> | |
<li>๐ Educational content planning</li> | |
<li>๐ฏ Learning objective design</li> | |
</ul> | |
<h5 style="color: #333; margin-top: 15px;">โ Not Available:</h5> | |
<ul style="margin: 10px 0; color: #666;"> | |
<li>๐ฅ Actual video rendering (requires Manim system libraries)</li> | |
<li>๐น MP4 file generation</li> | |
<li>๐จ Visual animations</li> | |
</ul> | |
</div> | |
<div style="background: #f5f5f5; padding: 15px; border-radius: 5px; font-family: monospace; font-size: 0.9em;"> | |
<strong>For Full Video Generation:</strong><br> | |
1. Clone repository locally<br> | |
2. Install system dependencies (pangocairo, manim)<br> | |
3. Set GEMINI_API_KEY environment variable<br> | |
4. Run: python app.py | |
</div> | |
</div> | |
""") | |
# Main interface | |
with gr.Row(): | |
with gr.Column(scale=2): | |
topic_input = gr.Textbox( | |
label="๐ Educational Topic", | |
placeholder="e.g., Pythagorean Theorem, Newton's Laws, DNA Structure...", | |
lines=1 | |
) | |
context_input = gr.Textbox( | |
label="๐ Learning Context (Optional)", | |
placeholder="Specify target audience, learning objectives, or focus areas...", | |
lines=3 | |
) | |
max_scenes_slider = gr.Slider( | |
label="๐ฌ Content Sections", | |
minimum=1, | |
maximum=6, | |
value=3, | |
step=1, | |
info="Number of content sections to plan" | |
) | |
generate_btn = gr.Button( | |
"๐ Generate Educational Content", | |
variant="primary", | |
size="lg" | |
) | |
with gr.Column(scale=1): | |
gr.HTML(""" | |
<div style="background: #f8f9fa; padding: 20px; border-radius: 10px; height: fit-content;"> | |
<h4 style="color: #495057; margin-top: 0;">๐ก Tips for Best Results</h4> | |
<ul style="color: #6c757d; font-size: 0.9em; line-height: 1.6;"> | |
<li><strong>Be Specific:</strong> "Quadratic formula derivation" vs "Math"</li> | |
<li><strong>Educational Focus:</strong> Include learning objectives</li> | |
<li><strong>Target Audience:</strong> Specify grade level or background</li> | |
<li><strong>Clear Context:</strong> Mention key concepts to cover</li> | |
</ul> | |
</div> | |
""") | |
# Examples | |
examples = gr.Examples( | |
examples=get_examples(), | |
inputs=[topic_input, context_input], | |
label="๐ Example Educational Topics" | |
) | |
# Output section | |
with gr.Row(): | |
with gr.Column(scale=2): | |
output_display = gr.Markdown( | |
value="๐ **Ready to generate educational content!** Enter a topic above and click 'Generate' to begin planning.", | |
label="๐ Generation Results" | |
) | |
with gr.Column(scale=1): | |
video_output = gr.Video( | |
label="๐ฅ Generated Video", | |
visible=True | |
) | |
# Wire up the interface | |
generate_btn.click( | |
fn=generate_video_gradio, | |
inputs=[topic_input, context_input, max_scenes_slider], | |
outputs=[output_display, system_status, video_output], | |
show_progress=True | |
) | |
# Launch configuration | |
if __name__ == "__main__": | |
demo.launch( | |
server_name="0.0.0.0", | |
server_port=7860, | |
share=False, | |
show_error=True | |
) |