"use client"; import dynamic from "next/dynamic"; import { useEffect, useMemo, useRef, useState, type MouseEvent } from "react"; import { createPortal } from "react-dom"; import { Code2, Copy, Check, ExternalLink, Maximize2, X } from "lucide-react"; import { useTranslation } from "react-i18next"; import { Mermaid } from "@/components/Mermaid"; import { prepareIframeHtml } from "@/lib/iframe-html"; import { isManimResult, type VisualizeResult } from "@/lib/visualize-types"; import "./svg-theme.css"; const MathAnimatorViewer = dynamic( () => import("@/components/math-animator/MathAnimatorViewer"), { ssr: false }, ); function stripCodeFence(source: string): string { const trimmed = source.trim(); const fenced = trimmed.match( /^```(?:json|javascript|js)?\s*([\s\S]*?)\s*```$/i, ); return fenced ? fenced[1].trim() : trimmed; } function parseChartConfig(source: string): unknown { const raw = stripCodeFence(source); try { return JSON.parse(raw); } catch { const jsonish = raw .replace(/([{,]\s*)([A-Za-z_$][\w$]*)\s*:/g, '$1"$2":') .replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, (_match, value: string) => JSON.stringify(value.replace(/\\'/g, "'")), ) .replace(/,\s*([}\]])/g, "$1"); return JSON.parse(jsonish); } } function ChartJsRenderer({ config }: { config: string }) { const { t } = useTranslation(); const canvasRef = useRef(null); const chartRef = useRef(null); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; async function render() { if (!canvasRef.current) return; try { const ChartModule = await import("chart.js/auto"); const Chart = ChartModule.default; if (chartRef.current) { (chartRef.current as InstanceType).destroy(); chartRef.current = null; } const parsedConfig = parseChartConfig(config) as ConstructorParameters< typeof Chart >[1]; if (cancelled) return; chartRef.current = new Chart(canvasRef.current, parsedConfig); setError(null); } catch (err) { if (!cancelled) { setError( err instanceof Error ? err.message : t("Failed to render chart"), ); } } } void render(); return () => { cancelled = true; if (chartRef.current) { (chartRef.current as { destroy: () => void }).destroy(); chartRef.current = null; } }; }, [config, t]); if (error) { return (

{t("Chart rendering error")}

          {error}
        
); } return (
); } function HtmlRenderer({ html }: { html: string }) { const { t } = useTranslation(); const iframeRef = useRef(null); const [height, setHeight] = useState(560); const prepared = useMemo(() => prepareIframeHtml(html || ""), [html]); useEffect(() => { const iframe = iframeRef.current; if (!iframe) return; iframe.srcdoc = prepared; }, [prepared]); // Listen for the iframe bridge: a sendPrompt() call (mirror into the composer // via the shared window event) or a height report (grow to fit, no clipping). useEffect(() => { const onMessage = (e: MessageEvent) => { const iframe = iframeRef.current; if (!iframe || e.source !== iframe.contentWindow) return; const data = e.data as { type?: string; text?: string; height?: number }; if (!data || typeof data !== "object") return; if (data.type === "dt:visualize-prompt" && data.text) { window.dispatchEvent( new CustomEvent("dt:visualize-prompt", { detail: data.text }), ); } else if ( data.type === "dt:visualize-height" && typeof data.height === "number" ) { setHeight(Math.min(2400, Math.max(240, Math.ceil(data.height) + 8))); } }; window.addEventListener("message", onMessage); return () => window.removeEventListener("message", onMessage); }, []); const handleOpenInNewTab = () => { try { const contentUrl = URL.createObjectURL( new Blob([prepared], { type: "text/html" }), ); const wrapper = `Visualization`; const url = URL.createObjectURL( new Blob([wrapper], { type: "text/html" }), ); window.open(url, "_blank", "noopener,noreferrer"); setTimeout(() => { URL.revokeObjectURL(url); URL.revokeObjectURL(contentUrl); }, 60_000); } catch { /* no-op */ } }; return (