File size: 3,301 Bytes
54ca85f
a9334c7
54ca85f
 
 
 
 
 
 
 
 
a9334c7
54ca85f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a9334c7
aa1c07c
9c4fab2
54ca85f
aa1c07c
 
9c4fab2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54ca85f
9c4fab2
 
 
 
aa1c07c
 
 
 
54ca85f
aa1c07c
54ca85f
aa1c07c
 
 
54ca85f
 
 
 
aa1c07c
 
 
54ca85f
 
 
a9334c7
54ca85f
aa1c07c
fa3da87
a9334c7
 
 
54ca85f
a9334c7
54ca85f
 
 
 
 
 
 
 
 
 
 
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
"""
utils.py — PDF extraction, Gemini LLM, and Manim code helpers.
"""

from __future__ import annotations

import re

from google import genai
from google.genai import types


# ── PDF Text Extraction ───────────────────────────────────────────────────────

def extract_pdf_text(pdf_path: str) -> str:
    """Extract plain text from a PDF using pypdf."""
    from pypdf import PdfReader

    reader = PdfReader(pdf_path)
    pages = []
    for page in reader.pages:
        text = page.extract_text()
        if text:
            pages.append(text)
    return "\n\n".join(pages)


# ── Gemini LLM ────────────────────────────────────────────────────────────────

def generate_manim_code(prompt_text: str, api_key: str) -> str:
    """Stream Manim code from Gemini 3 Flash Preview and return it as a string."""

    client = genai.Client(api_key=api_key)
    model = "gemini-3-flash-preview"

    # Robust system prompt
    system_prompt = """
You are an expert Python developer and Manim animation engineer.
Always produce code that:
- Is fully correct and runnable with Manim v1.0+.
- Uses clear, readable structure, proper imports, and PEP8-compliant formatting.
- Produces visually correct animations based on the user's description.
- Minimizes unnecessary complexity but keeps clarity.
- Names the main scene class `OutputVideo`.
- Returns code only, without extra explanation or markdown fences.
- Handles edge cases gracefully.
- Uses comments sparingly, only when clarifying complex parts.
- When in doubt, use Google search effectively to verify facts, functions, or best practices.
Always ensure your output is precise, accurate, and complete.
"""

    contents = [
        types.Content(
            role="system",
            parts=[types.Part.from_text(text=system_prompt)]
        ),
        types.Content(
            role="user",
            parts=[types.Part.from_text(text=prompt_text)]
        )
    ]

    config = types.GenerateContentConfig(
        thinking_config=types.ThinkingConfig(
            thinking_level="HIGH"
        )
    )

    code = ""
    for chunk in client.models.generate_content_stream(
        model=model,
        contents=contents,
        config=config
    ):
        if chunk.text:
            code += chunk.text
            print(chunk.text, end="", flush=True)

    return code

# ── Manim Code Sanitisation ───────────────────────────────────────────────────

def sanitize_manim_code(raw: str) -> str:
    """
    Strip markdown fences, ensure correct imports and class name.
    """
    code = re.sub(r"^```(?:python)?\s*", "", raw.strip(), flags=re.MULTILINE)
    code = re.sub(r"\s*```$", "", code.strip(), flags=re.MULTILINE)

    if "from manim import" not in code and "import manim" not in code:
        code = "from manim import *\n\n" + code

    # Ensure class is named OutputVideo
    code = re.sub(r"class\s+\w+\s*\(\s*Scene\s*\)", "class OutputVideo(Scene)", code)

    return code