ChatGPT实时渲染(MaynorAI)

为ChatGPT添加代码实时预览功能,支持多种编程语言的实时渲染,提供类似Claude的代码预览体验

/* ==UserStyle== */
@name           ChatGPT实时渲染(MaynorAI)
@namespace      github.com/maynor/chatgpt-preview
@version        1.3.0
@description    为ChatGPT添加代码实时预览功能,支持多种编程语言的实时渲染,提供类似Claude的代码预览体验
@author         Maynor
@preprocessor   default
@var           checkbox enablePreview "Enable Preview" 1
@var           select   previewStyle "Preview Style" {
  "default": "Default",
  "minimal": "Minimal",
  "full": "Full Width"
}
@license        GPL-2.0-only
@homepageURL    https://github.com/maynor/chatgpt-preview
@supportURL     https://github.com/maynor/chatgpt-preview/issues
==/UserStyle== */

@-moz-document domain("chat.openai.com"), domain("chatgpt.com"), domain("chatgpt-plus.top"), domain("maynor1024.live") {
    /* Your CSS code here */
}
(function () {
    "use strict";
    // 使用 MutationObserver 监听 DOM 变化
    const observer = new MutationObserver(debounce(xuanranHTML, 500));
    const createIframeObserver = new MutationObserver(
        debounce(createIframe, 500)
    );

    // 观察目标节点的变化
    observer.observe(document.body, { childList: true, subtree: true });
    createIframeObserver.observe(document.body, {
        childList: true,
        subtree: true,
    });

    // 首次调用渲染
    window.addEventListener("load", () => {
        setTimeout(xuanranHTML, 1000);
        // 创建一个iframe
        setTimeout(createIframe, 1000);
    });
})();

