File size: 18,455 Bytes
4588d9f
 
2c50e10
 
4588d9f
 
2c50e10
 
 
 
 
 
 
 
4588d9f
2c50e10
 
 
a131b22
2c50e10
a131b22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c50e10
 
 
 
 
 
 
 
a131b22
 
 
 
2c50e10
 
 
 
 
 
a131b22
2c50e10
 
 
 
a131b22
2c50e10
 
 
 
 
a131b22
 
 
2c50e10
 
 
 
 
 
 
 
 
 
a131b22
2c50e10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a131b22
2c50e10
a131b22
 
 
 
 
 
 
2c50e10
 
 
 
 
 
a131b22
2c50e10
 
a131b22
 
2c50e10
a131b22
 
2c50e10
a131b22
 
 
 
 
 
 
 
 
 
 
 
2c50e10
a131b22
 
2c50e10
 
a131b22
2c50e10
 
 
a131b22
2c50e10
 
a131b22
2c50e10
 
 
a131b22
2c50e10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a131b22
2c50e10
 
a131b22
 
2c50e10
a131b22
2c50e10
 
 
 
 
 
 
 
 
a131b22
 
 
 
 
 
 
 
 
 
 
 
 
2c50e10
a131b22
 
2c50e10
a131b22
2c50e10
 
 
 
 
a131b22
2c50e10
 
 
a131b22
2c50e10
 
 
a131b22
2c50e10
 
 
 
 
a131b22
 
 
 
 
 
 
 
 
 
 
 
2c50e10
a131b22
 
 
 
 
 
 
 
 
2c50e10
a131b22
 
 
2c50e10
 
a131b22
2c50e10
 
a131b22
2c50e10
a131b22
 
 
 
 
 
 
 
2c50e10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a131b22
 
2c50e10
 
 
a131b22
2c50e10
 
 
 
a131b22
2c50e10
a131b22
2c50e10
 
 
 
 
 
 
 
 
 
 
a131b22
 
2c50e10
a131b22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c50e10
 
 
 
 
 
 
 
 
a131b22
2c50e10
 
 
 
a131b22
 
2c50e10
 
 
 
a131b22
2c50e10
 
 
 
a131b22
2c50e10
 
a131b22
 
 
 
 
2c50e10
 
 
 
 
 
a131b22
 
 
 
2c50e10
 
 
 
 
 
 
 
a131b22
2c50e10
 
 
 
 
 
a131b22
2c50e10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4588d9f
2c50e10
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
#!/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
    )