Spaces:
Running
Running
| # src/generation/code_generator.py | |
| import json | |
| import tempfile | |
| from src.core.ai_helpers import generate_with_ai | |
| from src.utils.helpers import fix_invalid_colors | |
| from src.utils.smart_logger import logger | |
| def generate_scene_code(project_config: dict, gemini_client, temp_dir_name: str): | |
| """Workflow Step 2: Generate Manim code and save to a temporary file.""" | |
| logger.start_operation("code_gen", "Generating scene code") | |
| if not project_config.get("video_plan"): | |
| logger.end_operation("code_gen", "No video plan found") | |
| return | |
| plan = project_config["video_plan"] | |
| scenes_count = len(plan.get('scenes', [])) | |
| logger.quick_log("info", f"Generating code for {scenes_count} scenes") | |
| plan_str = json.dumps(project_config["video_plan"], indent=2) | |
| code_gen_prompt = f""" | |
| Generate a single Python file containing all Manim scene classes based on this video plan: | |
| {plan_str} | |
| Requirements: | |
| 1. Create ONE Python file with all necessary Manim scene classes. | |
| 2. Use modern Manim syntax (e.g., `from manim import *`). | |
| 3. Each class name must exactly match a `scene_name` from the plan. | |
| 4. Implement the `construct` method for each scene based on its `description`. | |
| 5. Set the background color for every scene to WHITE. | |
| 6. Add comments to explain the code. | |
| 7. At the end, add: `SCENE_ORDER = ["IntroScene", "MainConceptScene", ...]` | |
| 8. Return ONLY the Python code, inside a single markdown block. | |
| CRITICAL CONSTRAINTS: | |
| - NEVER use ImageMobject, SVGMobject, or any file references. | |
| - NEVER use MathTex, Tex, or any LaTeX-dependent objects (LaTeX not installed). | |
| - Use Text() for all text including mathematical formulas. | |
| - Create all visuals using Manim's built-in shapes and functions ONLY. | |
| - ONLY use these colors: RED, BLUE, GREEN, YELLOW, WHITE, BLACK, GRAY, ORANGE, PURPLE, PINK. | |
| - To position objects, call .to_edge(), .move_to(), or .shift() DIRECTLY on the Mobject. | |
| - For math formulas, use Text() with simple strings like "x² + y² = z²". | |
| """ | |
| response_text = generate_with_ai(gemini_client, code_gen_prompt, is_json=False) | |
| if not response_text: | |
| logger.end_operation("code_gen", "No AI response received") | |
| return | |
| if "```python" in response_text: | |
| all_scenes_code = response_text.split("```python")[1].split("```")[0].strip() | |
| else: | |
| all_scenes_code = response_text.strip() | |
| all_scenes_code = fix_invalid_colors(all_scenes_code) | |
| with tempfile.NamedTemporaryFile(mode='w+', suffix='.py', delete=False, dir=temp_dir_name) as temp_code_file: | |
| temp_code_file.write(all_scenes_code) | |
| temp_code_file_path = temp_code_file.name | |
| for scene_name in project_config["scenes"]: | |
| project_config["scenes"][scene_name]["code_file"] = temp_code_file_path | |
| code_size = len(all_scenes_code) | |
| logger.end_operation("code_gen", "Scene code generated", f"{code_size} chars → {temp_code_file_path}") | |
| def regenerate_single_scene_code(project_config: dict, gemini_client, temp_dir_name: str, scene_name: str) -> bool: | |
| """Regenerate code for a single scene when debugging fails.""" | |
| logger.start_operation("regen_scene", f"Regenerating code for {scene_name}") | |
| if not project_config.get("video_plan"): | |
| logger.end_operation("regen_scene", "No video plan found") | |
| return False | |
| # Find the specific scene in the plan | |
| scenes = project_config["video_plan"].get("scenes", []) | |
| target_scene = None | |
| for scene in scenes: | |
| if scene.get("scene_name") == scene_name: | |
| target_scene = scene | |
| break | |
| if not target_scene: | |
| logger.end_operation("regen_scene", f"Scene {scene_name} not found in plan") | |
| return False | |
| scene_prompt = f""" | |
| Generate a single Manim scene class for this specific scene: | |
| Scene Name: {scene_name} | |
| Description: {target_scene.get('description', '')} | |
| Narration: {target_scene.get('narration', '')} | |
| Requirements: | |
| 1. Create ONE Python file with the complete scene class. | |
| 2. Use modern Manim syntax (e.g., `from manim import *`). | |
| 3. Class name must be exactly: {scene_name} | |
| 4. Implement the `construct` method based on the description. | |
| 5. Set background color to WHITE. | |
| 6. Add detailed comments. | |
| 7. Return ONLY the Python code, inside a single markdown block. | |
| CRITICAL CONSTRAINTS: | |
| - NEVER use ImageMobject, SVGMobject, or any file references. | |
| - NEVER use MathTex, Tex, or any LaTeX-dependent objects (LaTeX not installed). | |
| - Use Text() for all text including mathematical formulas. | |
| - Create all visuals using Manim's built-in shapes and functions ONLY. | |
| - ONLY use these colors: RED, BLUE, GREEN, YELLOW, WHITE, BLACK, GRAY, ORANGE, PURPLE, PINK. | |
| - To position objects, call .to_edge(), .move_to(), or .shift() DIRECTLY on the Mobject. | |
| - For math formulas, use Text() with simple strings like "x² + y² = z²". | |
| - Make the animation clear and educational. | |
| """ | |
| response_text = generate_with_ai(gemini_client, scene_prompt, is_json=False) | |
| if not response_text: | |
| logger.end_operation("regen_scene", "No AI response received") | |
| return False | |
| if "```python" in response_text: | |
| scene_code = response_text.split("```python")[1].split("```")[0].strip() | |
| else: | |
| scene_code = response_text.strip() | |
| scene_code = fix_invalid_colors(scene_code) | |
| # Create a new temporary file for this regenerated scene | |
| with tempfile.NamedTemporaryFile(mode='w+', suffix='.py', delete=False, dir=temp_dir_name) as temp_code_file: | |
| temp_code_file.write(scene_code) | |
| temp_code_file_path = temp_code_file.name | |
| # Update the project config for this specific scene | |
| if scene_name in project_config["scenes"]: | |
| project_config["scenes"][scene_name]["code_file"] = temp_code_file_path | |
| project_config["scenes"][scene_name]["status"] = "code_regenerated" | |
| code_size = len(scene_code) | |
| logger.end_operation("regen_scene", f"Scene code regenerated", f"{code_size} chars → {temp_code_file_path}") | |
| return True | |