function createIframe() {
    // 判断是否已经创建 dynamicContentIframe
    if (document.getElementById("dynamicContentIframe")) {
        console.log("已经创建 dynamicContentIframe");
        return;
    }

    // 创建一个基于main标签的兄弟iframe元素
    const mainElement = document.querySelector("main");
    // mainElement.style.display = "flex";
    mainElement.style.overflow = "hidden";
    if (mainElement) {
        const iframe = document.createElement("iframe");
        iframe.id = "dynamicContentIframe"; // 添加id以便动态修改内容
        iframe.style.display = "relative";
        iframe.style.width = "100%";
        iframe.style.height = "100%";
        iframe.style.backgroundColor = "#FFFDF6"; // 添加奶白色背景
        // sandbox="allow-scripts"
        iframe.sandbox = "allow-scripts";
        iframe.srcdoc = "<html><body></body></html>";
        // 创建切换显示代码块的div
        const toggleCodeButton = document.createElement("div");
        toggleCodeButton.id = "toggleCodeButton";
        toggleCodeButton.style.position = "absolute";
        toggleCodeButton.style.top = "10px";
        toggleCodeButton.style.right = "40px";
        toggleCodeButton.style.cursor = "pointer";
        toggleCodeButton.innerHTML = `
            <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M4 6H16M4 10H16M4 14H16" stroke="black" stroke-width="2" stroke-linecap="round"/>
            </svg>
        `;
        toggleCodeButton.onclick = () => {
            // document.getElementById("dynamicContentIframe").contentWindow.document.body.innerHTML = "123";
            document.getElementById("codeContainer").style.display =
                document.getElementById("codeContainer").style.display ===
                "none"
                    ? "block"
                    : "none";
        };

        // 创建缩小按钮
        const minimizeButton = document.createElement("div");
        minimizeButton.id = "minimizeButton";
        minimizeButton.style.position = "absolute";
        minimizeButton.style.top = "10px";
        minimizeButton.style.right = "10px";
        minimizeButton.style.cursor = "pointer";
        minimizeButton.innerHTML = `
            <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M4 10H16" stroke="black" stroke-width="2" stroke-linecap="round"/>
            </svg>
        `;
        minimizeButton.onclick = () => {
            iframe.style.display =
                iframe.style.display === "none" ? "block" : "none";
            document.querySelector("main").style.display =
                document.querySelector("main").style.display === "block"
                    ? "flex"
                    : "block";
            document
                .querySelector(
                    "body > div.relative.flex.h-full.w-full.overflow-hidden.transition-colors.z-0 > div.flex-shrink-0.overflow-x-hidden.bg-token-sidebar-surface-primary.max-md\\:\\!w-0 > div > div > div > nav > div.flex.justify-between.flex.h-\\[60px\\].items-center.md\\:h-header-height > span > button"
                )
                .click();
        };
        //  toggleCodeButton 和 minimizeButton 添加到一个div下
        const buttonContainer = document.createElement("div");
        buttonContainer.id = "buttonContainer";
        buttonContainer.style.position = "relative";
        buttonContainer.style.top = "10px";
        buttonContainer.style.right = "10px";
        buttonContainer.appendChild(minimizeButton);
        buttonContainer.appendChild(toggleCodeButton);

        // 直接将 iframe 和按钮添加到 main 元素下
        mainElement.appendChild(iframe);
        mainElement.appendChild(buttonContainer);

        // 设置 main 元素为相对定位,以便正确定位最小化按钮
        mainElement.style.position = "relative";

        // mainElement.insertBefore(container, mainElement.nextSibling);
        // 创建一个用户存放显示代码的div
        const codeContainer = document.createElement("div");
        codeContainer.id = "codeContainer";
        codeContainer.style.width = "100%";
        codeContainer.style.height = "100%";
        codeContainer.style.zIndex = "1000";
        // codeContainer.style.backgroundColor = "#FFFDF6"; // 添加奶白色背景
        codeContainer.style.display = "block";
        codeContainer.style.overflow = "hidden";
        codeContainer.style.overflowY = "scroll";
        codeContainer.style.display = "none";
        mainElement.appendChild(codeContainer);
    } else {
        console.error("Main element not found");
    }
    // 显示
    document.getElementById("dynamicContentIframe").style.display = "block";
}
function xuanranHTML() {
    const codes = document.querySelectorAll(".overflow-y-auto.p-4 code");

    codes.forEach((codeElement) => {
        if (codeElement.classList.contains("processed")) {
            return;
        }

        codeElement.classList.add("processed");

        // 获取代码块类型
        const codeType = codeElement.parentNode.parentNode.children[0].innerText.toLowerCase();

        // 检查代码内容是否包含 SVG 标签
        const isSVGContent = codeElement.textContent.trim().startsWith('<svg');

        // 根据不同类型进行渲染
        switch(codeType) {
            case 'html':
                codeElement.parentNode.parentNode.style.display = "none";
                renderSmallWindow(codeElement);
                break;
            case 'svg':
            case 'xml':  // 添加对 xml 类型的支持
                if (isSVGContent) {  // 确认内容是 SVG
                    renderSVG(codeElement);
                } else {
                    console.log("不渲染: 非SVG的XML内容");
                }
                break;
            case 'mermaid':
                renderMermaid(codeElement);
                break;
            case 'pptx':
                renderPPTX(codeElement);
                break;
            default:
                console.log("不渲染: " + codeType);
        }
    });
}
function renderSmallWindow(codeElement) {
    // 创建组件容器
    const componentContainer = document.createElement("div");
    componentContainer.style.display = "flex";
    componentContainer.style.alignItems = "center";
    componentContainer.style.border = "1px solid #e5e7eb";
    componentContainer.style.borderRadius = "8px";
    componentContainer.style.padding = "10px";
    componentContainer.style.backgroundColor = "#f9fafb";
    componentContainer.style.marginBottom = "20px";
    componentContainer.style.cursor = "pointer";

    // 创建图标容器
    const iconContainer = document.createElement("div");
    iconContainer.style.borderRight = "1px solid #e5e7eb";
    iconContainer.style.paddingRight = "10px";

    // 创建 SVG 图标
    const svgIcon = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "svg"
    );
    svgIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg");
    svgIcon.setAttribute("style", "height: 24px; width: 24px; color: #6b7280;");
    svgIcon.setAttribute("fill", "none");
    svgIcon.setAttribute("viewBox", "0 0 24 24");
    svgIcon.setAttribute("stroke", "currentColor");
    svgIcon.setAttribute("stroke-width", "2");

    const pathElement = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "path"
    );
    pathElement.setAttribute("stroke-linecap", "round");
    pathElement.setAttribute("stroke-linejoin", "round");
    pathElement.setAttribute(
        "d",
        "M16 8c0-1.104-.9-2-2-2H6c-1.1 0-2 .896-2 2v8c0 1.104.9 2 2 2h8c1.1 0 2-.896 2-2V8zm-4 4h.01m-3 0h.01M7 12h.01m0 0h.01M12 7v.01M8 7v.01M7 7v.01M12 8v.01m-5 0v.01m-1 0v.01"
    );

    svgIcon.appendChild(pathElement);
    iconContainer.appendChild(svgIcon);

    // 创建文本容器
    const textContainer = document.createElement("div");
    textContainer.style.marginLeft = "10px";

    // 创建标题
    const title = document.createElement("h3");
    title.textContent = codeElement.parentNode.parentNode.children[0].innerText;
    title.style.margin = "0";
    title.style.fontSize = "16px";
    title.style.fontWeight = "600";
    title.style.color = "#374151";

    // 创建描述
    const description = document.createElement("p");
    description.textContent = "预览页面 / 刷新渲染";
    description.style.margin = "0";
    description.style.fontSize = "14px";
    description.style.color = "#6b7280";

    textContainer.appendChild(title);
    textContainer.appendChild(description);

    // 组装组件
    componentContainer.appendChild(iconContainer);
    componentContainer.appendChild(textContainer);

    // 添加点击事件,切换代码显示状态
    componentContainer.addEventListener("click", function () {
        const codeParent = codeElement.parentNode.parentNode;
        const mainElement = document.querySelector("main");
        // codeParent.style.display = codeParent.style.display === 'none' ? 'block' : 'none';
        // 隐藏展开左侧历史记录
        if (mainElement.style.display != "flex") {
            mainElement.style.display = "flex";
        }
        if (
            document.querySelector(
                "body > div.relative.flex.h-full.w-full.overflow-hidden.transition-colors.z-0 > div.flex-shrink-0.overflow-x-hidden.bg-token-sidebar-surface-primary.max-md\\:\\!w-0"
            ).style.width != "0px"
        ) {
            document
                .querySelector(
                    "body > div.relative.flex.h-full.w-full.overflow-hidden.transition-colors.z-0 > div.flex-shrink-0.overflow-x-hidden.bg-token-sidebar-surface-primary.max-md\\:\\!w-0 > div > div > div > nav > div.flex.justify-between.flex.h-\\[60px\\].items-center.md\\:h-header-height > span > button"
                )
                .click();
            // dynamicContentIframe
            document.getElementById("dynamicContentIframe").style.display =
                "block";
        }

        // iframe 赋值
        renderIframeContent(
            document.getElementById("dynamicContentIframe"),
            codeElement.textContent
        );
        // 拦截script标签

        // 设置代码容器代码
        // document.getElementById("codeContainer").innerHTML = codeElement.parentNode.parentNode.innerHTML;
        const codeContainer = document.getElementById("codeContainer");
        codeContainer.innerHTML = "";
        const clonedContent = codeElement.parentNode.parentNode.cloneNode(true);

        codeContainer.appendChild(clonedContent);
        codeContainer.childNodes[0].style.display = "contents";

        // 查找并处理按钮
        const buttons = clonedContent.querySelectorAll("button");
        buttons.forEach((button, index) => {
            console.log(button);
            if (button.className == "flex gap-1 items-center py-1") {
                button.addEventListener("click", function (event) {
                    button.innerText = " success √ ";
                    setTimeout(() => {
                        button.innerText = "Copy code";
                    }, 1000);
                });
            }
            button.addEventListener("click", function (event) {
                event.stopPropagation(); // 阻止事件冒泡到父元素
                // 找到原始按钮并模拟点击
                const originalButtons =
                    codeElement.parentNode.parentNode.querySelectorAll(
                        "button"
                    );
                if (originalButtons[index]) {
                    originalButtons[index].click();
                }
            });
        });

        // 添加事件委托到 codeContainer(用于处理代码元素的点击)
        codeContainer.addEventListener("click", function (event) {
            const clickedElement = event.target.closest("code");
            if (clickedElement) {
                // 模拟原始代码元素的点击行为
                const originalCodeElement = codeElement.closest("code");
                if (originalCodeElement) {
                    originalCodeElement.click();
                }
            }
        });
    });

    // 将组件插入到代码元素之前
    codeElement.parentNode.parentNode.insertAdjacentElement(
        "beforebegin",
        componentContainer
    );
    return componentContainer;
}
function renderIframeContent(iframe, content) {
    // console.log(__remixContext.state.loaderData.root.cspScriptNonce);
    // Add nonce to script tags in content
    const nonce = __remixContext.state.loaderData.root.cspScriptNonce;
    content = content.replace(/<script/g, `<script nonce="${nonce}"`);
    iframe.srcdoc = content;
    return;
    // 获取 iframe 内部的 document 对象
    const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;

    // 将 code ��的内容设置为 iframe 的内容
    iframeDoc.open();

    if (true) {
        // 刷新特效,true为有感刷新,!true为无痕刷新
        // iframeDoc.write(`loading`);
        // 生成一个随机的nonce值
        const nonce = generateNonce();

        // 在content中添加nonce
        // content = content.replace(/<script/g, `<script nonce="${nonce}"`);
        // 设置meta头替换
        // content = content.replace(/<meta/g, `<meta nonce="${nonce}"`);
        // let doms = new DOMParser().parseFromString(content, "text/html");

        // Remove script tags from content
        // content = content.replace(
        //     /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
        //     ""
        // );
        // 插入
        iframeDoc.write(content);
        doms.querySelectorAll("script").forEach((script) => {
            return;
            console.log("script");
            const newScript = iframeDoc.createElement("script");
            newScript.textContent = script.textContent;
            newScript.nonce = nonce;
            // iframeDoc.documentElement.appendChild(newScript);
            // 创建一个blob URL来加载脚本
            const blob = new Blob([script.textContent], {
                type: "application/javascript",
            });
            const scriptUrl = URL.createObjectURL(blob);
            newScript.src = scriptUrl;
            script.onload = () => {
                console.log("加载完成");
                URL.revokeObjectURL(scriptUrl);
            };
            iframeDoc.documentElement.appendChild(newScript);
        });

        // setTimeout(() => iframeDoc.write(content), 100);
    } else {
        iframeDoc.write(content);
    }
    iframeDoc.close();
}

