Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

124 lines
4.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Animation block Manim-rendered math animation.
Calls :class:`deeptutor.agents.math_animator.pipeline.MathAnimatorPipeline`
to generate a video clip explaining the chapter. The payload exposes the
rendered artifact URL(s) plus a short summary.
This block requires the optional ``math-animator`` extras (LaTeX, ffmpeg,
manim, …). When those packages are missing the generator raises
:class:`GenerationFailure` with a clear install hint.
"""
from __future__ import annotations
import importlib.util
import logging
from typing import Any
from ..models import BlockType, SourceAnchor
from .base import BlockContext, BlockGenerator, GenerationFailure
logger = logging.getLogger(__name__)
class AnimationGenerator(BlockGenerator):
block_type = BlockType.ANIMATION
async def _generate(
self, ctx: BlockContext
) -> tuple[dict[str, Any], list[SourceAnchor], dict[str, Any]]:
if importlib.util.find_spec("manim") is None:
raise GenerationFailure(
"AnimationGenerator requires the optional math-animator extras. "
"Install with `pip install -e '.[math-animator]'` "
"or `pip install -r requirements/math-animator.txt`."
)
params = ctx.block.params
chapter_title = params.get("chapter_title", ctx.chapter.title)
chapter_summary = params.get("chapter_summary", ctx.chapter.summary)
objectives = params.get("objectives") or ctx.chapter.learning_objectives
focus = str(params.get("focus") or "")
quality = str(params.get("quality") or "medium")
style_hint = str(params.get("style_hint") or "")
history_lines: list[str] = []
if chapter_summary:
history_lines.append(f"Chapter summary: {chapter_summary}")
if objectives:
history_lines.append("Learning objectives:")
for obj in objectives:
history_lines.append(f"- {obj}")
history_context = "\n".join(history_lines)
focus_clause = f" focusing on {focus}" if focus else ""
user_input = (
f"Create a short Manim animation that walks through the core "
f'derivation of "{chapter_title}"{focus_clause}. Aim for a '
"clear, step-by-step explanation a learner can follow."
)
try:
from deeptutor.agents.math_animator.pipeline import MathAnimatorPipeline
from deeptutor.agents.math_animator.request_config import (
MathAnimatorRequestConfig,
)
from deeptutor.services.llm.config import get_llm_config
llm_config = get_llm_config()
request_config = MathAnimatorRequestConfig(
output_mode="video",
quality=quality if quality in ("low", "medium", "high") else "medium",
style_hint=style_hint,
)
pipeline = MathAnimatorPipeline(
api_key=llm_config.api_key,
base_url=llm_config.base_url,
api_version=llm_config.api_version,
language=ctx.language,
)
turn_id = f"book-{ctx.book_id}-{ctx.block.id}"
result = await pipeline.run(
turn_id=turn_id,
user_input=user_input,
history_context=history_context,
request_config=request_config,
attachments=[],
)
except Exception as exc:
logger.warning(f"AnimationGenerator failed: {exc}", exc_info=True)
raise GenerationFailure(f"animation generation failed: {exc}") from exc
render_result = result["render_result"]
summary_payload = result["summary"]
analysis = result["analysis"]
artifacts = [artifact.model_dump() for artifact in render_result.artifacts]
primary = next(
(
a
for a in artifacts
if a.get("type") == "video" or "video" in (a.get("content_type") or "")
),
artifacts[0] if artifacts else None,
)
return (
{
"render_type": "video",
"artifacts": artifacts,
"video_url": (primary or {}).get("url", ""),
"filename": (primary or {}).get("filename", ""),
"summary": getattr(summary_payload, "summary_text", "") or "",
"key_points": list(getattr(summary_payload, "key_points", []) or []),
"description": getattr(analysis, "learning_goal", "") or "",
},
[],
{
"retry_attempts": render_result.retry_attempts,
"quality": request_config.quality,
},
)
__all__ = ["AnimationGenerator"]