File size: 6,853 Bytes
1645305
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""

Utilities for cleaning and validating Manim code generated by LLMs.

"""

import re
import logging
import json

logger = logging.getLogger(__name__)

def clean_manim_code(raw_code):
    """

    Clean Manim code from LLM responses by removing markdown formatting

    and ensuring proper structure.

    

    Args:

        raw_code (str): The raw code from the LLM response

        

    Returns:

        str: Cleaned, executable Python code

    """
    # Start with the raw code
    code = raw_code
    
    # Extract code from markdown code blocks if present
    if "```python" in code:
        parts = code.split("```python")
        if len(parts) > 1:
            code = parts[1]
            if "```" in code:
                code = code.split("```")[0]
    elif "```" in code:
        parts = code.split("```")
        if len(parts) > 1:
            code = parts[1]
            if "```" in parts[1]:
                code = code.split("```")[0]
    
    # Remove any remaining backticks
    code = code.replace('```', '')
    
    # Ensure code begins with the necessary import
    if not code.strip().startswith('from manim import'):
        code = 'from manim import *\n\n' + code
    
    # Verify the code contains a Scene class
    if 'class' not in code or 'Scene' not in code:
        logger.warning("Generated code does not contain a proper Scene class")
        # Add a basic scene structure if missing
        if 'class ManimScene(Scene):' not in code:
            code = 'from manim import *\n\nclass ManimScene(Scene):\n    def construct(self):\n        ' + code
    
    # Verify the code has a construct method
    if 'def construct(self)' not in code:
        logger.warning("Generated code does not contain a construct method")
        # Try to find where the class is defined and add construct method
        class_match = re.search(r'class\s+\w+\s*\(\s*Scene\s*\)\s*:', code)
        if class_match:
            insert_pos = class_match.end()
            code = code[:insert_pos] + '\n    def construct(self):\n        pass\n' + code[insert_pos:]
    
    # Ensure there's a wait at the end if not present
    if 'self.wait(' not in code.split('def construct')[-1]:
        # Find the end of the construct method to add wait
        construct_body_match = re.search(r'def\s+construct\s*\(\s*self\s*\)\s*:', code)
        if construct_body_match:
            # Check if the method has content
            method_content = code[construct_body_match.end():]
            indentation = '        '  # Default indentation
            
            # Try to determine indentation from code
            indent_match = re.search(r'\n(\s+)', method_content)
            if indent_match:
                indentation = indent_match.group(1)
            
            # Find a good place to insert the wait
            if '}' in method_content.splitlines()[-1]:  # If last line closes something
                code = code.rstrip() + f'\n{indentation}self.wait(1)\n'
            else:
                code = code.rstrip() + f'\n{indentation}self.wait(1)\n'
    
    return code.strip()

def parse_scenario_from_llm_response(content):
    """

    Extract structured scenario information from an LLM response.

    

    Args:

        content (str): The LLM response text

        

    Returns:

        dict: Extracted scenario dictionary

    """
    try:
        # Try to find and extract a JSON object
        json_match = re.search(r'\{.*\}', content, re.DOTALL)
        if json_match:
            json_str = json_match.group(0)
            scenario_dict = json.loads(json_str)
            return scenario_dict
    except Exception as e:
        logger.error(f"Error parsing scenario JSON: {e}")
    
    # Manual parsing fallback
    scenario = {
        "title": "",
        "objects": [],
        "transformations": [],
        "equations": []
    }
    
    # Simple pattern matching to extract information
    title_match = re.search(r'title["\s:]+([^"]+)', content, re.IGNORECASE)
    if title_match:
        scenario["title"] = title_match.group(1).strip()
    
    # Extract lists with various possible formats
    objects_pattern = r'objects[":\s\[]+([^\]]+)'
    objects_match = re.search(objects_pattern, content, re.IGNORECASE | re.DOTALL)
    if objects_match:
        objects_text = objects_match.group(1)
        # Handle both comma-separated and quote-wrapped items
        objects = re.findall(r'"([^"]+)"', objects_text)
        if not objects:
            objects = [item.strip() for item in objects_text.split(',')]
        scenario["objects"] = objects
    
    # Similar extraction for transformations
    trans_pattern = r'transformations[":\s\[]+([^\]]+)'
    trans_match = re.search(trans_pattern, content, re.IGNORECASE | re.DOTALL)
    if trans_match:
        trans_text = trans_match.group(1)
        transformations = re.findall(r'"([^"]+)"', trans_text)
        if not transformations:
            transformations = [item.strip() for item in trans_text.split(',')]
        scenario["transformations"] = transformations
    
    # Extract equations if present
    equations_pattern = r'equations[":\s\[]+([^\]]+)'
    equations_match = re.search(equations_pattern, content, re.IGNORECASE | re.DOTALL)
    if equations_match:
        equations_text = equations_match.group(1)
        if equations_text.lower().strip() in ['null', 'none']:
            scenario["equations"] = None
        else:
            equations = re.findall(r'"([^"]+)"', equations_text)
            if not equations:
                equations = [item.strip() for item in equations_text.split(',')]
            scenario["equations"] = equations
    
    return scenario

def validate_manim_code(code):
    """

    Perform basic validation on Manim code to catch common issues.

    

    Args:

        code (str): The Manim code to validate

        

    Returns:

        tuple: (is_valid, error_message)

    """
    # Check for basic Python syntax errors
    try:
        compile(code, '<string>', 'exec')
    except SyntaxError as e:
        return False, f"Syntax error: {str(e)}"
    
    # Check for necessary components
    if 'from manim import' not in code:
        return False, "Missing Manim import"
    
    if 'class' not in code or 'Scene' not in code:
        return False, "No Scene class defined"
    
    if 'def construct(self)' not in code:
        return False, "No construct method defined"
    
    # Check for common Manim issues
    if 'self.play(' not in code and 'self.add(' not in code:
        return False, "No objects added to scene (missing self.play or self.add calls)"
    
    # All checks passed
    return True, "Code appears valid"