Spaces:
Running
Running
Upload 2 files
Browse files- Dockerfile +55 -66
- ai_agent.py +831 -26
Dockerfile
CHANGED
@@ -1,67 +1,56 @@
|
|
1 |
-
FROM python:3.12-slim
|
2 |
-
|
3 |
-
# Install all dependencies in one layer
|
4 |
-
RUN apt-get update && apt-get install -y --no-install-recommends \
|
5 |
-
gcc \
|
6 |
-
libffi-dev \
|
7 |
-
curl \
|
8 |
-
ca-certificates \
|
9 |
-
python3-dev \
|
10 |
-
pkg-config \
|
11 |
-
libcairo2-dev \
|
12 |
-
libpango1.0-dev \
|
13 |
-
ffmpeg \
|
14 |
-
texlive-full \
|
15 |
-
dvisvgm \
|
16 |
-
fonts-dejavu \
|
17 |
-
&& apt-get clean \
|
18 |
-
&& rm -rf /var/lib/apt/lists/* \
|
19 |
-
&& which dvisvgm
|
20 |
-
|
21 |
-
WORKDIR /app
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
COPY
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
ENV
|
44 |
-
ENV
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
# Copy
|
50 |
-
COPY . /app
|
51 |
-
|
52 |
-
#
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
# Create directory for generated videos with proper permissions
|
57 |
-
RUN mkdir -p /app/generated_videos && \
|
58 |
-
chown -R appuser:appuser /app
|
59 |
-
|
60 |
-
# Switch to non-root user
|
61 |
-
USER appuser
|
62 |
-
|
63 |
-
# Expose the port for the Gradio interface
|
64 |
-
EXPOSE 7860
|
65 |
-
|
66 |
-
# Command to run the application with the virtual environment
|
67 |
CMD ["/app/manimations/bin/python", "ai_agent.py"]
|
|
|
1 |
+
FROM python:3.12-slim
|
2 |
+
|
3 |
+
# Install all dependencies in one layer
|
4 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
5 |
+
gcc \
|
6 |
+
libffi-dev \
|
7 |
+
curl \
|
8 |
+
ca-certificates \
|
9 |
+
python3-dev \
|
10 |
+
pkg-config \
|
11 |
+
libcairo2-dev \
|
12 |
+
libpango1.0-dev \
|
13 |
+
ffmpeg \
|
14 |
+
texlive-full \
|
15 |
+
dvisvgm \
|
16 |
+
fonts-dejavu \
|
17 |
+
&& apt-get clean \
|
18 |
+
&& rm -rf /var/lib/apt/lists/* \
|
19 |
+
&& which dvisvgm
|
20 |
+
|
21 |
+
WORKDIR /app
|
22 |
+
|
23 |
+
# Install uv for faster pip operations
|
24 |
+
ADD https://astral.sh/uv/install.sh /uv-installer.sh
|
25 |
+
RUN sh /uv-installer.sh && rm /uv-installer.sh
|
26 |
+
ENV PATH="/root/.local/bin:${PATH}"
|
27 |
+
|
28 |
+
# Copy virtual environment and project files
|
29 |
+
COPY requirements.txt .
|
30 |
+
|
31 |
+
|
32 |
+
# Activate the existing venv and install any missing packages
|
33 |
+
RUN uv venv /app/manimations \
|
34 |
+
&& . /app/manimations/bin/activate \
|
35 |
+
&& uv pip install --no-cache -r requirements.txt \
|
36 |
+
&& uv pip install --no-cache pycairo pangocffi manim
|
37 |
+
|
38 |
+
ENV PATH="/app/manimations/bin:${PATH}"
|
39 |
+
COPY *.py /app/
|
40 |
+
# Set environment variables
|
41 |
+
ENV PYTHONPATH=/app
|
42 |
+
ENV MPLBACKEND=Agg
|
43 |
+
ENV GRADIO_SERVER_NAME=0.0.0.0
|
44 |
+
ENV GRADIO_SERVER_PORT=7860
|
45 |
+
|
46 |
+
# Create directory for generated videos
|
47 |
+
RUN mkdir -p /app/generated_videos
|
48 |
+
|
49 |
+
# Copy .env file
|
50 |
+
COPY .env /app/.env
|
51 |
+
|
52 |
+
# Expose the port for the Gradio interface
|
53 |
+
EXPOSE 7860
|
54 |
+
|
55 |
+
# Command to run the application with the virtual environment
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
CMD ["/app/manimations/bin/python", "ai_agent.py"]
|
ai_agent.py
CHANGED
@@ -49,6 +49,27 @@ class AnimationResult(BaseModel):
|
|
49 |
code: str = Field(..., description="Generated Manim code")
|
50 |
video_path: str = Field(..., description="Path to the generated video file")
|
51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
model = OpenAIModel(
|
53 |
'deepseek-ai/DeepSeek-V3',
|
54 |
provider=OpenAIProvider(
|
@@ -66,6 +87,30 @@ manim_agent = Agent(
|
|
66 |
),
|
67 |
)
|
68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
# Configure OpenAI client to use Together API
|
70 |
client = openai.OpenAI(
|
71 |
api_key=os.environ.get("TOGETHER_API_KEY"),
|
@@ -98,6 +143,50 @@ def add_timestamp() -> str:
|
|
98 |
"""Add a timestamp to the system prompt."""
|
99 |
return f"Current date and time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
@manim_agent.tool
|
102 |
def extract_scenario(ctx: RunContext[AnimationPrompt]) -> AnimationScenario:
|
103 |
"""Extract a structured animation scenario from a text prompt."""
|
@@ -273,6 +362,302 @@ def generate_code(ctx: RunContext[AnimationPrompt], scenario: AnimationScenario)
|
|
273 |
)
|
274 |
return response.choices[0].message.content
|
275 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
276 |
@manim_agent.tool_plain
|
277 |
def render_animation(code: str, quality="medium_quality") -> str:
|
278 |
"""Render Manim code into a video. This doesn't need the context."""
|
@@ -461,14 +846,23 @@ memory = ConversationMemory()
|
|
461 |
def refine_animation(code: str, feedback: str, quality: str = "medium_quality") -> tuple:
|
462 |
"""Refine animation based on user feedback."""
|
463 |
try:
|
464 |
-
#
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
472 |
You are a Manim code expert. Your task is to refine animation code based on user feedback.
|
473 |
Keep the overall structure and purpose of the animation, but implement the changes requested.
|
474 |
Make sure the code remains valid and follows Manim best practices.
|
@@ -478,20 +872,21 @@ IMPORTANT REQUIREMENTS:
|
|
478 |
2. Ensure class name and structure remains consistent
|
479 |
3. All changes must be compatible with Manim Community edition
|
480 |
4. Do not explain your changes in comments outside of helpful inline comments
|
481 |
-
"""
|
482 |
-
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
|
|
495 |
|
496 |
# Render the refined code
|
497 |
video_path = render_manim_video(refined_code, quality)
|
@@ -602,14 +997,26 @@ VISUAL STRUCTURE AND LAYOUT:
|
|
602 |
1. Structure the animation as a narrative with clear sections (introduction, explanation, conclusion)
|
603 |
2. Create title screens with engaging typography and animations
|
604 |
3. Position ALL elements with EXPLICIT coordinates using shift() or move_to() methods
|
605 |
-
4. Ensure AT LEAST
|
606 |
5. For equations, use MathTex with proper scaling (scale(0.8) for complex equations)
|
607 |
6. Group related objects using VGroup and arrange them with arrange() method
|
608 |
7. When showing multiple equations, use arrange_in_grid() or arrange() with DOWN/RIGHT
|
609 |
8. For graphs, set explicit x_range and y_range with generous padding around functions
|
610 |
9. Scale ALL text elements appropriately (Title: 1.2, Headers: 1.0, Body: 0.8)
|
611 |
10. Use colors consistently and meaningfully (BLUE for emphasis, RED for important points)
|
612 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
613 |
|
614 |
ANIMATION TECHNIQUES:
|
615 |
1. Use FadeIn for introductions of new elements
|
@@ -617,7 +1024,7 @@ ANIMATION TECHNIQUES:
|
|
617 |
3. Use Create for drawing geometric objects
|
618 |
4. Implement smooth transitions between different concepts with ReplacementTransform
|
619 |
5. Highlight important parts with Indicate or Circumscribe
|
620 |
-
6. Add pauses (self.wait()
|
621 |
7. For complex animations, break them into smaller steps with appropriate timing
|
622 |
8. Use MoveAlongPath for demonstrating motion or change over time
|
623 |
9. Create emphasis with scale_about_point or succession of animations
|
@@ -642,7 +1049,224 @@ RESPOND WITH CLEAN, WELL-STRUCTURED CODE ONLY. DO NOT INCLUDE EXPLANATIONS OUTSI
|
|
642 |
{"role": "user", "content": f"Create a comprehensive Manim animation for '{scenario.title}' that teaches this concept: '{prompt}'. \n\nUse these mathematical objects: {objects_str}. \nImplement these transformations/animations: {transformations_str}. \nFeature these equations: {equations_str}. \n\nComplexity level: {complexity}. \n\nEnsure all elements are properly spaced and positioned to prevent overlap. Structure the animation with a clear introduction, step-by-step explanation, and conclusion."}
|
643 |
]
|
644 |
)
|
645 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
646 |
|
647 |
# Function to re-render animation with edited code
|
648 |
def rerender_animation(edited_code: str, quality: str = "medium_quality") -> tuple:
|
@@ -665,6 +1289,152 @@ def gradio_interface(prompt: str, complexity: str = "medium", quality: str = "me
|
|
665 |
else:
|
666 |
return code, None, log_output
|
667 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
668 |
# Replace the Gradio interface creation with a Blocks interface for better layout control
|
669 |
if __name__ == "__main__":
|
670 |
with gr.Blocks(title="Manimation Generator", theme=gr.themes.Base()) as demo:
|
@@ -707,6 +1477,17 @@ if __name__ == "__main__":
|
|
707 |
label="Your Feedback"
|
708 |
)
|
709 |
refine_btn = gr.Button("Apply Feedback", variant="secondary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
710 |
|
711 |
# Code editor (common to both tabs)
|
712 |
code_output = gr.Code(
|
@@ -766,6 +1547,23 @@ if __name__ == "__main__":
|
|
766 |
)
|
767 |
return video_path, log, new_history
|
768 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
769 |
# Connect the components to the function
|
770 |
generate_btn.click(
|
771 |
fn=generate_and_update_chat,
|
@@ -785,6 +1583,12 @@ if __name__ == "__main__":
|
|
785 |
outputs=[video_output, log_output, chat_history]
|
786 |
)
|
787 |
|
|
|
|
|
|
|
|
|
|
|
|
|
788 |
# Add footer with social media links
|
789 |
with gr.Row(equal_height=True):
|
790 |
gr.Markdown("""
|
@@ -799,3 +1603,4 @@ if __name__ == "__main__":
|
|
799 |
|
800 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|
801 |
|
|
|
|
49 |
code: str = Field(..., description="Generated Manim code")
|
50 |
video_path: str = Field(..., description="Path to the generated video file")
|
51 |
|
52 |
+
# New layout configuration model
|
53 |
+
class LayoutConfiguration(BaseModel):
|
54 |
+
"""Configuration for layout optimization of animation elements."""
|
55 |
+
min_spacing: float = Field(0.5, description="Minimum spacing between elements in Manim units")
|
56 |
+
vertical_alignment: List[str] = Field(["TOP", "CENTER", "BOTTOM"], description="Vertical alignment options")
|
57 |
+
horizontal_alignment: List[str] = Field(["LEFT", "CENTER", "RIGHT"], description="Horizontal alignment options")
|
58 |
+
staggered_animations: bool = Field(True, description="Whether to stagger animations for better clarity")
|
59 |
+
screen_regions: List[str] = Field(["UL", "UP", "UR", "LEFT", "CENTER", "RIGHT", "DL", "DOWN", "DR"],
|
60 |
+
description="Screen regions for element positioning")
|
61 |
+
canvas_size: tuple = Field((14, 8), description="Canvas size in Manim units (width, height)")
|
62 |
+
|
63 |
+
# New evaluation result model
|
64 |
+
class EvaluationResult(BaseModel):
|
65 |
+
"""Results of code evaluation."""
|
66 |
+
has_errors: bool = Field(False, description="Whether the code has any errors")
|
67 |
+
syntax_errors: List[str] = Field([], description="Syntax errors found in the code")
|
68 |
+
positioning_issues: List[str] = Field([], description="Issues with element positioning")
|
69 |
+
overlap_issues: List[str] = Field([], description="Potential element overlaps")
|
70 |
+
suggestions: List[str] = Field([], description="Suggestions for improvement")
|
71 |
+
fixed_code: Optional[str] = Field(None, description="Fixed code if available")
|
72 |
+
|
73 |
model = OpenAIModel(
|
74 |
'deepseek-ai/DeepSeek-V3',
|
75 |
provider=OpenAIProvider(
|
|
|
87 |
),
|
88 |
)
|
89 |
|
90 |
+
# Create a layout optimization agent
|
91 |
+
layout_agent = Agent(
|
92 |
+
model,
|
93 |
+
deps_type=AnimationPrompt,
|
94 |
+
system_prompt=(
|
95 |
+
"You are a specialized AI agent for optimizing layout and animations in Manim code. "
|
96 |
+
"Your goal is to analyze and improve element positioning, prevent overlaps, "
|
97 |
+
"and create step-by-step animations that are clear and educational. "
|
98 |
+
"You understand how to use Manim's coordinate system and positioning methods effectively."
|
99 |
+
),
|
100 |
+
)
|
101 |
+
|
102 |
+
# Create an evaluation agent
|
103 |
+
evaluation_agent = Agent(
|
104 |
+
model,
|
105 |
+
deps_type=AnimationPrompt,
|
106 |
+
system_prompt=(
|
107 |
+
"You are a specialized AI agent for evaluating Manim animation code. "
|
108 |
+
"Your goal is to detect errors, check for proper element positioning, "
|
109 |
+
"and ensure the code follows best practices for clear mathematical animations. "
|
110 |
+
"You understand Manim's syntax and common pitfalls in animation creation."
|
111 |
+
),
|
112 |
+
)
|
113 |
+
|
114 |
# Configure OpenAI client to use Together API
|
115 |
client = openai.OpenAI(
|
116 |
api_key=os.environ.get("TOGETHER_API_KEY"),
|
|
|
143 |
"""Add a timestamp to the system prompt."""
|
144 |
return f"Current date and time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
145 |
|
146 |
+
@layout_agent.system_prompt
|
147 |
+
def add_layout_guidance(ctx: RunContext[AnimationPrompt]) -> str:
|
148 |
+
"""Add layout guidance based on complexity."""
|
149 |
+
complexity = ctx.deps.complexity
|
150 |
+
if complexity == "simple":
|
151 |
+
return (
|
152 |
+
"Optimize layout for simple animations with minimal elements. "
|
153 |
+
"Use large spacing and clear positioning. "
|
154 |
+
"Each step should be very distinct and have ample wait time between transitions."
|
155 |
+
)
|
156 |
+
elif complexity == "complex":
|
157 |
+
return (
|
158 |
+
"Optimize layout for complex animations with many elements. "
|
159 |
+
"Use thoughtful positioning with elements grouped by relevance. "
|
160 |
+
"Break animations into logical steps with clear transitions between concepts."
|
161 |
+
)
|
162 |
+
else: # medium
|
163 |
+
return (
|
164 |
+
"Balance spacing and density in your layout. "
|
165 |
+
"Position elements with sufficient spacing while utilizing screen space efficiently. "
|
166 |
+
"Present animations in a step-by-step manner with appropriate timing."
|
167 |
+
)
|
168 |
+
|
169 |
+
@evaluation_agent.system_prompt
|
170 |
+
def add_evaluation_guidance(ctx: RunContext[AnimationPrompt]) -> str:
|
171 |
+
"""Add evaluation guidance based on complexity."""
|
172 |
+
complexity = ctx.deps.complexity
|
173 |
+
if complexity == "simple":
|
174 |
+
return (
|
175 |
+
"Focus on finding basic errors and ensuring clear positioning of minimal elements. "
|
176 |
+
"Simple animations should have ample spacing and no overlapping elements."
|
177 |
+
)
|
178 |
+
elif complexity == "complex":
|
179 |
+
return (
|
180 |
+
"Look for subtle issues in complex code, including potential element overlaps "
|
181 |
+
"when multiple transformations occur. Check for proper timing between steps "
|
182 |
+
"and verify that complex mathematical notations are correctly formatted."
|
183 |
+
)
|
184 |
+
else: # medium
|
185 |
+
return (
|
186 |
+
"Balance between checking for technical errors and verifying good animation principles. "
|
187 |
+
"Ensure elements are properly spaced and animations follow a logical step-by-step flow."
|
188 |
+
)
|
189 |
+
|
190 |
@manim_agent.tool
|
191 |
def extract_scenario(ctx: RunContext[AnimationPrompt]) -> AnimationScenario:
|
192 |
"""Extract a structured animation scenario from a text prompt."""
|
|
|
362 |
)
|
363 |
return response.choices[0].message.content
|
364 |
|
365 |
+
@layout_agent.tool
|
366 |
+
def analyze_element_layout(ctx: RunContext[AnimationPrompt], code: str) -> dict:
|
367 |
+
"""Analyze Manim code for potential layout issues and element positioning."""
|
368 |
+
prompt = ctx.deps
|
369 |
+
|
370 |
+
response = client.chat.completions.create(
|
371 |
+
model=llm,
|
372 |
+
messages=[
|
373 |
+
{"role": "system", "content": """
|
374 |
+
Analyze Manim code for layout issues and element positioning. Look for:
|
375 |
+
1. Overlapping elements or text
|
376 |
+
2. Elements positioned too close to each other
|
377 |
+
3. Elements positioned off-screen or at extreme edges
|
378 |
+
4. Poor use of screen space
|
379 |
+
5. Too many elements appearing simultaneously
|
380 |
+
6. Lack of clear positioning commands
|
381 |
+
|
382 |
+
Respond with a JSON object containing:
|
383 |
+
- issues: List of detected layout issues
|
384 |
+
- suggestions: List of positioning improvements
|
385 |
+
- animation_flow: List of animation sequence improvements
|
386 |
+
- spacing: Suggested minimum spacing between elements
|
387 |
+
- regions: Suggested screen regions to use for key elements
|
388 |
+
"""
|
389 |
+
},
|
390 |
+
{"role": "user", "content": f"Analyze this Manim code for layout issues:\n\n```python\n{code}\n```\n\nPrompt: {prompt.description}, Complexity: {prompt.complexity}"}
|
391 |
+
]
|
392 |
+
)
|
393 |
+
|
394 |
+
content = response.choices[0].message.content
|
395 |
+
|
396 |
+
try:
|
397 |
+
# Extract JSON from response
|
398 |
+
json_match = re.search(r'\{.*\}', content, re.DOTALL)
|
399 |
+
if json_match:
|
400 |
+
json_str = json_match.group(0)
|
401 |
+
analysis = json.loads(json_str)
|
402 |
+
return analysis
|
403 |
+
except Exception as e:
|
404 |
+
logger.error(f"Error parsing layout analysis: {e}")
|
405 |
+
|
406 |
+
# Fallback with default values
|
407 |
+
return {
|
408 |
+
"issues": ["Potential element overlap", "Undefined positioning"],
|
409 |
+
"suggestions": ["Use explicit coordinates for all elements", "Add spacing between elements"],
|
410 |
+
"animation_flow": ["Break complex animations into steps", "Add wait time between steps"],
|
411 |
+
"spacing": 1.0,
|
412 |
+
"regions": ["UP", "DOWN", "LEFT", "RIGHT", "CENTER"]
|
413 |
+
}
|
414 |
+
|
415 |
+
@layout_agent.tool
|
416 |
+
def optimize_layout(ctx: RunContext[AnimationPrompt], code: str, analysis: dict) -> str:
|
417 |
+
"""Optimize the layout of elements in Manim code."""
|
418 |
+
prompt = ctx.deps
|
419 |
+
|
420 |
+
# Serialize the analysis for the prompt
|
421 |
+
analysis_str = json.dumps(analysis, indent=2)
|
422 |
+
|
423 |
+
response = client.chat.completions.create(
|
424 |
+
model=llm,
|
425 |
+
messages=[
|
426 |
+
{"role": "system", "content": """
|
427 |
+
Optimize the layout and animation flow in Manim code. Follow these rules:
|
428 |
+
1. Explicitly position ALL elements with coordinates (e.g., .move_to(), .shift(), .to_edge())
|
429 |
+
2. Ensure minimum spacing (1.0 units) between all elements
|
430 |
+
3. Use screen regions effectively (UP, DOWN, LEFT, RIGHT, UL, UR, DL, DR)
|
431 |
+
4. Group related elements using VGroup and arrange them logically
|
432 |
+
5. Break complex animations into steps with self.wait() between them
|
433 |
+
6. Use sequential animations for clarity (one concept at a time)
|
434 |
+
7. Use consistent positioning and transitions throughout the animation
|
435 |
+
8. Add comments explaining positioning choices
|
436 |
+
|
437 |
+
Preserve all mathematical content and educational purpose of the animation.
|
438 |
+
Only make changes to improve layout, positioning, and animation flow.
|
439 |
+
"""
|
440 |
+
},
|
441 |
+
{"role": "user", "content": f"Original code:\n\n```python\n{code}\n```\n\nOptimize the layout based on this analysis:\n{analysis_str}\n\nPrompt: {prompt.description}, Complexity: {prompt.complexity}\n\nReturn the optimized code that fixes all layout issues."}
|
442 |
+
]
|
443 |
+
)
|
444 |
+
|
445 |
+
optimized_code = response.choices[0].message.content
|
446 |
+
|
447 |
+
# Clean up the response to extract just the code
|
448 |
+
if "```python" in optimized_code:
|
449 |
+
optimized_code = optimized_code.split("```python", 1)[1]
|
450 |
+
if "```" in optimized_code:
|
451 |
+
optimized_code = optimized_code.split("```", 1)[0]
|
452 |
+
|
453 |
+
return optimized_code.strip()
|
454 |
+
|
455 |
+
@evaluation_agent.tool
|
456 |
+
def check_syntax_errors(ctx: RunContext[AnimationPrompt], code: str) -> List[str]:
|
457 |
+
"""Check for Python and Manim-specific syntax errors."""
|
458 |
+
prompt = ctx.deps
|
459 |
+
|
460 |
+
response = client.chat.completions.create(
|
461 |
+
model=llm,
|
462 |
+
messages=[
|
463 |
+
{"role": "system", "content": """
|
464 |
+
Analyze this Manim code for syntax errors and logical mistakes. Look for:
|
465 |
+
|
466 |
+
1. Python syntax errors (missing colons, parentheses, indentation problems)
|
467 |
+
2. Manim-specific errors (incorrect class usage, invalid animation methods)
|
468 |
+
3. Undefined variables or objects that are used before definition
|
469 |
+
4. Incorrect parameter types or values
|
470 |
+
5. Missing imports or misused Manim classes
|
471 |
+
6. LaTeX syntax errors in MathTex objects
|
472 |
+
7. Animation errors (using wrong objects in animations, incorrect method calls)
|
473 |
+
|
474 |
+
For each error found, provide:
|
475 |
+
1. The line number or code region with the error
|
476 |
+
2. A description of what's wrong
|
477 |
+
3. A suggested fix
|
478 |
+
|
479 |
+
Be thorough but only focus on actual errors, not style issues.
|
480 |
+
"""
|
481 |
+
},
|
482 |
+
{"role": "user", "content": f"Check this Manim code for syntax errors:\n\n```python\n{code}\n```\n\nPrompt: {prompt.description}, Complexity: {prompt.complexity}"}
|
483 |
+
]
|
484 |
+
)
|
485 |
+
|
486 |
+
# Extract errors from response
|
487 |
+
error_content = response.choices[0].message.content
|
488 |
+
error_lines = error_content.split('\n')
|
489 |
+
|
490 |
+
# Filter for actual errors
|
491 |
+
errors = []
|
492 |
+
current_error = ""
|
493 |
+
for line in error_lines:
|
494 |
+
if line.strip().startswith(("Error", "Issue", "Problem", "Bug", "Line", "1.", "2.", "3.", "4.", "5.")):
|
495 |
+
if current_error:
|
496 |
+
errors.append(current_error.strip())
|
497 |
+
current_error = line.strip()
|
498 |
+
elif current_error and line.strip():
|
499 |
+
current_error += " " + line.strip()
|
500 |
+
|
501 |
+
# Add the last error if there is one
|
502 |
+
if current_error:
|
503 |
+
errors.append(current_error.strip())
|
504 |
+
|
505 |
+
return errors
|
506 |
+
|
507 |
+
@evaluation_agent.tool
|
508 |
+
def check_positioning(ctx: RunContext[AnimationPrompt], code: str) -> dict:
|
509 |
+
"""Check for proper positioning and potential overlaps in the animation."""
|
510 |
+
prompt = ctx.deps
|
511 |
+
|
512 |
+
response = client.chat.completions.create(
|
513 |
+
model=llm,
|
514 |
+
messages=[
|
515 |
+
{"role": "system", "content": """
|
516 |
+
Analyze this Manim code specifically for positioning and spacing issues. Look for:
|
517 |
+
|
518 |
+
1. Objects without explicit position commands (move_to, shift, to_edge, etc.)
|
519 |
+
2. Elements that might overlap based on their coordinates
|
520 |
+
3. Text or equations positioned too close to each other
|
521 |
+
4. Elements positioned too close to the edge of the screen
|
522 |
+
5. Improper grouping of related elements
|
523 |
+
6. Elements with undefined positioning that might appear at origin (0,0)
|
524 |
+
7. Animations where multiple elements move to the same location
|
525 |
+
|
526 |
+
Analyze the coordinates and create a mental map of where objects are positioned.
|
527 |
+
Flag any positions where elements might overlap or be too close (less than 1.0 units apart).
|
528 |
+
|
529 |
+
Respond with a JSON object containing:
|
530 |
+
- positioning_issues: List of positioning problems found
|
531 |
+
- overlap_issues: List of specific coordinates or elements that might overlap
|
532 |
+
- suggestions: Specific suggestions to improve positioning
|
533 |
+
"""
|
534 |
+
},
|
535 |
+
{"role": "user", "content": f"Analyze this Manim code for positioning and spacing issues:\n\n```python\n{code}\n```\n\nPrompt: {prompt.description}, Complexity: {prompt.complexity}"}
|
536 |
+
]
|
537 |
+
)
|
538 |
+
|
539 |
+
content = response.choices[0].message.content
|
540 |
+
|
541 |
+
try:
|
542 |
+
# Extract JSON from response
|
543 |
+
json_match = re.search(r'\{.*\}', content, re.DOTALL)
|
544 |
+
if json_match:
|
545 |
+
json_str = json_match.group(0)
|
546 |
+
positioning_analysis = json.loads(json_str)
|
547 |
+
return positioning_analysis
|
548 |
+
except Exception as e:
|
549 |
+
logger.error(f"Error parsing positioning analysis: {e}")
|
550 |
+
|
551 |
+
# If no valid JSON is found, extract information manually
|
552 |
+
positioning_issues = []
|
553 |
+
overlap_issues = []
|
554 |
+
suggestions = []
|
555 |
+
|
556 |
+
# Simple pattern matching to extract issues
|
557 |
+
for line in content.split('\n'):
|
558 |
+
line = line.strip()
|
559 |
+
if "position" in line.lower() or "coordinate" in line.lower() or "overlap" in line.lower():
|
560 |
+
if line.startswith(("- ", "* ", "1. ", "2. ")):
|
561 |
+
positioning_issues.append(line.lstrip("- *123456789. "))
|
562 |
+
if "overlap" in line.lower():
|
563 |
+
if line.startswith(("- ", "* ", "1. ", "2. ")):
|
564 |
+
overlap_issues.append(line.lstrip("- *123456789. "))
|
565 |
+
if "suggest" in line.lower() or "should" in line.lower() or "could" in line.lower():
|
566 |
+
if line.startswith(("- ", "* ", "1. ", "2. ")):
|
567 |
+
suggestions.append(line.lstrip("- *123456789. "))
|
568 |
+
|
569 |
+
return {
|
570 |
+
"positioning_issues": positioning_issues,
|
571 |
+
"overlap_issues": overlap_issues,
|
572 |
+
"suggestions": suggestions
|
573 |
+
}
|
574 |
+
|
575 |
+
@evaluation_agent.tool
|
576 |
+
def fix_code_issues(ctx: RunContext[AnimationPrompt], code: str, syntax_errors: List[str], positioning_issues: dict) -> str:
|
577 |
+
"""Fix detected issues in the code."""
|
578 |
+
prompt = ctx.deps
|
579 |
+
|
580 |
+
# Format issues for the prompt
|
581 |
+
syntax_errors_str = "\n".join([f"- {error}" for error in syntax_errors])
|
582 |
+
|
583 |
+
positioning_issues_str = ""
|
584 |
+
if "positioning_issues" in positioning_issues:
|
585 |
+
positioning_issues_str += "\nPositioning Issues:\n" + "\n".join([f"- {issue}" for issue in positioning_issues["positioning_issues"]])
|
586 |
+
|
587 |
+
if "overlap_issues" in positioning_issues:
|
588 |
+
positioning_issues_str += "\nOverlap Issues:\n" + "\n".join([f"- {issue}" for issue in positioning_issues["overlap_issues"]])
|
589 |
+
|
590 |
+
if "suggestions" in positioning_issues:
|
591 |
+
positioning_issues_str += "\nSuggestions:\n" + "\n".join([f"- {suggestion}" for suggestion in positioning_issues["suggestions"]])
|
592 |
+
|
593 |
+
response = client.chat.completions.create(
|
594 |
+
model=llm,
|
595 |
+
messages=[
|
596 |
+
{"role": "system", "content": """
|
597 |
+
Fix the provided Manim code by addressing all identified issues. Follow these guidelines:
|
598 |
+
|
599 |
+
1. Fix all syntax errors and logical mistakes first
|
600 |
+
2. Fix positioning issues by adding explicit positioning commands
|
601 |
+
3. Resolve element overlaps by repositioning elements with adequate spacing
|
602 |
+
4. Implement all positioning suggestions to improve clarity
|
603 |
+
5. Maintain the original educational intent and mathematical content
|
604 |
+
6. Ensure all animations follow a logical step-by-step flow
|
605 |
+
7. Add comments explaining your fixes for complex changes
|
606 |
+
|
607 |
+
Return the complete, corrected code ready for rendering.
|
608 |
+
"""
|
609 |
+
},
|
610 |
+
{"role": "user", "content":
|
611 |
+
f"Fix the following Manim code by addressing these issues:\n\n"
|
612 |
+
f"Syntax Errors:\n{syntax_errors_str}\n\n"
|
613 |
+
f"Positioning Issues:{positioning_issues_str}\n\n"
|
614 |
+
f"Original Code:\n```python\n{code}\n```\n\n"
|
615 |
+
f"Original Prompt: {prompt.description}, Complexity: {prompt.complexity}\n\n"
|
616 |
+
f"Return the complete fixed code."
|
617 |
+
}
|
618 |
+
]
|
619 |
+
)
|
620 |
+
|
621 |
+
fixed_code = response.choices[0].message.content
|
622 |
+
|
623 |
+
# Clean up the response to extract just the code
|
624 |
+
if "```python" in fixed_code:
|
625 |
+
fixed_code = fixed_code.split("```python", 1)[1]
|
626 |
+
if "```" in fixed_code:
|
627 |
+
fixed_code = fixed_code.split("```", 1)[0]
|
628 |
+
|
629 |
+
return fixed_code.strip()
|
630 |
+
|
631 |
+
@evaluation_agent.tool
|
632 |
+
def evaluate_code(ctx: RunContext[AnimationPrompt], code: str) -> EvaluationResult:
|
633 |
+
"""Evaluate Manim code for errors and positioning issues."""
|
634 |
+
# Check for syntax errors
|
635 |
+
syntax_errors = check_syntax_errors(ctx, code)
|
636 |
+
|
637 |
+
# Check for positioning issues
|
638 |
+
positioning_analysis = check_positioning(ctx, code)
|
639 |
+
|
640 |
+
positioning_issues = positioning_analysis.get("positioning_issues", [])
|
641 |
+
overlap_issues = positioning_analysis.get("overlap_issues", [])
|
642 |
+
suggestions = positioning_analysis.get("suggestions", [])
|
643 |
+
|
644 |
+
# Determine if there are errors
|
645 |
+
has_errors = len(syntax_errors) > 0 or len(positioning_issues) > 0 or len(overlap_issues) > 0
|
646 |
+
|
647 |
+
# If there are errors, fix the code
|
648 |
+
fixed_code = None
|
649 |
+
if has_errors:
|
650 |
+
fixed_code = fix_code_issues(ctx, code, syntax_errors, positioning_analysis)
|
651 |
+
|
652 |
+
return EvaluationResult(
|
653 |
+
has_errors=has_errors,
|
654 |
+
syntax_errors=syntax_errors,
|
655 |
+
positioning_issues=positioning_issues,
|
656 |
+
overlap_issues=overlap_issues,
|
657 |
+
suggestions=suggestions,
|
658 |
+
fixed_code=fixed_code
|
659 |
+
)
|
660 |
+
|
661 |
@manim_agent.tool_plain
|
662 |
def render_animation(code: str, quality="medium_quality") -> str:
|
663 |
"""Render Manim code into a video. This doesn't need the context."""
|
|
|
846 |
def refine_animation(code: str, feedback: str, quality: str = "medium_quality") -> tuple:
|
847 |
"""Refine animation based on user feedback."""
|
848 |
try:
|
849 |
+
# Special case for layout/positioning feedback
|
850 |
+
if any(keyword in feedback.lower() for keyword in ["position", "layout", "overlap", "spacing", "step by step", "clear"]):
|
851 |
+
# Try to apply specialized layout optimization
|
852 |
+
prompt = memory.history[-1]["prompt"] if memory.history else "Mathematical animation"
|
853 |
+
complexity = "medium" # Default complexity
|
854 |
+
|
855 |
+
refined_code = optimize_element_positioning(code, prompt, complexity)
|
856 |
+
else:
|
857 |
+
# Original feedback processing code
|
858 |
+
# Get context from memory
|
859 |
+
context = memory.get_context_for_refinement()
|
860 |
+
|
861 |
+
# Use LLM to refine the code based on feedback
|
862 |
+
response = client.chat.completions.create(
|
863 |
+
model=llm,
|
864 |
+
messages=[
|
865 |
+
{"role": "system", "content": """
|
866 |
You are a Manim code expert. Your task is to refine animation code based on user feedback.
|
867 |
Keep the overall structure and purpose of the animation, but implement the changes requested.
|
868 |
Make sure the code remains valid and follows Manim best practices.
|
|
|
872 |
2. Ensure class name and structure remains consistent
|
873 |
3. All changes must be compatible with Manim Community edition
|
874 |
4. Do not explain your changes in comments outside of helpful inline comments
|
875 |
+
"""
|
876 |
+
},
|
877 |
+
{"role": "user", "content": f"Here is the current Manim animation code:\n\n```python\n{code}\n```\n\n{context}\nPlease refine this code based on this feedback: \"{feedback}\"\n\nReturn only the improved code."}
|
878 |
+
]
|
879 |
+
)
|
880 |
+
|
881 |
+
refined_code = response.choices[0].message.content.strip()
|
882 |
+
|
883 |
+
# Remove any markdown code formatting if present
|
884 |
+
if refined_code.startswith("```python"):
|
885 |
+
refined_code = refined_code.split("```python", 1)[1]
|
886 |
+
if refined_code.endswith("```"):
|
887 |
+
refined_code = refined_code.rsplit("```", 1)[0]
|
888 |
+
|
889 |
+
refined_code = refined_code.strip()
|
890 |
|
891 |
# Render the refined code
|
892 |
video_path = render_manim_video(refined_code, quality)
|
|
|
997 |
1. Structure the animation as a narrative with clear sections (introduction, explanation, conclusion)
|
998 |
2. Create title screens with engaging typography and animations
|
999 |
3. Position ALL elements with EXPLICIT coordinates using shift() or move_to() methods
|
1000 |
+
4. Ensure AT LEAST 2.0 units of space between separate visual elements
|
1001 |
5. For equations, use MathTex with proper scaling (scale(0.8) for complex equations)
|
1002 |
6. Group related objects using VGroup and arrange them with arrange() method
|
1003 |
7. When showing multiple equations, use arrange_in_grid() or arrange() with DOWN/RIGHT
|
1004 |
8. For graphs, set explicit x_range and y_range with generous padding around functions
|
1005 |
9. Scale ALL text elements appropriately (Title: 1.2, Headers: 1.0, Body: 0.8)
|
1006 |
10. Use colors consistently and meaningfully (BLUE for emphasis, RED for important points)
|
1007 |
+
|
1008 |
+
CRITICAL: ELEMENT MANAGEMENT AND STEP-BY-STEP REQUIREMENTS:
|
1009 |
+
1. NEVER show too many elements on screen at once - max 3-4 related elements at any time
|
1010 |
+
2. ALWAYS use self.play(FadeOut(element)) to explicitly remove elements when moving to a new concept
|
1011 |
+
3. DO NOT use self.clear() as it doesn't actually remove elements from the scene
|
1012 |
+
4. Implement strict SEQUENTIAL animation - introduce only ONE concept or element at a time
|
1013 |
+
5. Use self.wait(0.7) to 1.5 for short pauses and self.wait(2) for important concepts
|
1014 |
+
6. Organize the screen into distinct regions (TOP for titles, CENTER for main content, BOTTOM for explanations)
|
1015 |
+
7. For sequential steps in derivations or proofs, use transform_matching_tex() to smoothly evolve equations
|
1016 |
+
8. Use MoveToTarget() for repositioning elements that need to stay on screen between steps
|
1017 |
+
9. At the end of each section, EXPLICITLY remove all elements with self.play(FadeOut(elem1, elem2, ...))
|
1018 |
+
10. When positioning new elements, verify they won't overlap existing elements
|
1019 |
+
11. For elements that must appear together, use VGroup but animate their creation one by one
|
1020 |
|
1021 |
ANIMATION TECHNIQUES:
|
1022 |
1. Use FadeIn for introductions of new elements
|
|
|
1024 |
3. Use Create for drawing geometric objects
|
1025 |
4. Implement smooth transitions between different concepts with ReplacementTransform
|
1026 |
5. Highlight important parts with Indicate or Circumscribe
|
1027 |
+
6. Add appropriate pauses: self.wait(0.7) after minor steps, self.wait(1.5) after important points
|
1028 |
7. For complex animations, break them into smaller steps with appropriate timing
|
1029 |
8. Use MoveAlongPath for demonstrating motion or change over time
|
1030 |
9. Create emphasis with scale_about_point or succession of animations
|
|
|
1049 |
{"role": "user", "content": f"Create a comprehensive Manim animation for '{scenario.title}' that teaches this concept: '{prompt}'. \n\nUse these mathematical objects: {objects_str}. \nImplement these transformations/animations: {transformations_str}. \nFeature these equations: {equations_str}. \n\nComplexity level: {complexity}. \n\nEnsure all elements are properly spaced and positioned to prevent overlap. Structure the animation with a clear introduction, step-by-step explanation, and conclusion."}
|
1050 |
]
|
1051 |
)
|
1052 |
+
|
1053 |
+
initial_code = response.choices[0].message.content
|
1054 |
+
|
1055 |
+
# Analyze and optimize the layout using the layout agent
|
1056 |
+
try:
|
1057 |
+
# Create prompt object for context
|
1058 |
+
prompt_obj = AnimationPrompt(description=prompt, complexity=complexity)
|
1059 |
+
|
1060 |
+
# Fix: Don't pass 'code' as a keyword argument to run_sync
|
1061 |
+
# Instead, include the code in the prompt text
|
1062 |
+
layout_result = layout_agent.run_sync(
|
1063 |
+
f"Analyze and optimize the layout of this Manim code for the prompt: {prompt}\n\n"
|
1064 |
+
f"```python\n{initial_code}\n```",
|
1065 |
+
deps=prompt_obj
|
1066 |
+
)
|
1067 |
+
|
1068 |
+
# If the layout agent successfully returned optimized code, use that
|
1069 |
+
if isinstance(layout_result, str) and "from manim import" in layout_result:
|
1070 |
+
# Extract the code part if it returned markdown-formatted code
|
1071 |
+
if "```python" in layout_result:
|
1072 |
+
layout_result = layout_result.split("```python", 1)[1].split("```", 1)[0].strip()
|
1073 |
+
return layout_result
|
1074 |
+
|
1075 |
+
# Fix: Don't manually create a RunContext
|
1076 |
+
# Instead use a direct approach for optimization
|
1077 |
+
optimized_code = direct_optimize_layout(initial_code, prompt, complexity)
|
1078 |
+
|
1079 |
+
if optimized_code and "from manim import" in optimized_code:
|
1080 |
+
return optimized_code
|
1081 |
+
except Exception as e:
|
1082 |
+
logger.error(f"Error during layout optimization: {e}")
|
1083 |
+
# If optimization fails, return the initial code
|
1084 |
+
|
1085 |
+
return initial_code
|
1086 |
+
|
1087 |
+
# Add direct implementation of layout optimization functions
|
1088 |
+
def direct_analyze_layout(code: str, prompt: str, complexity: str = "medium") -> dict:
|
1089 |
+
"""Analyze Manim code for layout issues without using agent tools."""
|
1090 |
+
try:
|
1091 |
+
response = client.chat.completions.create(
|
1092 |
+
model=llm,
|
1093 |
+
messages=[
|
1094 |
+
{"role": "system", "content": """
|
1095 |
+
Analyze Manim code for layout issues and element positioning. Look for:
|
1096 |
+
1. Overlapping elements or text
|
1097 |
+
2. Elements positioned too close to each other
|
1098 |
+
3. Elements positioned off-screen or at extreme edges
|
1099 |
+
4. Poor use of screen space
|
1100 |
+
5. Too many elements appearing simultaneously
|
1101 |
+
6. Lack of clear positioning commands
|
1102 |
+
|
1103 |
+
Respond with a JSON object containing:
|
1104 |
+
- issues: List of detected layout issues
|
1105 |
+
- suggestions: List of positioning improvements
|
1106 |
+
- animation_flow: List of animation sequence improvements
|
1107 |
+
- spacing: Suggested minimum spacing between elements
|
1108 |
+
- regions: Suggested screen regions to use for key elements
|
1109 |
+
"""
|
1110 |
+
},
|
1111 |
+
{"role": "user", "content": f"Analyze this Manim code for layout issues:\n\n```python\n{code}\n```\n\nPrompt: {prompt}, Complexity: {complexity}"}
|
1112 |
+
]
|
1113 |
+
)
|
1114 |
+
|
1115 |
+
content = response.choices[0].message.content
|
1116 |
+
|
1117 |
+
try:
|
1118 |
+
# Extract JSON from response
|
1119 |
+
json_match = re.search(r'\{.*\}', content, re.DOTALL)
|
1120 |
+
if json_match:
|
1121 |
+
json_str = json_match.group(0)
|
1122 |
+
analysis = json.loads(json_str)
|
1123 |
+
return analysis
|
1124 |
+
except Exception as e:
|
1125 |
+
logger.error(f"Error parsing layout analysis: {e}")
|
1126 |
+
|
1127 |
+
# Fallback with default values
|
1128 |
+
return {
|
1129 |
+
"issues": ["Potential element overlap", "Undefined positioning"],
|
1130 |
+
"suggestions": ["Use explicit coordinates for all elements", "Add spacing between elements"],
|
1131 |
+
"animation_flow": ["Break complex animations into steps", "Add wait time between steps"],
|
1132 |
+
"spacing": 1.0,
|
1133 |
+
"regions": ["UP", "DOWN", "LEFT", "RIGHT", "CENTER"]
|
1134 |
+
}
|
1135 |
+
except Exception as e:
|
1136 |
+
logger.error(f"Error in direct_analyze_layout: {e}")
|
1137 |
+
return {
|
1138 |
+
"issues": ["Analysis failed"],
|
1139 |
+
"suggestions": ["Check code manually"],
|
1140 |
+
"animation_flow": [],
|
1141 |
+
"spacing": 1.0,
|
1142 |
+
"regions": ["CENTER"]
|
1143 |
+
}
|
1144 |
+
|
1145 |
+
def direct_optimize_layout(code: str, prompt: str, complexity: str = "medium") -> str:
|
1146 |
+
"""Optimize layout in Manim code without using agent tools."""
|
1147 |
+
try:
|
1148 |
+
# First, analyze the layout
|
1149 |
+
analysis = direct_analyze_layout(code, prompt, complexity)
|
1150 |
+
|
1151 |
+
# Serialize the analysis for the prompt
|
1152 |
+
analysis_str = json.dumps(analysis, indent=2)
|
1153 |
+
|
1154 |
+
response = client.chat.completions.create(
|
1155 |
+
model=llm,
|
1156 |
+
messages=[
|
1157 |
+
{"role": "system", "content": """
|
1158 |
+
Optimize the layout and animation flow in Manim code. Follow these strict rules:
|
1159 |
+
|
1160 |
+
ELEMENT POSITIONING AND SPACING:
|
1161 |
+
1. Explicitly position ALL elements with coordinates (e.g., .move_to(), .shift(), .to_edge())
|
1162 |
+
2. Ensure minimum spacing of 2.0 units between all elements
|
1163 |
+
3. Use screen regions effectively (UP, DOWN, LEFT, RIGHT, UL, UR, DL, DR)
|
1164 |
+
4. Group related elements using VGroup and arrange them with arrange(direction, buff=1.0)
|
1165 |
+
5. Add buffer around elements: .move_to(point).shift(UP*0.5) to ensure spacing
|
1166 |
+
6. Use coordinate grid to map element positions: x values from -6 to 6, y values from -3.5 to 3.5
|
1167 |
+
7. Shrink elements to 80% size when needed with scale(0.8)
|
1168 |
+
|
1169 |
+
STEP-BY-STEP ANIMATION FLOW:
|
1170 |
+
1. CRITICAL: Use self.play(FadeOut(element)) to explicitly remove elements when done with them
|
1171 |
+
2. DO NOT use self.clear() as it doesn't actually remove elements from the scene
|
1172 |
+
3. Divide the animation into clear sequences with comments like "# Step 1: Introduction"
|
1173 |
+
4. Use appropriate wait times: self.wait(0.7) for minor steps, self.wait(1.5) for new concepts
|
1174 |
+
5. At the end of each major section, add: self.play(FadeOut(*[all_objects_in_current_section]))
|
1175 |
+
6. Max 3-4 elements should be visible simultaneously
|
1176 |
+
7. For each step, state element positions clearly in comments
|
1177 |
+
8. Use sequential animations (one element at a time) rather than AnimationGroup
|
1178 |
+
|
1179 |
+
FIXES FOR COMMON PROBLEMS:
|
1180 |
+
1. Add .to_edge(direction) to all Tex/MathTex elements
|
1181 |
+
2. For Title elements, always use .to_edge(UP)
|
1182 |
+
3. For equations, use .scale(0.8).next_to(previous_element, DOWN*2)
|
1183 |
+
4. For diagrams, center them with .move_to(ORIGIN)
|
1184 |
+
5. For graphs, explicitly set axes ranges with x_range=[-5, 5, 1], y_range=[-3, 3, 1]
|
1185 |
+
6. For multiple text elements, align them with .align_to(reference, direction)
|
1186 |
+
7. For explanatory text, position at the bottom with .to_edge(DOWN)
|
1187 |
+
8. Before introducing new sections, add: self.play(FadeOut(*[all_current_elements]))
|
1188 |
+
|
1189 |
+
Preserve all mathematical content and educational purpose of the animation.
|
1190 |
+
Only make changes to improve layout, positioning, and animation flow.
|
1191 |
+
"""
|
1192 |
+
},
|
1193 |
+
{"role": "user", "content": f"Original code:\n\n```python\n{code}\n```\n\nOptimize the layout based on this analysis:\n{analysis_str}\n\nPrompt: {prompt}, Complexity: {complexity}\n\nReturn the optimized code that ensures a step-by-step animation with proper spacing and element removal to prevent overlaps."}
|
1194 |
+
]
|
1195 |
+
)
|
1196 |
+
|
1197 |
+
optimized_code = response.choices[0].message.content
|
1198 |
+
|
1199 |
+
# Clean up the response to extract just the code
|
1200 |
+
if "```python" in optimized_code:
|
1201 |
+
optimized_code = optimized_code.split("```python", 1)[1]
|
1202 |
+
if "```" in optimized_code:
|
1203 |
+
optimized_code = optimized_code.split("```", 1)[0]
|
1204 |
+
|
1205 |
+
return optimized_code.strip()
|
1206 |
+
except Exception as e:
|
1207 |
+
logger.error(f"Error in direct_optimize_layout: {e}")
|
1208 |
+
return code # Return original code if optimization fails
|
1209 |
+
|
1210 |
+
# Add a new function to specifically check and fix positioning issues in existing code
|
1211 |
+
def optimize_element_positioning(code: str, prompt: str, complexity: str = "medium") -> str:
|
1212 |
+
"""Analyze and optimize element positioning in Manim code."""
|
1213 |
+
try:
|
1214 |
+
response = client.chat.completions.create(
|
1215 |
+
model=llm,
|
1216 |
+
messages=[
|
1217 |
+
{"role": "system", "content": """
|
1218 |
+
You are a Manim layout expert. Review the provided code and improve element positioning and animation flow.
|
1219 |
+
Focus on these critical aspects:
|
1220 |
+
|
1221 |
+
1. STEP-BY-STEP ANIMATION:
|
1222 |
+
- CRITICAL: Add explicit self.play(FadeOut(...)) to remove elements when they're no longer needed
|
1223 |
+
- DO NOT use self.clear() as it doesn't actually remove elements from the scene
|
1224 |
+
- Ensure only 3-4 related elements are visible at once
|
1225 |
+
- Sequence animations to show just one new element at a time
|
1226 |
+
- Use appropriate wait times: self.wait(0.7) for minor points, self.wait(1.5) for important concepts
|
1227 |
+
|
1228 |
+
2. POSITIONING AND SPACING:
|
1229 |
+
- Position ALL elements with explicit coordinates: move_to(), shift(), to_edge(), etc.
|
1230 |
+
- Maintain AT LEAST 2.0 units of space between elements
|
1231 |
+
- Use all screen regions effectively: UP, DOWN, LEFT, RIGHT, UL, UR, DL, DR, etc.
|
1232 |
+
- Use coordinate grid system: x values from -6 to 6, y values from -3.5 to 3.5
|
1233 |
+
- Scale elements with .scale(0.8) when needed to prevent overlap
|
1234 |
+
|
1235 |
+
3. ELEMENT ORGANIZATION:
|
1236 |
+
- Group related elements using VGroup and arrange them with arrange(direction, buff=1.0)
|
1237 |
+
- Position titles at the top with .to_edge(UP)
|
1238 |
+
- Position explanatory text at the bottom with .to_edge(DOWN)
|
1239 |
+
- Center diagrams with .move_to(ORIGIN)
|
1240 |
+
- For multiple text elements, use .align_to(reference, direction)
|
1241 |
+
|
1242 |
+
4. ELEMENT CLEANUP:
|
1243 |
+
- At the end of each section, add: self.play(FadeOut(*[all_objects_in_section]))
|
1244 |
+
- For elements that transform, use ReplacementTransform not Transform
|
1245 |
+
- Keep track of all created elements and remove them when not needed
|
1246 |
+
- Add comments before element removal: "# Remove all elements from this section"
|
1247 |
+
|
1248 |
+
DO NOT change the mathematical content or educational purpose of the animation.
|
1249 |
+
Only modify layout, positioning, and animation flow to ensure a clear, step-by-step experience.
|
1250 |
+
|
1251 |
+
Return ONLY the improved code without explanations outside of code comments.
|
1252 |
+
"""
|
1253 |
+
},
|
1254 |
+
{"role": "user", "content": f"Review and optimize element positioning and step-by-step flow in this Manim code:\n\n```python\n{code}\n```\n\nThe animation is about: '{prompt}' with complexity level '{complexity}'.\n\nFocus on preventing element overlap by ensuring proper spacing, explicit positioning, AND ADDING FadeOut() calls to remove elements when moving between sections. DO NOT use self.clear() since it doesn't work properly."}
|
1255 |
+
]
|
1256 |
+
)
|
1257 |
+
|
1258 |
+
optimized_code = response.choices[0].message.content
|
1259 |
+
|
1260 |
+
# Clean up the response to extract just the code
|
1261 |
+
if "```python" in optimized_code:
|
1262 |
+
optimized_code = optimized_code.split("```python", 1)[1]
|
1263 |
+
if "```" in optimized_code:
|
1264 |
+
optimized_code = optimized_code.split("```", 1)[0]
|
1265 |
+
|
1266 |
+
return optimized_code.strip()
|
1267 |
+
except Exception as e:
|
1268 |
+
logger.error(f"Error optimizing element positioning: {e}")
|
1269 |
+
return code # Return original code if optimization fails
|
1270 |
|
1271 |
# Function to re-render animation with edited code
|
1272 |
def rerender_animation(edited_code: str, quality: str = "medium_quality") -> tuple:
|
|
|
1289 |
else:
|
1290 |
return code, None, log_output
|
1291 |
|
1292 |
+
# Function to evaluate and fix Manim code
|
1293 |
+
def evaluate_and_fix_manim_code(code: str, prompt: str, complexity: str = "medium") -> tuple:
|
1294 |
+
"""Evaluate Manim code for errors and fix them if found."""
|
1295 |
+
try:
|
1296 |
+
# Create prompt object for context
|
1297 |
+
prompt_obj = AnimationPrompt(description=prompt, complexity=complexity)
|
1298 |
+
|
1299 |
+
# Run evaluation through the agent
|
1300 |
+
evaluation_result = evaluation_agent.run_sync(
|
1301 |
+
f"Evaluate this Manim code for the prompt: {prompt}",
|
1302 |
+
deps=prompt_obj,
|
1303 |
+
code=code
|
1304 |
+
)
|
1305 |
+
|
1306 |
+
# Check if we got a proper evaluation result
|
1307 |
+
if isinstance(evaluation_result, EvaluationResult):
|
1308 |
+
if evaluation_result.has_errors and evaluation_result.fixed_code:
|
1309 |
+
return evaluation_result.fixed_code, format_evaluation_results(evaluation_result)
|
1310 |
+
elif evaluation_result.has_errors:
|
1311 |
+
# If no fixed code was provided but errors exist, try direct fixing
|
1312 |
+
prompt_ctx = RunContext(deps=prompt_obj)
|
1313 |
+
fixed_code = fix_code_issues(
|
1314 |
+
prompt_ctx,
|
1315 |
+
code,
|
1316 |
+
evaluation_result.syntax_errors,
|
1317 |
+
{
|
1318 |
+
"positioning_issues": evaluation_result.positioning_issues,
|
1319 |
+
"overlap_issues": evaluation_result.overlap_issues,
|
1320 |
+
"suggestions": evaluation_result.suggestions
|
1321 |
+
}
|
1322 |
+
)
|
1323 |
+
return fixed_code, format_evaluation_results(evaluation_result)
|
1324 |
+
else:
|
1325 |
+
# No errors found
|
1326 |
+
return code, "## Code Evaluation\n\nNo errors or positioning issues found. Code looks good!"
|
1327 |
+
|
1328 |
+
# Fallback to direct evaluation if agent result isn't an EvaluationResult
|
1329 |
+
return direct_evaluate_and_fix(code, prompt, complexity)
|
1330 |
+
|
1331 |
+
except Exception as e:
|
1332 |
+
logger.error(f"Error during code evaluation: {e}")
|
1333 |
+
# If evaluation fails, return the original code
|
1334 |
+
return code, f"## Error During Evaluation\n\nCould not complete code evaluation: {str(e)}"
|
1335 |
+
|
1336 |
+
def direct_evaluate_and_fix(code: str, prompt: str, complexity: str = "medium") -> tuple:
|
1337 |
+
"""Direct implementation of code evaluation and fixing without using agents."""
|
1338 |
+
try:
|
1339 |
+
response = client.chat.completions.create(
|
1340 |
+
model=llm,
|
1341 |
+
messages=[
|
1342 |
+
{"role": "system", "content": """
|
1343 |
+
Evaluate this Manim code for errors and positioning issues. Look for:
|
1344 |
+
1. Python syntax errors
|
1345 |
+
2. Manim-specific errors (incorrect class usage, invalid animation methods)
|
1346 |
+
3. Positioning issues (elements without explicit positioning)
|
1347 |
+
4. Potential element overlaps
|
1348 |
+
5. Timing and animation flow issues
|
1349 |
+
|
1350 |
+
If you find any issues, fix them and return both an evaluation report and the fixed code.
|
1351 |
+
If no issues are found, say so and return the original code.
|
1352 |
+
|
1353 |
+
Format your response as:
|
1354 |
+
```evaluation
|
1355 |
+
[List all issues found with explanations]
|
1356 |
+
```
|
1357 |
+
|
1358 |
+
```python
|
1359 |
+
[The fixed code or original code if no issues]
|
1360 |
+
```
|
1361 |
+
"""
|
1362 |
+
},
|
1363 |
+
{"role": "user", "content": f"Evaluate this Manim code:\n\n```python\n{code}\n```\n\nThe animation is about: '{prompt}' with complexity level '{complexity}'.\n\nCheck for errors and positioning issues, especially element overlaps."}
|
1364 |
+
]
|
1365 |
+
)
|
1366 |
+
|
1367 |
+
content = response.choices[0].message.content
|
1368 |
+
|
1369 |
+
# Extract evaluation report
|
1370 |
+
evaluation_report = ""
|
1371 |
+
if "```evaluation" in content:
|
1372 |
+
evaluation_parts = content.split("```evaluation", 1)[1].split("```", 1)
|
1373 |
+
if len(evaluation_parts) > 0:
|
1374 |
+
evaluation_report = evaluation_parts[0].strip()
|
1375 |
+
|
1376 |
+
# Extract fixed code
|
1377 |
+
fixed_code = code # Default to original code
|
1378 |
+
if "```python" in content:
|
1379 |
+
code_parts = content.split("```python", 1)[1].split("```", 1)
|
1380 |
+
if len(code_parts) > 0:
|
1381 |
+
potential_fixed_code = code_parts[0].strip()
|
1382 |
+
# Only use the fixed code if it's valid (contains basic Manim imports)
|
1383 |
+
if "from manim import" in potential_fixed_code or "import manim" in potential_fixed_code:
|
1384 |
+
fixed_code = potential_fixed_code
|
1385 |
+
|
1386 |
+
# Format the evaluation report
|
1387 |
+
if evaluation_report:
|
1388 |
+
formatted_report = f"## Code Evaluation\n\n{evaluation_report}\n\n"
|
1389 |
+
if fixed_code != code:
|
1390 |
+
formatted_report += "Issues were found and fixed in the code."
|
1391 |
+
return fixed_code, formatted_report
|
1392 |
+
else:
|
1393 |
+
return fixed_code, "## Code Evaluation\n\nNo significant issues found in the code."
|
1394 |
+
|
1395 |
+
except Exception as e:
|
1396 |
+
logger.error(f"Error during direct evaluation: {e}")
|
1397 |
+
return code, f"## Error During Evaluation\n\nCould not complete code evaluation: {str(e)}"
|
1398 |
+
|
1399 |
+
def format_evaluation_results(result: EvaluationResult) -> str:
|
1400 |
+
"""Format evaluation results for display."""
|
1401 |
+
output = "## Code Evaluation Results\n\n"
|
1402 |
+
|
1403 |
+
if not result.has_errors:
|
1404 |
+
output += "✅ No errors or positioning issues detected. Code looks good!\n\n"
|
1405 |
+
return output
|
1406 |
+
|
1407 |
+
if result.syntax_errors:
|
1408 |
+
output += "### Syntax Errors\n\n"
|
1409 |
+
for i, error in enumerate(result.syntax_errors):
|
1410 |
+
output += f"{i+1}. {error}\n"
|
1411 |
+
output += "\n"
|
1412 |
+
|
1413 |
+
if result.positioning_issues:
|
1414 |
+
output += "### Positioning Issues\n\n"
|
1415 |
+
for i, issue in enumerate(result.positioning_issues):
|
1416 |
+
output += f"{i+1}. {issue}\n"
|
1417 |
+
output += "\n"
|
1418 |
+
|
1419 |
+
if result.overlap_issues:
|
1420 |
+
output += "### Potential Element Overlaps\n\n"
|
1421 |
+
for i, issue in enumerate(result.overlap_issues):
|
1422 |
+
output += f"{i+1}. {issue}\n"
|
1423 |
+
output += "\n"
|
1424 |
+
|
1425 |
+
if result.suggestions:
|
1426 |
+
output += "### Suggestions for Improvement\n\n"
|
1427 |
+
for i, suggestion in enumerate(result.suggestions):
|
1428 |
+
output += f"{i+1}. {suggestion}\n"
|
1429 |
+
output += "\n"
|
1430 |
+
|
1431 |
+
if result.fixed_code:
|
1432 |
+
output += "✅ These issues have been automatically fixed in the updated code.\n"
|
1433 |
+
else:
|
1434 |
+
output += "❌ Could not automatically fix all issues. Please review the code manually.\n"
|
1435 |
+
|
1436 |
+
return output
|
1437 |
+
|
1438 |
# Replace the Gradio interface creation with a Blocks interface for better layout control
|
1439 |
if __name__ == "__main__":
|
1440 |
with gr.Blocks(title="Manimation Generator", theme=gr.themes.Base()) as demo:
|
|
|
1477 |
label="Your Feedback"
|
1478 |
)
|
1479 |
refine_btn = gr.Button("Apply Feedback", variant="secondary")
|
1480 |
+
|
1481 |
+
# Add the missing "Evaluate Code" tab
|
1482 |
+
with gr.TabItem("Evaluate Code"):
|
1483 |
+
gr.Markdown("""
|
1484 |
+
Check your Manim code for:
|
1485 |
+
- Syntax errors
|
1486 |
+
- Positioning issues
|
1487 |
+
- Element overlaps
|
1488 |
+
- Animation flow problems
|
1489 |
+
""")
|
1490 |
+
evaluate_btn = gr.Button("Check Code for Errors", variant="secondary")
|
1491 |
|
1492 |
# Code editor (common to both tabs)
|
1493 |
code_output = gr.Code(
|
|
|
1547 |
)
|
1548 |
return video_path, log, new_history
|
1549 |
|
1550 |
+
def evaluate_and_update_chat(code, history):
|
1551 |
+
# Extract prompt from memory
|
1552 |
+
prompt = memory.history[-1]["prompt"] if memory.history else "Mathematical animation"
|
1553 |
+
complexity = "medium" # Default complexity
|
1554 |
+
|
1555 |
+
# Evaluate the code
|
1556 |
+
fixed_code, evaluation_report = evaluate_and_fix_manim_code(code, prompt, complexity)
|
1557 |
+
|
1558 |
+
new_history = update_chat_history(
|
1559 |
+
history,
|
1560 |
+
"**Request:** Check code for errors and positioning issues",
|
1561 |
+
f"**Evaluation complete**",
|
1562 |
+
None
|
1563 |
+
)
|
1564 |
+
|
1565 |
+
return fixed_code, evaluation_report, new_history
|
1566 |
+
|
1567 |
# Connect the components to the function
|
1568 |
generate_btn.click(
|
1569 |
fn=generate_and_update_chat,
|
|
|
1583 |
outputs=[video_output, log_output, chat_history]
|
1584 |
)
|
1585 |
|
1586 |
+
evaluate_btn.click(
|
1587 |
+
fn=evaluate_and_update_chat,
|
1588 |
+
inputs=[code_output, chat_history],
|
1589 |
+
outputs=[code_output, log_output, chat_history]
|
1590 |
+
)
|
1591 |
+
|
1592 |
# Add footer with social media links
|
1593 |
with gr.Row(equal_height=True):
|
1594 |
gr.Markdown("""
|
|
|
1603 |
|
1604 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|
1605 |
|
1606 |
+
|