import os import re from google import genai from google.genai import types as genai_types import logging from .gemini import SYSTEM_PROMPT, base_prompt_instructions logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') def fix_manim_code(faulty_code: str, error_message: str, original_context: str): 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) fix_prompt_text = ( f"The following Manim code, intended to '{original_context}', failed with an error.\n\n" "### FAULTY CODE:\n" f"```python\n{faulty_code}\n```\n\n" "### ERROR MESSAGE:\n" f"```\n{error_message}\n```\n\n" "### INSTRUCTIONS:\n" "1. Analyze the error message and the faulty code.\n" "2. Correct the code to fix the specific error reported.\n" "3. Ensure the corrected code still fulfills the original request and adheres strictly to *all* the requirements listed below.\n" "4. Pay close attention to vector dimensions, matrix operations, allowed Manim methods, and total duration (30 seconds).\n" "5. If the code logic changes significantly, update the narration accordingly.\n" "6. Return *only* the corrected code and narration using the '### MANIM CODE:' and '### NARRATION:' delimiters, just like the original request.\n\n" "### REQUIREMENTS (Apply these to the corrected code):\n" f"{base_prompt_instructions}" ) contents = [fix_prompt_text] logging.info("Attempting to fix Manim code via fallback...") try: generation_config = genai_types.GenerateContentConfig( system_instruction=SYSTEM_PROMPT ) response = client.models.generate_content( model="gemini-2.0-flash", contents=contents, config=generation_config ) if response: try: content = response.text logging.info("Received response from fallback attempt.") if "### NARRATION:" in content: manim_code, narration = content.split("### NARRATION:", 1) 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) 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