function debounce(func, wait) {
    let timeout;
    return function () {
        const context = this,
            args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(context, args), wait);
    };
}
function generateNonce() {
    return (
        Math.random().toString(36).substring(2, 15) +
        Math.random().toString(36).substring(2, 15)
    );
}

// 添加新的渲染函数
function renderSVG(codeElement) {
    // 创建组件容器
    const componentContainer = document.createElement("div");
    componentContainer.style.display = "flex";
    componentContainer.style.alignItems = "center";
    componentContainer.style.border = "1px solid #e5e7eb";
    componentContainer.style.borderRadius = "8px";
    componentContainer.style.padding = "10px";
    componentContainer.style.backgroundColor = "#f9fafb";
    componentContainer.style.marginBottom = "20px";
    componentContainer.style.cursor = "pointer";

    // 创建图标容器
    const iconContainer = document.createElement("div");
    iconContainer.style.borderRight = "1px solid #e5e7eb";
    iconContainer.style.paddingRight = "10px";

    // 创建 SVG 图标
    const svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svgIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg");
    svgIcon.setAttribute("style", "height: 24px; width: 24px; color: #6b7280;");
    svgIcon.setAttribute("fill", "none");
    svgIcon.setAttribute("viewBox", "0 0 24 24");
    svgIcon.setAttribute("stroke", "currentColor");
    svgIcon.setAttribute("stroke-width", "2");

    const pathElement = document.createElementNS("http://www.w3.org/2000/svg", "path");
    pathElement.setAttribute("stroke-linecap", "round");
    pathElement.setAttribute("stroke-linejoin", "round");
    pathElement.setAttribute("d", "M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2zm0 0V5");
    svgIcon.appendChild(pathElement);
    iconContainer.appendChild(svgIcon);

    // 创建文本容器
    const textContainer = document.createElement("div");
    textContainer.style.marginLeft = "10px";

    // 创建标题
    const title = document.createElement("h3");
    title.textContent = "SVG Preview";
    title.style.margin = "0";
    title.style.fontSize = "16px";
    title.style.fontWeight = "600";
    title.style.color = "#374151";

    // 创建描述
    const description = document.createElement("p");
    description.textContent = "点击切换显示/隐藏";
    description.style.margin = "0";
    description.style.fontSize = "14px";
    description.style.color = "#6b7280";

    textContainer.appendChild(title);
    textContainer.appendChild(description);

    // 组装组件
    componentContainer.appendChild(iconContainer);
    componentContainer.appendChild(textContainer);

    // 创建 SVG 预览容器
    const svgPreviewContainer = document.createElement("div");
    svgPreviewContainer.style.margin = "10px 0";
    svgPreviewContainer.style.padding = "20px";
    svgPreviewContainer.style.backgroundColor = "#fff";
    svgPreviewContainer.style.border = "1px solid #e5e7eb";
    svgPreviewContainer.style.borderRadius = "8px";
    svgPreviewContainer.style.display = "none";
    svgPreviewContainer.innerHTML = codeElement.textContent;

    // 创建代码容器
    const codeContainer = document.createElement("div");
    codeContainer.style.margin = "10px 0";
    codeContainer.style.display = "none";
    const clonedContent = codeElement.parentNode.parentNode.cloneNode(true);
    codeContainer.appendChild(clonedContent);
    codeContainer.childNodes[0].style.display = "block";

    // 添加点击事件
    componentContainer.addEventListener("click", function() {
        svgPreviewContainer.style.display = svgPreviewContainer.style.display === "none" ? "block" : "none";
        codeContainer.style.display = codeContainer.style.display === "none" ? "block" : "none";
    });

    // 将组件插入到代码元素的位置
    const parent = codeElement.parentNode.parentNode;
    parent.style.display = "none";
    parent.insertAdjacentElement("beforebegin", componentContainer);
    componentContainer.insertAdjacentElement("afterend", svgPreviewContainer);
    svgPreviewContainer.insertAdjacentElement("afterend", codeContainer);

    return componentContainer;
}

