#!/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 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("""

šŸŽ“ Theorem Explanation Agent

AI-Powered Educational Content Generation

Powered by Gemini 2.0 Flash

""") # System status section with gr.Row(): with gr.Column(): gr.HTML(f"""

šŸ” API Configuration

Status: {"āœ… API keys configured" if has_api_keys else "āš ļø No API keys found"}

{"Ready for content generation" if has_api_keys else "Limited to demo capabilities"}

""") 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("""

šŸŽ® Demo Mode Active

This HF Spaces instance runs in demonstration mode due to system dependency limitations.

āœ… Available Features:
  • šŸ¤– Gemini 2.0 Flash AI integration
  • šŸ”„ Comma-separated API key rotation
  • šŸ“š Educational content planning
  • šŸŽÆ Learning objective design
āŒ Not Available:
  • šŸŽ„ Actual video rendering (requires Manim system libraries)
  • šŸ“¹ MP4 file generation
  • šŸŽØ Visual animations
For Full Video Generation:
1. Clone repository locally
2. Install system dependencies (pangocairo, manim)
3. Set GEMINI_API_KEY environment variable
4. Run: python app.py
""") # 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("""

šŸ’” Tips for Best Results

""") # 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 )