File size: 6,195 Bytes
aeaa761
 
 
 
 
ac02dae
aeaa761
 
 
ac02dae
 
aeaa761
ac02dae
aeaa761
 
 
ac02dae
 
aeaa761
 
 
 
 
 
 
 
 
 
 
 
 
 
ac02dae
aeaa761
 
ac02dae
aeaa761
e5644ac
 
aeaa761
ac02dae
 
e5644ac
aeaa761
ac02dae
aeaa761
ac02dae
 
 
aeaa761
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ac02dae
 
d2bb83f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5644ac
 
d2bb83f
 
 
e5644ac
d2bb83f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# 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