function renderMermaid(codeElement) {
    // 创建组件容器
    const componentContainer = document.createElement("div");
    componentContainer.style.display = "flex";
    componentContainer.style.alignItems = "center";
    componentContainer.style.border = "1px solid #e5e7eb";
    componentContainer.style.borderRadius = "8px";
    componentContainer.style.padding = "10px";
    componentContainer.style.backgroundColor = "#f9fafb";
    componentContainer.style.marginBottom = "20px";
    componentContainer.style.cursor = "pointer";

    // 创建图标容器
    const iconContainer = document.createElement("div");
    iconContainer.style.borderRight = "1px solid #e5e7eb";
    iconContainer.style.paddingRight = "10px";

    // 创建 SVG 图标
    const svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svgIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg");
    svgIcon.setAttribute("style", "height: 24px; width: 24px; color: #6b7280;");
    svgIcon.setAttribute("fill", "none");
    svgIcon.setAttribute("viewBox", "0 0 24 24");
    svgIcon.setAttribute("stroke", "currentColor");
    svgIcon.setAttribute("stroke-width", "2");

    const pathElement = document.createElementNS("http://www.w3.org/2000/svg", "path");
    pathElement.setAttribute("stroke-linecap", "round");
    pathElement.setAttribute("stroke-linejoin", "round");
    pathElement.setAttribute("d", "M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z");
    svgIcon.appendChild(pathElement);
    iconContainer.appendChild(svgIcon);

    // 创建文本容器
    const textContainer = document.createElement("div");
    textContainer.style.marginLeft = "10px";

    // 创建标题
    const title = document.createElement("h3");
    title.textContent = "Mermaid Diagram";
    title.style.margin = "0";
    title.style.fontSize = "16px";
    title.style.fontWeight = "600";
    title.style.color = "#374151";

    // 创建描述
    const description = document.createElement("p");
    description.textContent = "点击切换显示/隐藏";
    description.style.margin = "0";
    description.style.fontSize = "14px";
    description.style.color = "#6b7280";

    textContainer.appendChild(title);
    textContainer.appendChild(description);

    // 组装组件
    componentContainer.appendChild(iconContainer);
    componentContainer.appendChild(textContainer);

    // 创建 Mermaid 预览容器
    const mermaidPreviewContainer = document.createElement("div");
    mermaidPreviewContainer.style.margin = "10px 0";
    mermaidPreviewContainer.style.padding = "20px";
    mermaidPreviewContainer.style.backgroundColor = "#fff";
    mermaidPreviewContainer.style.border = "1px solid #e5e7eb";
    mermaidPreviewContainer.style.borderRadius = "8px";
    mermaidPreviewContainer.style.display = "none";

    // 创建 Mermaid 图表容器
    const mermaidContainer = document.createElement("div");
    mermaidContainer.className = "mermaid";
    mermaidContainer.textContent = codeElement.textContent;
    mermaidPreviewContainer.appendChild(mermaidContainer);

    // 创建代码容器
    const codeContainer = document.createElement("div");
    codeContainer.style.margin = "10px 0";
    codeContainer.style.display = "none";
    const clonedContent = codeElement.parentNode.parentNode.cloneNode(true);
    codeContainer.appendChild(clonedContent);
    codeContainer.childNodes[0].style.display = "block";

    // 添加点击事件
    componentContainer.addEventListener("click", function() {
        const newDisplayState = mermaidPreviewContainer.style.display === "none" ? "block" : "none";
        mermaidPreviewContainer.style.display = newDisplayState;
        codeContainer.style.display = newDisplayState;

        // 如果是显示状态,确保 Mermaid 图表被渲染
        if (newDisplayState === "block") {
            if (typeof mermaid === 'undefined') {
                // 动态加载 mermaid
                const script = document.createElement('script');
                script.src = 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js';
                script.onload = () => {
                    mermaid.initialize({ startOnLoad: true });
                    mermaid.init(undefined, mermaidContainer);
                };
                document.head.appendChild(script);
            } else {
                mermaid.init(undefined, mermaidContainer);
            }
        }
    });

    // 将组件插入到代码元素的位置
    const parent = codeElement.parentNode.parentNode;
    parent.style.display = "none";
    parent.insertAdjacentElement("beforebegin", componentContainer);
    componentContainer.insertAdjacentElement("afterend", mermaidPreviewContainer);
    mermaidPreviewContainer.insertAdjacentElement("afterend", codeContainer);

    return componentContainer;
}

