您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Claude-like Artefacts inside ChatGPT Code Blocks.
// ==UserScript== // @name ChatGPT Artefacts // @namespace http://tampermonkey.net/ // @version 1.3.1 // @description Claude-like Artefacts inside ChatGPT Code Blocks. // @match https://chatgpt.com/* // @grant GM_addElement // @grant GM_addStyle // @author @MartianInGreen // @license MIT // @run-at document-end // ==/UserScript== // @attribution https://gist.github.com/CurtisAccelerate/64a20b1d5df6240119bb0a3f4b5abf31 // Base of script made by https://github.com/CurtisAccelerate @ https://gist.github.com/CurtisAccelerate/64a20b1d5df6240119bb0a3f4b5abf31 / https://x.com/BBacktesting/status/1804481588941533255 (function() { 'use strict'; // Check if we're in an artefact context if (window.location.href.includes('/artefact') || window.parent !== window) { return; // Exit early if we're in an artefact or iframe } let panel; let isDragging = false; let startX; let startWidth; let chatObserver = null; // ---------------- Library Management ---------------- // const LIBRARY_KEY = 'chatgpt_artefacts_library'; function getLibrary() { const library = localStorage.getItem(LIBRARY_KEY); return library ? JSON.parse(library) : []; } function saveLibrary(library) { localStorage.setItem(LIBRARY_KEY, JSON.stringify(library)); } function addToLibrary(code, title = `Snippet ${new Date().toLocaleString()}`, isHTML = false) { const library = getLibrary(); const id = Date.now(); library.push({ id, title, code, isHTML }); saveLibrary(library); updateLibraryUI(); updateLibraryButtonVisibility(); } function removeFromLibrary(id) { let library = getLibrary(); library = library.filter(item => item.id !== id); saveLibrary(library); updateLibraryUI(); updateLibraryButtonVisibility(); } function exportLibrary() { const library = getLibrary(); const dataStr = JSON.stringify(library, null, 2); const blob = new Blob([dataStr], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'chatgpt_artefacts_library.json'; a.click(); URL.revokeObjectURL(url); } function importLibrary(jsonData) { try { const importedLibrary = JSON.parse(jsonData); if (Array.isArray(importedLibrary)) { saveLibrary(importedLibrary); updateLibraryUI(); updateLibraryButtonVisibility(); alert('Library imported successfully!'); } else { alert('Invalid library format.'); } } catch (e) { alert('Failed to parse JSON.'); } } // ---------------- UI Elements ---------------- // // Create the Library Button (Book Icon) const toggleButton = document.createElement("button"); toggleButton.innerHTML = "📖"; // Book Icon // KEEP SIZES LIKE THIS! THEY MATCH THE SIZES OF THE OTHER BUTTONS IN THE CHATGPT UI toggleButton.style.fontSize = "10px"; toggleButton.style.position = "fixed"; toggleButton.style.bottom = "12px"; toggleButton.style.right = "40px"; toggleButton.style.width = "22px"; toggleButton.style.height = "22px"; toggleButton.style.backgroundColor = "#212121"; toggleButton.style.color = "#fff"; toggleButton.style.border = "2px solid #676767"; toggleButton.style.borderRadius = "50%"; toggleButton.style.cursor = "pointer"; toggleButton.style.boxShadow = "0 2px 6px rgba(0,0,0,0.3)"; toggleButton.style.zIndex = "10000"; toggleButton.style.display = "flex"; // Make it visible toggleButton.style.justifyContent = "center"; toggleButton.style.alignItems = "center"; document.body.appendChild(toggleButton); // Create the Library Pop-up const libraryContainer = document.createElement('div'); libraryContainer.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 50%; height: 60%; background: #2c2c2c; box-shadow: 0 2px 10px rgba(0,0,0,0.5); border-radius: 8px; z-index: 12000; display: none; flex-direction: column; color: #e0e0e0; font-family: Arial, sans-serif; `; // Header const libraryHeader = document.createElement('div'); libraryHeader.style.cssText = ` padding: 15px; background: #1e1e1e; color: #fff; display: flex; justify-content: space-between; align-items: center; border-top-left-radius: 8px; border-top-right-radius: 8px; cursor: move; `; const libraryTitle = document.createElement('span'); libraryTitle.textContent = "Library"; libraryTitle.style.fontSize = "18px"; const closeLibraryButton = document.createElement('button'); closeLibraryButton.textContent = "✖"; closeLibraryButton.style.cssText = ` background: none; border: none; color: #fff; font-size: 20px; cursor: pointer; `; closeLibraryButton.onclick = () => { libraryContainer.style.display = 'none'; toggleButton.style.display = "flex"; }; libraryHeader.appendChild(libraryTitle); libraryHeader.appendChild(closeLibraryButton); libraryContainer.appendChild(libraryHeader); // Library Content const libraryContent = document.createElement('div'); libraryContent.style.cssText = ` padding: 20px; overflow-y: auto; flex-grow: 1; display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: min-content; gap: 15px; `; libraryContainer.appendChild(libraryContent); // Footer with Add, Export, Import const libraryFooter = document.createElement('div'); libraryFooter.style.cssText = ` padding: 15px; background: #1e1e1e; display: flex; justify-content: space-between; align-items: center; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; `; const addButton = document.createElement('button'); addButton.textContent = "➕ Add"; addButton.style.cssText = ` padding: 8px 16px; background: #4CAF50; border: none; color: white; border-radius: 4px; cursor: pointer; font-size: 14px; `; addButton.onclick = () => { const title = prompt("Enter a title for the snippet:", `Snippet ${new Date().toLocaleString()}`); if (title === null) return; // Cancelled const code = prompt("Enter the code for the snippet:"); if (code === null) return; // Cancelled addToLibrary(code, title); }; const exportButton = document.createElement('button'); exportButton.textContent = "⬇️ Export"; exportButton.style.cssText = ` padding: 8px 16px; background: #2196F3; border: none; color: white; border-radius: 4px; cursor: pointer; font-size: 14px; `; exportButton.onclick = exportLibrary; const importButton = document.createElement('button'); importButton.textContent = "⬆️ Import"; importButton.style.cssText = ` padding: 8px 16px; background: #FF9800; border: none; color: white; border-radius: 4px; cursor: pointer; font-size: 14px; `; importButton.onclick = () => { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = 'application/json'; fileInput.onchange = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { importLibrary(event.target.result); }; reader.readAsText(file); }; fileInput.click(); }; libraryFooter.appendChild(addButton); const footerButtons = document.createElement('div'); footerButtons.appendChild(exportButton); footerButtons.appendChild(importButton); libraryFooter.appendChild(footerButtons); libraryContainer.appendChild(libraryFooter); document.body.appendChild(libraryContainer); // Toggle Library Pop-up toggleButton.addEventListener("click", () => { libraryContainer.style.display = libraryContainer.style.display === "none" ? "flex" : "none"; toggleButton.style.display = libraryContainer.style.display === "flex" ? "none" : "flex"; }); // Update Library Button Visibility function updateLibraryButtonVisibility() { const library = getLibrary(); if (library.length > 0) { toggleButton.style.display = "flex"; } else { toggleButton.style.display = "none"; libraryContainer.style.display = "none"; } } // Update Library UI function updateLibraryUI() { libraryContent.innerHTML = ''; const library = getLibrary(); if (library.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.textContent = "Library is empty."; emptyMsg.style.textAlign = "center"; emptyMsg.style.color = "#777"; emptyMsg.style.gridColumn = "1 / -1"; libraryContent.appendChild(emptyMsg); return; } library.forEach(item => { const card = document.createElement('div'); card.style.cssText = ` background: #3c3c3c; border: 1px solid #555; border-radius: 8px; padding: 12px; display: flex; flex-direction: column; justify-content: space-between; color: #fff; box-shadow: 0 2px 5px rgba(0,0,0,0.2); transition: transform 0.2s, box-shadow 0.2s; cursor: default; height: 140px; /* Reduced height */ `; card.addEventListener('mouseenter', () => { card.style.transform = "scale(1.02)"; card.style.boxShadow = "0 4px 10px rgba(0,0,0,0.3)"; }); card.addEventListener('mouseleave', () => { card.style.transform = "scale(1)"; card.style.boxShadow = "0 2px 5px rgba(0,0,0,0.2)"; }); const title = document.createElement('h3'); title.textContent = item.title; title.style.cssText = ` margin: 0 0 10px 0; font-size: 16px; word-break: break-word; height: 40px; overflow: hidden; `; card.appendChild(title); const actions = document.createElement('div'); actions.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-top: auto; `; // Left Actions: Open Sidebar and Open in New Tab const leftActions = document.createElement('div'); leftActions.style.cssText = ` display: flex; gap: 5px; `; // Open in Sidebar Button const openSidebarButton = document.createElement('button'); openSidebarButton.textContent = "🔍"; openSidebarButton.title = "Open in Sidebar"; openSidebarButton.style.cssText = ` padding: 6px; background: #4CAF50; border: none; color: white; border-radius: 4px; cursor: pointer; font-size: 14px; width: 32px; height: 32px; `; openSidebarButton.onclick = (e) => { e.stopPropagation(); let htmlDoc; if (item.isHTML) { htmlDoc = item.code; createSlideOutPanel(htmlDoc, item.isHTML); } else { const parser = new DOMParser(); htmlDoc = parser.parseFromString(item.code, 'text/html'); } // Close the library pop-up libraryContainer.style.display = "none"; toggleButton.style.display = "flex"; createSlideOutPanel(htmlDoc, item.isHTML); }; // Open in New Tab Button const openTabButton = document.createElement('button'); openTabButton.textContent = "🌐"; openTabButton.title = "Open in New Tab"; openTabButton.style.cssText = ` padding: 6px; background: #2196F3; border: none; color: white; border-radius: 4px; cursor: pointer; font-size: 14px; width: 32px; height: 32px; `; openTabButton.onclick = (e) => { e.stopPropagation(); let htmlDoc; if (item.isHTML) { htmlDoc = item.code; createSlideOutPanel(htmlDoc, item.isHTML); } else { const parser = new DOMParser(); htmlDoc = parser.parseFromString(item.code, 'text/html'); } openCodeInNewTab(htmlDoc, item.isHTML); }; leftActions.appendChild(openSidebarButton); leftActions.appendChild(openTabButton); // Right Actions: Copy Code and Delete const rightActions = document.createElement('div'); rightActions.style.cssText = ` display: flex; gap: 5px; `; // Copy Code Button const copyButton = document.createElement('button'); copyButton.textContent = "📋"; copyButton.title = "Copy Code"; copyButton.style.cssText = ` padding: 6px; background: #9C27B0; border: none; color: white; border-radius: 4px; cursor: pointer; font-size: 14px; width: 32px; height: 32px; `; copyButton.onclick = (e) => { e.stopPropagation(); navigator.clipboard.writeText(item.code).then(() => { // Temporarily change button text to indicate success const originalText = copyButton.textContent; copyButton.textContent = "✅"; setTimeout(() => { copyButton.textContent = originalText; }, 2000); }).catch(err => { console.error('Failed to copy text: ', err); alert('Failed to copy code. Please try again.'); }); }; // Delete Button const deleteButton = document.createElement('button'); deleteButton.textContent = "🗑️"; deleteButton.title = "Delete"; deleteButton.style.cssText = ` background: none; border: none; cursor: pointer; font-size: 16px; color: #e74c3c; width: 32px; height: 32px; `; deleteButton.onclick = (e) => { e.stopPropagation(); if (confirm(`Delete "${item.title}" from library?`)) { removeFromLibrary(item.id); } }; rightActions.appendChild(copyButton); rightActions.appendChild(deleteButton); actions.appendChild(leftActions); actions.appendChild(rightActions); card.appendChild(actions); libraryContent.appendChild(card); }); } // Helper Function to Escape HTML function escapeHtml(text) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', }; return text.replace(/[&<>"']/g, function(m) { return map[m]; }); } // Function to Open Code in New Tab function openCodeInNewTab(codeBlock, isHTML) { const currentUrl = window.location.href; const newUrl = currentUrl + '/artefact'; let codeBlockContent = codeBlock; let codeElement; let languageMatch; let language; if (!isHTML) { codeBlockContent = codeBlock.querySelector('code').innerText; codeElement = codeBlock.querySelector('code'); languageMatch = codeElement ? codeElement.className.match(/language-(\w+)/) : null; language = languageMatch ? languageMatch[1] : 'unknown'; } if (language === 'html') { isHTML = true; } const newWindow = window.open(newUrl, '_blank'); if (newWindow) { newWindow.document.open(); if (isHTML) { newWindow.document.write(codeBlockContent); } else { // Get the content of the code block by removing the outer <div><code></code></div> structure console.log("Code block Language: " + language); if (language === 'mermaid') { newWindow.document.write(` <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script> <div class="mermaid"> ${codeBlockContent} </div> <script> mermaid.initialize({ startOnLoad: true }); </script> `); } if (language === 'md' || language === 'markdown') { const doc = iframe.contentDocument || iframe.contentWindow.document; doc.open(); // Use markdown library to properly render and parse markdown doc.write(` <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <div id="content"></div> <script> document.getElementById('content').innerHTML = marked.parse(\`${codeBlockContent.replace(/`/g, '\\`')}\`); </script> `); doc.close(); } else { // If not any of the additionally supported file types, assume it's plain text code newWindow.document.write(`<pre>${escapeHtml(codeBlockContent)}</pre>`); } } // Update the URL display without navigating newWindow.history.pushState(null, '', newUrl); newWindow.document.close(); } else { alert('Failed to open new tab. Please allow pop-ups for this site.'); } } // Function to Create Slide-Out Panel function createSlideOutPanel(codeBlock, isHTML) { if (panel) { panel.remove(); } panel = document.createElement('div'); panel.style.cssText = ` position: fixed; top: 0; right: 0; width: 600px; height: 100%; background: #f7f7f8; box-shadow: -2px 0 5px rgba(0,0,0,0.3); z-index: 1000; display: flex; flex-direction: column; transform: translateX(100%); transition: transform 0.3s ease-in-out; `; const header = document.createElement('div'); header.style.cssText = ` padding: 10px; background: #282c34; color: #fff; display: flex; justify-content: space-between; align-items: center; cursor: move; `; const closeButton = document.createElement('button'); closeButton.textContent = 'Close'; closeButton.style.cssText = ` background: #ff5f57; border: none; border-radius: 5px; padding: 5px 10px; cursor: pointer; color: white; `; closeButton.onclick = () => panel.style.transform = 'translateX(100%)'; header.appendChild(closeButton); panel.appendChild(header); const contentContainer = document.createElement('div'); contentContainer.style.cssText = ` padding: 10px; overflow-y: auto; flex-grow: 1; `; const iframe = document.createElement('iframe'); iframe.style.cssText = ` width: 100%; height: 100%; border: none; margin: 0; padding: 0; `; contentContainer.appendChild(iframe); panel.appendChild(contentContainer); document.body.appendChild(panel); console.log(codeBlock); let codeBlockContent = codeBlock; let codeElement; let languageMatch; let language; if (!isHTML) { codeBlockContent = codeBlock.querySelector('code').innerText; codeElement = codeBlock.querySelector('code'); languageMatch = codeElement ? codeElement.className.match(/language-(\w+)/) : null; language = languageMatch ? languageMatch[1] : 'unknown'; } if (language === 'html') { isHTML = true; } if (isHTML === false) { // Get the content of the code block by removing the outer <div><code></code></div> structure console.log("Code block Language: " + language); if (language === 'mermaid') { const doc = iframe.contentDocument || iframe.contentWindow.document; doc.open(); doc.write(` <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script> <div class="mermaid"> ${codeBlockContent} </div> <script> mermaid.initialize({ startOnLoad: true }); </script> `); doc.close(); } if (language === 'md' || language === 'markdown') { const doc = iframe.contentDocument || iframe.contentWindow.document; doc.open(); // Use markdown library to properly render and parse markdown doc.write(` <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <div id="content"></div> <script> document.getElementById('content').innerHTML = marked.parse(\`${codeBlockContent.replace(/`/g, '\\`')}\`); </script> `); doc.close(); } else { // If not any of the additionally supported file types, assume it's plain text code const doc = iframe.contentDocument || iframe.contentWindow.document; doc.open(); doc.write(`<pre>${escapeHtml(codeBlockContent)}</pre>`); doc.close(); } } else if (isHTML === true) { const doc = iframe.contentDocument || iframe.contentWindow.document; doc.open(); doc.write(codeBlockContent); doc.close(); } setTimeout(() => panel.style.transform = 'translateX(0)', 0); header.addEventListener('mousedown', startDragging); document.addEventListener('mousemove', dragPanel); document.addEventListener('mouseup', stopDragging); // Close the panel if clicking outside document.addEventListener('click', function(event) { if (!panel.contains(event.target)) { panel.style.transform = 'translateX(100%)'; } }, { once: true }); } function addButtonsNextToCopy(codeBlock) { const outerContainer = codeBlock.closest('.relative'); if (!outerContainer) return; const copyButtonContainer = outerContainer.querySelector('.absolute.bottom-0.right-2'); if (!copyButtonContainer) return; // Check if the buttons are already added to prevent duplicates if (outerContainer.querySelector('.run-demo-button') || outerContainer.querySelector('.open-tab-button') || outerContainer.querySelector('.save-button')) { return; } // Create a new span for our custom buttons const customButtonsSpan = document.createElement('span'); customButtonsSpan.className = 'custom-buttons-span'; customButtonsSpan.style.cssText = ` display: flex; flex-direction: row; align-items: center; margin-right: 5px; `; const buttonStyle = ` padding: 0 8px; /* Adjust padding to control the button's width */ height: 24px; /* Set the fixed height to 24px */ background: #2f2f2f; border: none; color: #b4b4b4; cursor: pointer; font-size: 13px; font-weight: 500; border-radius: 4px; display: flex; align-items: center; justify-content: center; /* Center the text */ transition: background 0.3s, color 0.3s; margin-left: 4px; `; // Create "Run Demo" button const runDemoButton = document.createElement('button'); runDemoButton.innerHTML = ` <div style="display: flex; align-items: center;"> <div style="margin-right: 4px; display: flex; align-items: center;"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polygon points="5 3 19 12 5 21 5 3"></polygon> </svg> </div> <div>Open Demo</div> </div> `; runDemoButton.className = 'run-demo-button custom-tooltip'; runDemoButton.style.cssText = buttonStyle; runDemoButton.onclick = (e) => { e.stopPropagation(); createSlideOutPanel(codeBlock, false); }; // Create "Open in New Tab" button const openTabButton = document.createElement('button'); openTabButton.innerHTML = ` <div style="display: flex; align-items: center;"> <div style="margin-right: 4px; display: flex; align-items: center;"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path> <polyline points="15 3 21 3 21 9"></polyline> <line x1="10" y1="14" x2="21" y2="3"></line> </svg> </div> <div>Tab</div> </div> `; openTabButton.className = 'open-tab-button custom-tooltip'; openTabButton.style.cssText = buttonStyle; openTabButton.onclick = (e) => { e.stopPropagation(); openCodeInNewTab(codeBlock); }; // Create "Save" button const saveButton = document.createElement('button'); saveButton.innerHTML = ` <div style="display: flex; align-items: center;"> <div style="margin-right: 4px; display: flex; align-items: center;"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path> <polyline points="17 21 17 13 7 13 7 21"></polyline> <polyline points="7 3 7 8 15 8"></polyline> </svg> </div> <div>Save</div> </div> `; saveButton.className = 'save-button custom-tooltip'; saveButton.style.cssText = buttonStyle; saveButton.onclick = (e) => { e.stopPropagation(); const codeElement = codeBlock.querySelector('code'); const isHTML = !codeElement; const codeText = isHTML ? codeBlock.innerHTML : codeBlock.outerHTML; const title = prompt("Enter a title for the saved snippet:", `Snippet ${new Date().toLocaleString()}`); if (title === null || title.trim() === "") { alert("Save cancelled or invalid title."); return; } addToLibrary(codeText, title.trim(), isHTML); alert(`"${title.trim()}" has been saved to your library.`); }; // Add hover effects [runDemoButton, openTabButton, saveButton].forEach(button => { button.addEventListener('mouseover', () => { button.style.background = '#3f3f3f'; button.style.color = '#ffffff'; }); button.addEventListener('mouseout', () => { button.style.background = '#2f2f2f'; button.style.color = '#b4b4b4'; }); }); // Add hover text for the buttons runDemoButton.setAttribute('data-hover-text', 'Open Demo in Slideout Panel'); openTabButton.setAttribute('data-hover-text', 'Open in New Tab'); saveButton.setAttribute('data-hover-text', 'Save to Library'); // Add the new buttons to the custom span customButtonsSpan.appendChild(runDemoButton); customButtonsSpan.appendChild(openTabButton); customButtonsSpan.appendChild(saveButton); // Insert the custom span before the existing span containing the "Copy code" button copyButtonContainer.insertBefore(customButtonsSpan, copyButtonContainer.firstChild); } function processCodeBlocks() { const codeBlocks = document.querySelectorAll('.overflow-y-auto'); codeBlocks.forEach(codeBlock => { if (codeBlock.closest('.markdown')) { addButtonsNextToCopy(codeBlock); } }); } function observeChat() { chatObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { const codeBlocks = node.querySelectorAll('.overflow-y-auto'); codeBlocks.forEach(codeBlock => { if (codeBlock.closest('.markdown')) { addButtonsNextToCopy(codeBlock); } }); } }); } }); }); const chatContainer = document.querySelector('main'); if (chatContainer) { chatObserver.observe(chatContainer, { childList: true, subtree: true }); } } function checkAndReinitialize() { const chatContainer = document.querySelector('main'); if (!chatContainer || !chatObserver) { console.log("ChatGPT Artefacts: Observer not running or chat container missing, reinitializing..."); reinitializeProcessor(); } else { // Check if the observer is still connected to the DOM const observerActive = Array.from(chatObserver.takeRecords()).length > 0 || chatObserver.observe(chatContainer, { childList: true, subtree: true }); if (!observerActive) { reinitializeProcessor(); } } } function setupPeriodicCheck() { setInterval(checkAndReinitialize, 10000); // Check every 10 seconds } function addIndicator() { const indicator = document.createElement('div'); indicator.textContent = 'Artefacts Active'; indicator.style.cssText = ` position: fixed; top: 10px; right: 10px; z-index: 1000; padding: 5px 10px; background-color: #4CAF50; color: white; border-radius: 5px; font-size: 12px; `; document.body.appendChild(indicator); setTimeout(() => indicator.style.display = 'none', 3000); } function initializeProcessor() { addIndicator(); processCodeBlocks(); observeChat(); setupPeriodicCheck(); updateLibraryButtonVisibility(); updateLibraryUI(); } function reinitializeProcessor() { try { chatObserver.disconnect(); chatObserver = null; } catch (e) { } processCodeBlocks(); observeChat(); } // Use MutationObserver to wait for the chat interface to load const bodyObserver = new MutationObserver((mutations) => { if (document.querySelector('main')) { bodyObserver.disconnect(); setTimeout(initializeProcessor, 500); // Delay execution by 0.5 seconds } }); bodyObserver.observe(document.body, { childList: true, subtree: true }); // Dragging functionality for the slide-out panel function startDragging(e) { isDragging = true; startX = e.clientX; startWidth = parseInt(document.defaultView.getComputedStyle(panel).width, 10); document.documentElement.addEventListener('mousemove', dragPanel, false); document.documentElement.addEventListener('mouseup', stopDragging, false); } function dragPanel(e) { if (!isDragging) return; let newWidth = startWidth - (e.clientX - startX); if (newWidth < 300) newWidth = 300; if (newWidth > 900) newWidth = 900; panel.style.width = newWidth + 'px'; } function stopDragging(e) { isDragging = false; document.documentElement.removeEventListener('mousemove', dragPanel, false); document.documentElement.removeEventListener('mouseup', stopDragging, false); } })(); GM_addStyle(` .custom-tooltip { position: relative; z-index: 10; } .custom-tooltip::after { content: attr(data-hover-text); position: absolute; bottom: 120%; left: 50%; transform: translateX(-50%); background-color: #333; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; white-space: nowrap; opacity: 0; transition: opacity 0.3s; pointer-events: none; z-index: 11; }c .custom-tooltip:hover::after { opacity: 1; } .custom-tooltip:hover + [role="tooltip"] { display: none !important; } `);