"use client"; import { useEffect, useMemo, useRef, useState } from "react"; import { Code2, Expand, Image as ImageIcon, Timer, Video } from "lucide-react"; import { useTranslation } from "react-i18next"; import { apiUrl } from "@/lib/api"; import type { MathAnimatorResult } from "@/lib/math-animator-types"; export default function MathAnimatorViewer({ result, }: { result: MathAnimatorResult; }) { const { t } = useTranslation(); const [fullscreenUrl, setFullscreenUrl] = useState(null); const rootRef = useRef(null); const images = useMemo( () => result.artifacts.filter((item) => item.type === "image"), [result.artifacts], ); const videos = useMemo( () => result.artifacts.filter((item) => item.type === "video"), [result.artifacts], ); const resolveAssetUrl = (url: string) => url.startsWith("http://") || url.startsWith("https://") ? url : apiUrl(url); useEffect(() => { const el = rootRef.current; if (!el) return; const handler = (e: WheelEvent) => { if (Math.abs(e.deltaY) < 1) return; // Don't intercept if the event target is inside a scrollable child // (e.g. the code
 block) that can still scroll in this direction.
      let node = e.target as HTMLElement | null;
      while (node && node !== el) {
        if (node.scrollHeight > node.clientHeight + 2) {
          const style = window.getComputedStyle(node);
          if (style.overflowY === "auto" || style.overflowY === "scroll") {
            const atBottom =
              node.scrollTop + node.clientHeight >= node.scrollHeight - 2;
            const atTop = node.scrollTop <= 2;
            if ((e.deltaY > 0 && !atBottom) || (e.deltaY < 0 && !atTop)) {
              return;
            }
          }
        }
        node = node.parentElement;
      }

      let scrollRoot: HTMLElement | null = el.closest(
        "[data-chat-scroll-root='true']",
      ) as HTMLElement | null;
      if (!scrollRoot) {
        let parent: HTMLElement | null = el.parentElement;
        while (parent) {
          const style = window.getComputedStyle(parent);
          if (
            (style.overflowY === "auto" || style.overflowY === "scroll") &&
            parent.scrollHeight > parent.clientHeight + 2
          ) {
            scrollRoot = parent;
            break;
          }
          parent = parent.parentElement;
        }
      }

      if (scrollRoot) {
        e.preventDefault();
        scrollRoot.scrollBy({ top: e.deltaY, behavior: "auto" });
      }
    };

    el.addEventListener("wheel", handler, { passive: false });
    return () => el.removeEventListener("wheel", handler);
  }, []);

  return (
    
{videos.length > 0 ? (
{videos.map((item) => (
))}
) : null} {images.length > 0 ? (
{images.map((item) => ( ))}
) : null} {result.code.content ? (
{t("View Manim Code")}
            {result.code.content}
          
) : null} {result.render.visual_review && result.render.visual_review.passed === false ? (
Visual review warning:{" "} {result.render.visual_review.summary || "The generated result still has presentation issues."}
{result.render.visual_review.issues && result.render.visual_review.issues.length > 0 ? (
{result.render.visual_review.issues.join(" ")}
) : null}
) : null} {result.render.retry_attempts || Object.keys(result.timings).length > 0 ? (
{result.render.quality ? ( quality: {result.render.quality} ) : null} {typeof result.render.retry_attempts === "number" ? ( retries: {result.render.retry_attempts} ) : null} {Object.keys(result.timings).length > 0 ? ( {Object.entries(result.timings) .map(([key, value]) => `${key} ${value}s`) .join(" ยท ")} ) : null}
) : null} {fullscreenUrl ? (
setFullscreenUrl(null)} > {t("Fullscreen
) : null}
); } function Header({ icon: Icon, title }: { icon: typeof Video; title: string }) { return (
{title}
); }