// 新增 PPTX 渲染函数
function renderPPTX(codeElement) {
    // 创建组件容器
    const componentContainer = document.createElement("div");
    componentContainer.style.display = "flex";
    componentContainer.style.alignItems = "center";
    componentContainer.style.border = "1px solid #e5e7eb";
    componentContainer.style.borderRadius = "8px";
    componentContainer.style.padding = "10px";
    componentContainer.style.backgroundColor = "#f9fafb";
    componentContainer.style.marginBottom = "20px";
    componentContainer.style.cursor = "pointer";

    // 创建图标容器
    const iconContainer = document.createElement("div");
    iconContainer.style.borderRight = "1px solid #e5e7eb";
    iconContainer.style.paddingRight = "10px";

    // 创建 PPT 图标
    const svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svgIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg");
    svgIcon.setAttribute("style", "height: 24px; width: 24px; color: #6b7280;");
    svgIcon.setAttribute("fill", "none");
    svgIcon.setAttribute("viewBox", "0 0 24 24");
    svgIcon.setAttribute("stroke", "currentColor");
    svgIcon.setAttribute("stroke-width", "2");

    const pathElement = document.createElementNS("http://www.w3.org/2000/svg", "path");
    pathElement.setAttribute("stroke-linecap", "round");
    pathElement.setAttribute("stroke-linejoin", "round");
    pathElement.setAttribute("d", "M4 5h16a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1zm8 3v8m-4-4h8");
    svgIcon.appendChild(pathElement);
    iconContainer.appendChild(svgIcon);

    // 创建文本容器
    const textContainer = document.createElement("div");
    textContainer.style.marginLeft = "10px";

    // 创建标题
    const title = document.createElement("h3");
    title.textContent = "PowerPoint Presentation";
    title.style.margin = "0";
    title.style.fontSize = "16px";
    title.style.fontWeight = "600";
    title.style.color = "#374151";

    // 创建描述
    const description = document.createElement("p");
    description.textContent = "点击切换显示/隐藏";
    description.style.margin = "0";
    description.style.fontSize = "14px";
    description.style.color = "#6b7280";

    textContainer.appendChild(title);
    textContainer.appendChild(description);

    // 组装组件
    componentContainer.appendChild(iconContainer);
    componentContainer.appendChild(textContainer);

    // 创建 PPTX 预览容器
    const pptxPreviewContainer = document.createElement("div");
    pptxPreviewContainer.style.margin = "10px 0";
    pptxPreviewContainer.style.padding = "20px";
    pptxPreviewContainer.style.backgroundColor = "#fff";
    pptxPreviewContainer.style.border = "1px solid #e5e7eb";
    pptxPreviewContainer.style.borderRadius = "8px";
    pptxPreviewContainer.style.display = "none";

    // 创��� PPTX 容器
    const pptxContainer = document.createElement("div");
    pptxContainer.id = "pptx-container-" + Math.random().toString(36).substr(2, 9);
    pptxPreviewContainer.appendChild(pptxContainer);

    // 创建控制按钮
    const controlsContainer = document.createElement("div");
    controlsContainer.style.marginTop = "10px";
    controlsContainer.style.textAlign = "center";

    const prevButton = document.createElement("button");
    prevButton.textContent = "上一页";
    prevButton.style.marginRight = "10px";
    prevButton.style.padding = "5px 10px";

    const nextButton = document.createElement("button");
    nextButton.textContent = "下一页";
    nextButton.style.padding = "5px 10px";

    controlsContainer.appendChild(prevButton);
    controlsContainer.appendChild(nextButton);
    pptxPreviewContainer.appendChild(controlsContainer);

    // 创建代码容器
    const codeContainer = document.createElement("div");
    codeContainer.style.margin = "10px 0";
    codeContainer.style.display = "none";
    const clonedContent = codeElement.parentNode.parentNode.cloneNode(true);
    codeContainer.appendChild(clonedContent);
    codeContainer.childNodes[0].style.display = "block";

    // 添加点击事件
    componentContainer.addEventListener("click", function() {
        const newDisplayState = pptxPreviewContainer.style.display === "none" ? "block" : "none";
        pptxPreviewContainer.style.display = newDisplayState;
        codeContainer.style.display = newDisplayState;

        if (newDisplayState === "block") {
            // 检查是否已加载 pptxjs
            if (typeof jQuery === 'undefined') {
                // 加载 jQuery
                const jqueryScript = document.createElement('script');
                jqueryScript.src = 'https://code.jquery.com/jquery-3.6.0.min.js';
                jqueryScript.onload = () => {
                    // 加载 pptxjs
                    const pptxjsScript = document.createElement('script');
                    pptxjsScript.src = 'https://cdn.jsdelivr.net/gh/meshesha/pptxjs@latest/dist/pptxjs.min.js';
                    pptxjsScript.onload = () => {
                        renderPPTXContent(pptxContainer.id, codeElement.textContent);
                    };
                    document.head.appendChild(pptxjsScript);

                    // 加载 pptxjs CSS
                    const pptxjsCSS = document.createElement('link');
                    pptxjsCSS.rel = 'stylesheet';
                    pptxjsCSS.href = 'https://cdn.jsdelivr.net/gh/meshesha/pptxjs@latest/dist/pptxjs.min.css';
                    document.head.appendChild(pptxjsCSS);
                };
                document.head.appendChild(jqueryScript);
            } else {
                renderPPTXContent(pptxContainer.id, codeElement.textContent);
            }
        }
    });

    // 将组件插入到代码元素的位置
    const parent = codeElement.parentNode.parentNode;
    parent.style.display = "none";
    parent.insertAdjacentElement("beforebegin", componentContainer);
    componentContainer.insertAdjacentElement("afterend", pptxPreviewContainer);
    pptxPreviewContainer.insertAdjacentElement("afterend", codeContainer);

    return componentContainer;
}

