Skip to content

Instantly share code, notes, and snippets.

@programmerq
Last active May 14, 2023 15:56
Show Gist options
  • Save programmerq/4828014ab37255bddf1c340cfa3fd7ec to your computer and use it in GitHub Desktop.
Save programmerq/4828014ab37255bddf1c340cfa3fd7ec to your computer and use it in GitHub Desktop.
renderer for chatgpt code blocks that can have a visual representation.
// ==UserScript==
// @name Render SVGs in Code Blocks
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Render SVGs in code blocks on https://chat.openai.com
// @author Jeff Anderson
// @match https://chat.openai.com/*
// @require https://github.com/mdaines/viz.js/releases/download/v2.1.2/viz.js
// @require https://github.com/mdaines/viz.js/releases/download/v2.1.2/full.render.js
// @grant none
// ==/UserScript==
(function () {
'use strict';
const observerConfig = {childList: true, subtree: true};
// Helper function to decode HTML entities
function decodeHtml(html) {
const txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
}
const renderTasks = {
'language-html': function (codeBlock) {
console.log('Running render task for language-html');
const content = decodeHtml(codeBlock.innerHTML.replace(/<span[^>]*>|<\/span>/g, ''));
if (/<svg[^>]*>/i.test(content)) {
// Call the 'language-svg' function if SVG tag is present
console.log('SVG tag found in language-html block, calling language-svg task');
renderTasks['language-svg'](codeBlock, content);
}
},
'language-svg-old': function (codeBlock, content) {
console.log('Running render task for language-svg');
// Use the provided content if available, or decode the content from the code block
content = content || decodeHtml(codeBlock.innerHTML.replace(/<span[^>]*>|<\/span>/g, ''));
const svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgElement.innerHTML = content;
codeBlock.closest('pre').parentNode.insertBefore(svgElement, codeBlock.closest('pre').nextSibling);
},
'language-svg': function renderSvg(codeBlock) {
console.log('Running render task for language-svg');
const svgContent = decodeHtml(codeBlock.innerHTML.replace(/<span[^>]*>|<\/span>/g, ''));
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.innerHTML = svgContent;
// Determine the natural aspect ratio of the SVG
const width = svg.firstChild.getAttribute('width') || 200;
const height = svg.firstChild.getAttribute('height') || 200;
// Set the viewBox to preserve the aspect ratio
svg.firstChild.setAttribute('viewBox', `0 0 ${width} ${height}`);
// Set the width and height of the SVG to fit its container
svg.firstChild.setAttribute('width', '100%');
svg.firstChild.setAttribute('height', 'auto');
codeBlock.closest('pre').parentNode.insertBefore(svg, codeBlock.closest('pre').nextSibling);
},
'language-graphviz': function (codeBlock) {
console.log('Running render task for language-graphviz');
const viz = new Viz();
const graphvizCode = codeBlock.innerText;
viz.renderSVGElement(graphvizCode)
.then(function (element) {
const div = document.createElement('div');
div.appendChild(element);
codeBlock.parentElement.parentElement.appendChild(div);
})
.catch(error => {
// Create an error element to append to the page
const errorElement = document.createElement('p');
errorElement.textContent = 'Error rendering Graphviz diagram';
errorElement.style.color = 'red';
codeBlock.parentElement.parentElement.appendChild(errorElement);
// Log the error to the console for debugging
console.error('Error rendering Graphviz diagram:', error);
});
},
};
let retryTimeouts = new Map();
function processCodeBlock(codeBlock, className) {
console.log(`Processing code block with class: className`);
if (retryTimeouts.has(codeBlock)) {
clearTimeout(retryTimeouts.get(codeBlock));
}
var renderTask = renderTasks[className];
let previousContent = '';
const retryRender = () => {
const currentContent = codeBlock.innerText;
if (previousContent === currentContent) {
try {
renderTask(codeBlock);
console.log(`Rendered successfully: ${codeBlock.className}`);
retryTimeouts.delete(codeBlock);
} catch (err) {
console.error(`Error processing code block: ${err}`);
}
} else {
previousContent = currentContent;
retryTimeouts.set(codeBlock, setTimeout(retryRender, 1000));
}
};
retryTimeouts.set(codeBlock, setTimeout(retryRender, 1000));
}
let observer;
function startObserver() {
if (observer) {
observer.disconnect();
console.log("Disconnected old observer");
}
const observerTarget = document.querySelector('body');
if (observerTarget) {
console.log("Found observer target, starting observer");
observer = new MutationObserver(handleMutations);
observer.observe(observerTarget, observerConfig);
} else {
console.log("Could not find observer target element");
setTimeout(startObserver, 1000); // Retry after 1 second
}
}
function handleMutations(mutationsList) {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
for (let addedNode of mutation.addedNodes) {
if (addedNode.nodeType === Node.ELEMENT_NODE) {
for (let className in renderTasks) {
const codeBlocks = addedNode.querySelectorAll(`code.${className}`);
codeBlocks.forEach(codeBlock => {
console.log('Processing code block with class:', className);
processCodeBlock(codeBlock, className);
});
}
}
}
}
}
}
// Process any existing code blocks on page load
function processExistingCodeBlocks() {
for (let className in renderTasks) {
console.log('Processing existing code blocks with class:', className);
const xpath = `//div[2]/code[contains(@class, "${className}")]`;
const codeBlocks = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (let i = 0; i < codeBlocks.snapshotLength; i++) {
console.log('processing done');
processCodeBlock(codeBlocks.snapshotItem(i), className);
}
}
}
window.addEventListener('load', (event) => {
console.log('Document loaded, starting script');
processExistingCodeBlocks();
startObserver();
});
window.addEventListener('popstate', (event) => {
console.log('URL changed, restarting observer');
startObserver();
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment