Lazy loaded image
人工智能
ChatGPT实时渲染(MaynorAI)脚本
字数 994阅读时长 3 分钟
2026-1-2
2026-1-4
type
status
date
slug
summary
tags
category
icon
password
// ==UserScript== // @name ChatGPT实时渲染(MaynorAI) // @namespace github.com/maynor/chatgpt-preview // @version 1.5.0 // @description 为ChatGPT添加代码实时预览功能,支持多种编程语言的实时渲染,修复复制按钮功能 // @author Maynor // @match https://chat.openai.com/* // @match https://chatgpt.com/* // @match https://chatgpt-plus.top/* // @match https://*.maynor1024.live/* // @license GPL-2.0-only // @homepageURL https://github.com/maynor/chatgpt-preview // @supportURL https://github.com/maynor/chatgpt-preview/issues // @run-at document-end // @grant none // ==/UserScript==
(function () { "use strict";
// 注入样式 addStyles();
// 使用 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); setTimeout(createIframe, 1000); }); })();
// 添加 CSS 样式
function addStyles() {
const style = document.createElement('style');
style.textContent = `
.maynor-toggle-container {
display: flex;
background: #e5e7eb;
padding: 2px;
border-radius: 6px;
position: absolute;
top: 10px;
right: 50px;
z-index: 1001;
}
.maynor-toggle-btn {
padding: 4px 12px;
border: none;
background: transparent;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
color: #6b7280;
cursor: pointer;
transition: all 0.2s;
}
.maynor-toggle-btn.active {
background: white;
color: #374151;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.maynor-toggle-btn:hover:not(.active) {
color: #374151;
}
.maynor-inline-tabs {
display: flex;
border-bottom: 1px solid #e5e7eb;
margin-bottom: 0; /* 修改: 紧贴内容 */
background: #f9fafb;
border-radius: 8px 8px 0 0;
padding: 0 10px;
}
.maynor-inline-tab {
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
color: #6b7280;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
}
.maynor-inline-tab.active {
color: #10a37f;
border-bottom: 2px solid #10a37f;
font-weight: 600;
}
.maynor-inline-content-wrapper {
border: 1px solid #e5e7eb;
border-top: none;
border-radius: 0 0 8px 8px;
overflow: hidden;
}
`;
document.head.appendChild(style);
}
function createIframe() { if (document.getElementById("dynamicContentIframe")) return;
const mainElement = document.querySelector("main");
if (mainElement) { mainElement.style.overflow = "hidden";
// 1. Iframe const iframe = document.createElement("iframe"); iframe.id = "dynamicContentIframe"; iframe.style.position = "relative"; iframe.style.display = "block"; iframe.style.width = "100%"; iframe.style.height = "100%"; iframe.style.backgroundColor = "#FFFDF6"; iframe.style.border = "none"; iframe.sandbox = "allow-scripts allow-modals allow-forms allow-popups allow-same-origin"; iframe.srcdoc = "<html><body></body></html>";
// 2. Code Container const codeContainer = document.createElement("div"); codeContainer.id = "codeContainer"; codeContainer.style.width = "100%"; codeContainer.style.height = "100%"; codeContainer.style.zIndex = "1000"; codeContainer.style.display = "none"; codeContainer.style.overflow = "hidden"; codeContainer.style.overflowY = "auto"; codeContainer.style.backgroundColor = "#fff"; codeContainer.style.padding = "20px"; codeContainer.style.boxSizing = "border-box";
// 3. Toggle Buttons const toggleContainer = document.createElement("div"); toggleContainer.className = "maynor-toggle-container";
const btnPreview = document.createElement("button"); btnPreview.className = "maynor-toggle-btn active"; btnPreview.innerText = "预览";
const btnCode = document.createElement("button"); btnCode.className = "maynor-toggle-btn"; btnCode.innerText = "代码";
btnPreview.onclick = () => { btnPreview.classList.add("active"); btnCode.classList.remove("active"); iframe.style.display = "block"; codeContainer.style.display = "none"; };
btnCode.onclick = () => { btnCode.classList.add("active"); btnPreview.classList.remove("active"); iframe.style.display = "none"; codeContainer.style.display = "block"; };
toggleContainer.appendChild(btnPreview); toggleContainer.appendChild(btnCode);
// 4. Minimize Button const minimizeButton = document.createElement("div"); minimizeButton.id = "minimizeButton"; minimizeButton.style.position = "absolute"; minimizeButton.style.top = "12px"; minimizeButton.style.right = "15px"; minimizeButton.style.cursor = "pointer"; minimizeButton.style.zIndex = "1002"; minimizeButton.innerHTML = ` <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M6 18L18 6M6 6l12 12" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> `; minimizeButton.onclick = () => { iframe.style.display = iframe.style.display === "none" ? "block" : "none"; const main = document.querySelector("main"); main.style.display = main.style.display === "block" ? "flex" : "block";
const sidebarBtn = 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" ); if (sidebarBtn) sidebarBtn.click(); };
// 5. Assembly const controlsWrapper = document.createElement("div"); controlsWrapper.id = "controlsWrapper"; controlsWrapper.style.position = "absolute"; controlsWrapper.style.top = "0"; controlsWrapper.style.right = "0"; controlsWrapper.style.width = "100%"; controlsWrapper.style.height = "50px"; controlsWrapper.style.pointerEvents = "none";
toggleContainer.style.pointerEvents = "auto"; minimizeButton.style.pointerEvents = "auto";
controlsWrapper.appendChild(toggleContainer); controlsWrapper.appendChild(minimizeButton);
mainElement.style.position = "relative"; mainElement.appendChild(iframe); mainElement.appendChild(codeContainer); mainElement.appendChild(controlsWrapper); } }
// 核心功能:修复复制按钮 function enableCopyButton(container, codeText) { // 查找复制按钮 (使用用户提供的精确 class) // 同时也尝试查找通常的 button 结构以防 class 变化 const buttons = container.querySelectorAll("button");
buttons.forEach(btn => { // 简单的判断:如果包含 "复制" 字样 或者 有特定的 svg const isCopyBtn = btn.innerText.includes("复制") || btn.innerText.includes("Copy") || btn.querySelector('svg') || btn.classList.contains('gap-1'); // 针对用户提供的类名特征
if (isCopyBtn) { // 移除旧的事件监听器 (cloneNode 已经移除了,这里是逻辑重置) // 强制替换为新的节点以去除所有可能残留的 React 绑定干扰 const newBtn = btn.cloneNode(true); btn.parentNode.replaceChild(newBtn, btn);
newBtn.style.cursor = "pointer"; newBtn.onclick = async (e) => { e.preventDefault(); e.stopPropagation();
try { await navigator.clipboard.writeText(codeText);
// 保存原始 HTML const originalHTML = newBtn.innerHTML;
// 设置成功状态 newBtn.innerHTML = ` <div style="display:flex; align-items:center; gap:4px;"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <polyline points="20 6 9 17 4 12"></polyline> </svg> <span>已复制</span> </div> `;
// 2秒后恢复 setTimeout(() => { newBtn.innerHTML = originalHTML; }, 2000); } catch (err) { console.error("复制失败:", err); alert("复制失败,请手动复制"); } }; } }); }
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 typeDiv = codeElement.parentNode.parentNode.children[0]; if (!typeDiv) return;
const codeType = typeDiv.innerText.toLowerCase(); const isSVGContent = codeElement.textContent.trim().startsWith('<svg');
switch(codeType) { case 'html': codeElement.parentNode.parentNode.style.display = "none"; renderSmallWindow(codeElement); break; case 'svg': case 'xml': if (isSVGContent) { renderInlineWithTabs(codeElement, "SVG Preview", (container) => { container.innerHTML = codeElement.textContent; container.style.display = "flex"; container.style.justifyContent = "center"; container.style.alignItems = "center"; }); } break; case 'mermaid': renderInlineWithTabs(codeElement, "Mermaid Diagram", (container) => { const mermaidDiv = document.createElement("div"); mermaidDiv.className = "mermaid"; mermaidDiv.textContent = codeElement.textContent; container.appendChild(mermaidDiv);
if (typeof mermaid === 'undefined') { 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, mermaidDiv); }; document.head.appendChild(script); } else { mermaid.init(undefined, mermaidDiv); } }); break; case 'pptx': renderInlineWithTabs(codeElement, "PowerPoint", (container) => { renderPPTXContentInDiv(container, codeElement.textContent); }); break; } }); }
上一篇
ChatGPT被降智怎么办?ChatGPT不降智网页版分享
下一篇
ChatGPT全流程实操:3小时完成论文初稿! (附提示词模板)

评论
Loading...