// 渲染 PPTX 内容的辅助函数
function renderPPTXContent(containerId, base64Content) {
    try {
        // 将 base64 转换为 Blob
        const byteCharacters = atob(base64Content);
        const byteNumbers = new Array(byteCharacters.length);
        for (let i = 0; i < byteCharacters.length; i++) {
            byteNumbers[i] = byteCharacters.charCodeAt(i);
        }
        const byteArray = new Uint8Array(byteNumbers);
        const blob = new Blob([byteArray], { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' });

        // 使用 pptxjs 渲染
        $("#" + containerId).pptxToHtml({
            pptxFileUrl: URL.createObjectURL(blob),
            slidesScale: "50%",
            slideMode: true,
            keyBoardShortCut: true
        });
    } catch (error) {
        console.error("PPTX rendering failed:", error);
        document.getElementById(containerId).innerHTML = "无法渲染 PPTX 内容,请检查格式是否正确。";
    }
}

function savaAiRecording(askFileName = false, format = 'html') {
    // askFileName为true时弹窗询问文件名
    var fileName = document.title;
    var today = new Date();
    var month = (today.getMonth() + 1).toString().padStart(2, '0');
    var day = today.getDate().toString().padStart(2, '0');
    fileName = `${fileName}-${month}${day}.${format}`;
    fileName = askFileName ? prompt('输入要保存的文件名:', fileName) : fileName;
    var body = document.createElement('body');
    body.innerHTML = document.body.innerHTML;
    // 删除所有script标签
    var ps = body.querySelectorAll('script');
    for (var i = 0; i < ps.length; i++) {
        ps[i].parentNode.removeChild(ps[i]);
    }
    // 删除所有style标签,因为downloadHtml会自动再获取一次
    var ps = body.querySelectorAll('style');
    for (var i = 0; i < ps.length; i++) {
        ps[i].parentNode.removeChild(ps[i]);
    }
    // 删除下边框
    var element = body.querySelector('#__next > div > div > main > div.absolute');
    element && element.remove();
    // 删除侧边框
    var element = body.querySelector('#__next > div > div.hidden');
    element && element.remove();
    // 删除侧边框间隔
    var element = body.querySelector('#__next > div > div');
    if (element) { element.className = ''; }
    // 添加script标签,用于修复一键复制
    var script = document.createElement('script');
    script.innerHTML = copyScript;
    body.appendChild(script);

    if (format === 'html') {
        downloadHtml(body.innerHTML, fileName);
    } else if (format === 'docx') {
        // 使用 docx 库生成 docx 文件
        var doc = new docx.Document();
        doc.addSection({
            children: [
                new docx.Paragraph(body.innerText)
            ]
        });
        docx.Packer.toBlob(doc).then(blob => {
            saveAs(blob, fileName);
        });
    } else if (format === 'md') {
        // 使用 showdown 库将 HTML 转换为 Markdown
        var converter = new showdown.Converter();
        var markdown = converter.makeMarkdown(body.innerHTML);
        var blob = new Blob([markdown], { type: 'text/markdown' });
        saveAs(blob, fileName);
    }
}