File size: 9,806 Bytes
d970572
 
 
 
 
2e31ab2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d970572
 
 
2e31ab2
 
 
d970572
 
 
 
 
 
 
2e31ab2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d970572
 
 
2e31ab2
 
d970572
2e31ab2
 
 
d970572
2e31ab2
d970572
c4a80a5
2e31ab2
 
 
d970572
2e31ab2
d970572
 
 
2e31ab2
 
 
 
 
 
d970572
 
 
2e31ab2
 
 
 
 
 
d970572
2e31ab2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d970572
2e31ab2
 
 
 
d970572
 
2e31ab2
d970572
 
2e31ab2
 
 
d970572
2e31ab2
 
 
d970572
 
2e31ab2
 
 
 
 
 
d970572
2e31ab2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d970572
2e31ab2
 
 
 
 
 
 
d970572
 
 
 
2e31ab2
 
 
d970572
 
2e31ab2
 
d970572
 
 
 
 
 
 
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
import os
import re
from google import genai
from google.genai import types as genai_types
import logging
from .gemini import base_prompt_instructions

logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)

FALLBACK_SYSTEM_PROMPT = """You are an expert Manim programmer specializing in fixing broken Manim code and creating visually striking 60-second animations, strictly following Manim Community v0.19.0 standards.

CRITICAL TIMING REQUIREMENTS:
- **Total Duration:** Exactly 60 seconds (1 minute)
- **Narration:** Exactly 150-160 words (average speaking pace: 2.5 words per second)
- **Animation Structure:** Use this timing framework:
  * Introduction: 8-10 seconds
  * Main content: 40-45 seconds (3-4 major segments)
  * Conclusion/summary: 7-10 seconds
- **Synchronization:** Each narration sentence should correspond to 3-5 seconds of animation

Core Requirements:
- **API Version:** Use only Manim Community v0.19.0 API
- **Vectors & Math:** Use 3D vectors (np.array([x, y, 0])) and ensure correct math operations
- **Matrix Visualization:** Use MathTex for matrices: r'\\begin{bmatrix} a & b \\\\ c & d \\end{bmatrix}'
- **Star Usage:** Use Star(n=5, ...) not n_points
- **Error Prevention:** Always validate Scene class exists; avoid 3D scenes
- **Visual Style:** Create vibrant, dynamic animations with smooth transitions

IMPORTANT: Your response must be formatted with clear delimiters:
- Start Manim code with: ### MANIM CODE:
- Start narration with: ### NARRATION:
- End response after narration (no additional text)
"""


def fix_manim_code(faulty_code: str, error_message: str, original_context: str):
    """
    Enhanced fallback function with Google Search integration.
    """
    api_key = os.getenv("GEMINI_API_KEY")
    if not api_key:
        logging.error("GEMINI_API_KEY not found in environment variables for fallback.")
        return None, None

    client = genai.Client(api_key=api_key)

    # Enhanced fallback prompt with better structure and error analysis
    fix_prompt_text = f"""
TASK: Fix the broken Manim code that failed with a specific error.

### ORIGINAL REQUEST:
{original_context}

### BROKEN MANIM CODE:
```python
{faulty_code}
```

### ERROR ENCOUNTERED:
```
{error_message}
```

### ANALYSIS INSTRUCTIONS:
1. **Error Analysis**: Examine the error message carefully. Common issues include:
   - Import errors (missing 'from manim import *' or 'import numpy as np')
   - Scene class not found (class must inherit from Scene)
   - Invalid Manim methods or syntax
   - Vector dimension mismatches (use np.array([x, y, 0]))
   - Animation object validation errors
   - Timing issues (ensure total duration = 60 seconds)

2. **Google Search**: Use Google Search to find:
   - Recent Manim Community v0.19.0 API changes
   - Specific error message solutions
   - Updated method signatures or deprecated features
   - Working examples of similar animations

3. **Code Fixing Strategy**:
   - Keep the original animation concept intact
   - Fix only what's necessary to resolve the error
   - Maintain 60-second duration and 120-150 word narration
   - Ensure all imports are present
   - Validate Scene class exists and is properly named
   - Use only verified Manim methods from the allowed list

4. **Quality Checks**:
   - Verify vector operations use 3D format: np.array([x, y, 0])
   - Check all self.play() calls have valid animation objects
   - Ensure run_time and self.wait() sum to exactly 60 seconds
   - Count narration words (must be 120-150)

### OUTPUT FORMAT:
Provide your response in exactly this format:

### MANIM CODE:
[Insert the complete, fixed Manim code here - include all imports and Scene class]

### NARRATION:
[Insert the narration script here - exactly 120-150 words, synchronized with animations]

### REQUIREMENTS TO FOLLOW:
{base_prompt_instructions}
"""

    contents = [fix_prompt_text]

    logging.info("Attempting to fix Manim code via fallback...")
    try:
        grounding_tool = genai_types.Tool(google_search=genai_types.GoogleSearch())

        generation_config = genai_types.GenerateContentConfig(
            tools=[grounding_tool],
            temperature=0.4,  # lower coz grounding
            system_instruction=FALLBACK_SYSTEM_PROMPT,
        )

        response = client.models.generate_content(
            model="gemini-2.5-pro",
            contents=contents,  # type: ignore
            config=generation_config,
        )
        if response:
            # print(response)
            try:
                content = response.text
                logging.info("Received response from fallback attempt.")

                if "### NARRATION:" in content:  # type: ignore
                    manim_code, narration = content.split("### NARRATION:", 1)  # type: ignore
                    manim_code = (
                        re.sub(r"```python", "", manim_code).replace("```", "").strip()
                    )
                    narration = narration.strip()

                    if "from manim import *" not in manim_code:
                        logging.warning(
                            "Adding missing 'from manim import *' (fallback fix)."
                        )
                        manim_code = (
                            "from manim import *\nimport numpy as np\n" + manim_code
                        )
                    elif "import numpy as np" not in manim_code:
                        logging.warning(
                            "Adding missing 'import numpy as np' (fallback fix)."
                        )
                        lines = manim_code.splitlines()
                        for i, line in enumerate(lines):
                            if "from manim import *" in line:
                                lines.insert(i + 1, "import numpy as np")
                                manim_code = "\n".join(lines)
                                break

                    logging.info(
                        "Successfully parsed fixed code and narration from fallback."
                    )
                    return {
                        "manim_code": manim_code,
                        "output_file": "output.mp4",
                    }, narration
                else:
                    logging.warning(
                        "Delimiter '### NARRATION:' not found in fallback response. Attempting fallback extraction."
                    )
                    code_match = re.search(r"```python(.*?)```", content, re.DOTALL)  # type: ignore
                    if code_match:
                        manim_code = code_match.group(1).strip()
                        narration_part = content.split("```", 2)[-1].strip()
                        narration = narration_part if len(narration_part) > 20 else ""
                        if not narration:
                            logging.warning(
                                "Fallback narration extraction resulted in empty or very short text (fallback fix)."
                            )
                        else:
                            logging.info(
                                "Successfully parsed code and narration using fallback regex (fallback fix)."
                            )

                        if "from manim import *" not in manim_code:
                            logging.warning(
                                "Adding missing 'from manim import *' (fallback fix, regex path)."
                            )
                            manim_code = (
                                "from manim import *\nimport numpy as np\n" + manim_code
                            )
                        elif "import numpy as np" not in manim_code:
                            logging.warning(
                                "Adding missing 'import numpy as np' (fallback fix, regex path)."
                            )
                            lines = manim_code.splitlines()
                            for i, line in enumerate(lines):
                                if "from manim import *" in line:
                                    lines.insert(i + 1, "import numpy as np")
                                    manim_code = "\n".join(lines)
                                    break

                        logging.info(
                            "Successfully parsed fixed code using fallback extraction."
                        )
                        return {
                            "manim_code": manim_code,
                            "output_file": "output.mp4",
                        }, narration
                    else:
                        logging.error(
                            "Fallback extraction failed: No Python code block found in fallback response."
                        )
                        logging.debug(
                            f"Fallback content without code block:\n{content}"
                        )
                        return None, None

            except ValueError:
                logging.error("Could not extract text from the fallback response.")
                if response.prompt_feedback and response.prompt_feedback.block_reason:
                    logging.error(
                        f"Fallback content generation blocked. Reason: {response.prompt_feedback.block_reason.name}"
                    )
                return None, None
            except Exception as e:
                logging.exception(f"Error processing fallback response: {e}")
                return None, None
        else:
            logging.error("No response received from Gemini during fallback attempt.")
            return None, None

    except Exception as e:
        logging.exception(f"Error calling Gemini API during fallback: {e}")
        return None, None