Function
filter
v2.0.0
Artifacts V2
Artifacts V2 inspired by Claude Artifacts. Orignal code is from atgehrhardt.
Function ID
artifacts_v2
Downloads
2.7K+

Function Content
python
"""
Changes: 
- Improved formatting for CSS and JavaScript code.
- Structured functions for better readability.
- Cleaned up indentation and spacing for clarity.
- Enhanced CSS styles for better responsiveness, including mobile, tablet, and desktop buttons.
- Improved script functionality for handling multiple artifacts and toggling between views.

author: open-webui, helloworldwastaken, atgehrhardt
author_url:https://github.com/helloworldxdwastaken
orignal_coder_author_url: https://github.com/atgehrhardt 

funding_url: https://github.com/open-webui
version: 2.0.0
required_open_webui_version: 0.3.10 or above
"""

import os
import re
import uuid
import html
from typing import Optional, List, Dict
from pydantic import BaseModel, Field
from bs4 import BeautifulSoup
from open_webui.apps.webui.models.files import Files, FileForm
from open_webui.config import UPLOAD_DIR


class MiddlewareHTMLGenerator:
    @staticmethod
    def generate_style():
        return """
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            background-color: #1e1e1e;
            color: #ffffff;
        }
        .header {
            height: 40px;
            background-color: #2d2d2d;
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0 10px;
            position: sticky;
            top: 0;
            z-index: 1000;
        }
        .content-wrapper {
            padding: 20px;
        }
        .content-item {
            width: 100%;
            margin-bottom: 20px;
            border: 1px solid #444;
            background-color: #2d2d2d;
        }
        .content-item.code-view {
            padding: 10px;
        }
        .render-view .rendered-content {
            margin: 0;
            padding: 0;
        }
        pre {
            white-space: pre-wrap;
            word-wrap: break-word;
            background-color: #1e1e1e;
            padding: 10px;
            border-radius: 5px;
            margin: 0;
        }
        code {
            font-family: 'Courier New', Courier, monospace;
        }
        .hidden {
            display: none;
        }
        h2 {
            margin: 0;
            padding: 10px;
            background-color: #3d3d3d;
        }
        .iframe-wrapper {
            width: 100%;
            height: 600px;
            overflow: hidden;
            position: relative;
            resize: both;
            background-color: transparent;
        }
        .content-frame {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            border: none;
            background-color: transparent;
        }
        .resize-handle {
            position: absolute;
            bottom: 0;
            right: 0;
            width: 20px;
            height: 20px;
            cursor: se-resize;
        }
        .responsive-controls {
            display: flex;
            justify-content: center;
            margin-bottom: 10px;
            margin-top: 15px;
        }
        .device-button {
            margin: 0 5px;
            padding: 5px 10px;
            background-color: transparent;
            color: #ffffff;
            border: 1px solid #ffffff;
            cursor: pointer;
            border-radius: 4px;
            transition: background-color 0.3s, color 0.3s;
        }
        .device-button:hover {
            background-color: rgba(255, 255, 255, 0.1);
        }
        .device-button.active {
            background-color: rgba(255, 255, 255, 0.2);
            font-weight: bold;
        }
        .switch {
            position: relative;
            display: inline-block;
            width: 60px;
            height: 24px;
        }
        .switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }
        .slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #ccc;
            transition: .4s;
            border-radius: 24px;
        }
        .slider:before {
            position: absolute;
            content: "";
            height: 16px;
            width: 16px;
            left: 4px;
            bottom: 4px;
            background-color: white;
            transition: .4s;
            border-radius: 50%;
        }
        input:checked + .slider {
            background-color: #2196F3;
        }
        input:checked + .slider:before {
            transform: translateX(36px);
        }
        .slider-text {
            position: absolute;
            color: white;
            top: 50%;
            transform: translateY(-50%);
            text-align: center;
            left: 0;
            right: 0;
            font-size: 12px;
        }
        .nav-buttons {
            display: flex;
            align-items: center;
        }
        .nav-button, .select-button, .fullscreen-button {
            background-color: transparent;
            border: none;
            color: #ffffff;
            cursor: pointer;
            font-size: 18px;
            padding: 5px;
            margin: 0 5px;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 30px;
            height: 30px;
            border-radius: 50%;
            transition: background-color 0.3s ease;
        }
        .select-button svg {
            width: 30px;
            height: 30px;
        }
        .nav-button:hover, .select-button:hover, .fullscreen-button:hover {
            background-color: rgba(255, 255, 255, 0.1);
        }
        .nav-button:disabled {
            color: #666666;
            cursor: not-allowed;
        }
        .nav-button:disabled:hover {
            background-color: transparent;
        }
        .modal {
            display: none;
            position: fixed;
            z-index: 1001;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            overflow: auto;
            background-color: rgba(0,0,0,0.4);
        }
        .modal-content {
            background-color: #2d2d2d;
            margin: 5% auto;
            padding: 20px;
            border: 1px solid #888;
            width: 90%;
            max-width: 800px;
            border-radius: 5px;
            max-height: 80vh;
            overflow-y: auto;
        }
        .close {
            color: #aaa;
            float: right;
            font-size: 28px;
            font-weight: bold;
            cursor: pointer;
        }
        .close:hover, .close:focus {
            color: #fff;
            text-decoration: none;
            cursor: pointer;
        }
        .artifact-list {
            list-style-type: none;
            padding: 0;
        }
        .artifact-list li {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px;
            border-bottom: 1px solid #444;
            cursor: pointer;
        }
        .artifact-list li:hover {
            background-color: rgba(255, 255, 255, 0.1);
        }
        .artifact-info {
            flex: 1;
            margin-right: 10px;
        }
        .artifact-preview {
            width: 200px;
            height: 120px;
            overflow: hidden;
            background-color: transparent;
        }
        .artifact-preview iframe {
            width: 400px;
            height: 240px;
            border: none;
            transform: scale(0.5);
            transform-origin: top left;
            pointer-events: none;
        }
        .editor {
            width: 100%;
            height: 300px;
            font-family: monospace;
            font-size: 14px;
            border: 1px solid #444;
            background-color: #1e1e1e;
            color: #ffffff;
            padding: 10px;
            box-sizing: border-box;
            overflow: auto;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        .copy-button {
            position: absolute;
            top: 10px;
            right: 10px;
            background-color: #5E5B5A;
            border: none;
            color: white;
            padding: 5px 10px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 14px;
            margin: 4px 2px;
            cursor: pointer;
            border-radius: 4px;
        }
        .copy-button:hover {
            background-color: #45a049;
        }
        .code-container {
            position: relative;
        }
        .iframe-wrapper:-webkit-full-screen,
        .iframe-wrapper:-moz-full-screen,
        .iframe-wrapper:-ms-fullscreen,
        .iframe-wrapper:fullscreen {
            width: 100%;
            height: 100%;
        }
        """

    @staticmethod
    def generate_script():
        return """
        const totalArtifacts = document.querySelectorAll('.render-view').length;
        let currentArtifact = 1;
        let isCodeView = false;

        const modal = document.getElementById("artifactModal");
        const selectButton = document.getElementById("selectArtifact");
        const closeButton = document.getElementsByClassName("close")[0];
        const artifactList = document.getElementById("artifactList");
        const fullscreenButton = document.getElementById('fullscreenButton');
        const body = document.body;

        window.addEventListener('load', () => {
            for (let i = 0; i < totalArtifacts; i++) {
                ['html', 'css', 'js'].forEach(type => {
                    const storedContent = localStorage.getItem(`artifact_${i}_${type}`);
                    if (storedContent) {
                        const editor = document.getElementById(`${type}-editor-${i}`);
                        if (editor) {
                            editor.value = storedContent;
                        }
                    }
                });
            }
            reloadCurrentArtifact();
        });

        function applyStoredChanges(artifactNumber) {
            ['html', 'css', 'js'].forEach(type => {
                const storedContent = localStorage.getItem(`artifact_${artifactNumber - 1}_${type}`);
                if (storedContent) {
                    updateContent(type, artifactNumber - 1, true);
                }
            });
        }

        document.getElementById('toggleView').addEventListener('change', function() {
            isCodeView = this.checked;
            const sliderText = document.querySelector('.slider-text');
            sliderText.textContent = isCodeView ? 'Code' : 'Render';
            updateArtifactVisibility();
        });

        function updateArtifactVisibility() {
            document.querySelectorAll('.content-item').forEach(item => {
                const isCorrectArtifact = item.dataset.artifact == currentArtifact;
                const isCorrectView = (item.classList.contains('render-view') && !isCodeView) || 
                                      (item.classList.contains('code-view') && isCodeView);
                item.classList.toggle('hidden', !(isCorrectArtifact && isCorrectView));
            });
            document.getElementById('prevArtifact').disabled = currentArtifact === 1;
            document.getElementById('nextArtifact').disabled = currentArtifact === totalArtifacts;
        }

        function navigateToArtifact(artifactNumber) {
            currentArtifact = artifactNumber;
            updateArtifactVisibility();
            reloadCurrentArtifact();
            modal.style.display = "none";
        }

        function reloadCurrentArtifact() {
            const frame = document.querySelector(`.content-item[data-artifact="${currentArtifact}"] .content-frame`);
            if (frame) {
                const currentSrcdoc = frame.getAttribute('data-original-content');
                frame.srcdoc = '';
                setTimeout(() => {
                    frame.srcdoc = currentSrcdoc;
                }, 0);
            }
        }

        document.getElementById('prevArtifact').addEventListener('click', () => {
            if (currentArtifact > 1) {
                currentArtifact--;
                updateArtifactVisibility();
                reloadCurrentArtifact();
            }
        });

        document.getElementById('nextArtifact').addEventListener('click', () => {
            if (currentArtifact < totalArtifacts) {
                currentArtifact++;
                updateArtifactVisibility();
                reloadCurrentArtifact();
            }
        });

        function updateContent(type, index, skipReload = false) {
            const frame = document.querySelector(`.content-item[data-artifact="${index + 1}"] .content-frame`);
            const editor = document.getElementById(`${type}-editor-${index}`);
            const content = editor.value;
            
            let updatedSrcdoc = frame.getAttribute('data-original-content');
            const parser = new DOMParser();
            const doc = parser.parseFromString(updatedSrcdoc, 'text/html');
            
            if (type === 'html') {
                doc.body.innerHTML = content;
            } else if (type === 'css') {
                let styleTag = doc.querySelector('style');
                if (!styleTag) {
                    styleTag = doc.createElement('style');
                    doc.head.appendChild(styleTag);
                }
                styleTag.textContent = content;
            } else if (type === 'js') {
                let scriptTag = doc.querySelector('script:not([src])');
                if (!scriptTag) {
                    scriptTag = doc.createElement('script');
                    doc.body.appendChild(scriptTag);
                }
                scriptTag.textContent = content;
            }
        
            updatedSrcdoc = new XMLSerializer().serializeToString(doc);
            
            frame.setAttribute('data-original-content', updatedSrcdoc);
            
            if (!skipReload) {
                frame.srcdoc = '';
                setTimeout(() => {
                    frame.srcdoc = updatedSrcdoc;
                }, 0);
            }
        
            localStorage.setItem(`artifact_${index}_${type}`, content);
            console.log(`Content updated for artifact ${index + 1}, type ${type}`);
        }

        function copyToClipboard(button, elementId) {
            const codeElement = document.getElementById(elementId);
            const textArea = document.createElement('textarea');
            textArea.value = codeElement.textContent;
            document.body.appendChild(textArea);
            textArea.select();
            document.execCommand('copy');
            document.body.removeChild(textArea);
            
            const originalText = button.textContent;
            button.textContent = 'Copied!';
            setTimeout(() => {
                button.textContent = originalText;
            }, 2000);
        }

        selectButton.onclick = function() {
            const makeTransparent = (doc) => {
                doc.body.style.background = 'transparent';
                const styleEl = doc.createElement('style');
                styleEl.textContent = 'body { background: transparent !important; }';
                doc.head.appendChild(styleEl);
            };

            artifactList.innerHTML = '';
            document.querySelectorAll('.content-frame').forEach((frame, index) => {
                const li = document.createElement('li');
                const previewContent = frame.getAttribute('srcdoc');
                
                li.innerHTML = `
                    
Artifact ${index + 1}
`; li.onclick = function() { navigateToArtifact(index + 1); }; artifactList.appendChild(li); const previewIframe = li.querySelector('.artifact-preview iframe'); previewIframe.onload = function() { makeTransparent(this.contentDocument); this.contentDocument.body.style.transform = 'scale(0.5)'; this.contentDocument.body.style.transformOrigin = 'top left'; this.style.pointerEvents = 'none'; }; previewIframe.srcdoc = previewContent; }); modal.style.display = "block"; } closeButton.onclick = function() { modal.style.display = "none"; } window.onclick = function(event) { if (event.target == modal) { modal.style.display = "none"; } } document.querySelectorAll('.device-button').forEach(button => { button.addEventListener('click', function() { const width = this.getAttribute('data-width'); const wrapper = this.closest('.content-item').querySelector('.iframe-wrapper'); const iframe = wrapper.querySelector('.content-frame'); if (width === '100%') { wrapper.style.width = '100%'; wrapper.style.height = '600px'; iframe.style.width = '100%'; iframe.style.height = '100%'; } else { wrapper.style.width = width; wrapper.style.height = '80vh'; iframe.style.width = width; iframe.style.height = '100%'; } this.closest('.responsive-controls').querySelectorAll('.device-button').forEach(btn => { btn.classList.remove('active'); }); this.classList.add('active'); }); }); document.querySelectorAll('.resize-handle').forEach(handle => { handle.addEventListener('mousedown', initResize, false); }); function initResize(e) { window.addEventListener('mousemove', resize, false); window.addEventListener('mouseup', stopResize, false); } function resize(e) { if (!body.classList.contains('fullscreen')) { const wrapper = e.target.closest('.iframe-wrapper'); wrapper.style.width = (e.clientX - wrapper.offsetLeft) + 'px'; wrapper.style.height = (e.clientY - wrapper.offsetTop) + 'px'; } } function stopResize(e) { window.removeEventListener('mousemove', resize, false); window.removeEventListener('mouseup', stopResize, false); } function toggleFullscreen() { const currentFrame = document.querySelector(`.content-item[data-artifact="${currentArtifact}"] .iframe-wrapper`); if (!document.fullscreenElement) { if (currentFrame.requestFullscreen) { currentFrame.requestFullscreen(); } else if (currentFrame.mozRequestFullScreen) { currentFrame.mozRequestFullScreen(); } else if (currentFrame.webkitRequestFullscreen) { currentFrame.webkitRequestFullscreen(); } else if (currentFrame.msRequestFullscreen) { currentFrame.msRequestFullscreen(); } } else { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } } } fullscreenButton.addEventListener('click', toggleFullscreen); document.addEventListener('fullscreenchange', updateFullscreenButtonIcon); document.addEventListener('webkitfullscreenchange', updateFullscreenButtonIcon); document.addEventListener('mozfullscreenchange', updateFullscreenButtonIcon); document.addEventListener('MSFullscreenChange', updateFullscreenButtonIcon); function updateFullscreenButtonIcon() { if (document.fullscreenElement) { fullscreenButton.innerHTML = ` `; } else { fullscreenButton.innerHTML = ` `; } } updateArtifactVisibility(); """ @staticmethod def generate_content_item(i, page): html_content = page.get("html", "") raw_html = page.get("raw_html", "") css_content = page.get("css", "") js_content = page.get("js", "") escaped_html = html.escape(raw_html) escaped_css = html.escape(css_content) escaped_js = html.escape(js_content) base_html = f""" {html_content} """ escaped_base_html = html.escape(base_html) return f"""

HTML Content {i+1}

{"" if not css_content else f''' '''} {"" if not js_content else f''' '''} """ @classmethod def create_middleware_html(cls, pages): content_items = "".join( cls.generate_content_item(i, page) for i, page in enumerate(pages) ) return f""" Generated Content
{content_items}
""" class Filter: class Valves(BaseModel): priority: int = Field( default=0, description="Priority level for the filter operations." ) def __init__(self): self.valves = self.Valves() self.viz_dir = "visualizations" self.html_dir = "html" self.middleware_file = "middleware.html" self.current_artifact = None def ensure_chat_directory(self, chat_id, content_type): chat_dir = os.path.join(UPLOAD_DIR, self.viz_dir, content_type, chat_id) os.makedirs(chat_dir, exist_ok=True) return chat_dir def extract_content(self, content, pattern): return re.findall(pattern, content, re.IGNORECASE | re.DOTALL) def write_content_to_file(self, content, user_id, chat_id, content_type): chat_dir = self.ensure_chat_directory(chat_id, content_type) filename = f"{content_type}_{uuid.uuid4()}.html" file_path = os.path.join(chat_dir, filename) with open(file_path, "w") as f: f.write(content) relative_path = os.path.join(self.viz_dir, content_type, chat_id, filename) file_form = FileForm( id=str(uuid.uuid4()), filename=relative_path, meta={ "name": filename, "content_type": "text/html", "size": len(content), "path": file_path, }, ) return Files.insert_new_file(user_id, file_form).id def parse_content(self, content): html_pattern = r"```(?:html|xml)\s*([\s\S]*?)\s*```" css_pattern = r"```css\s*([\s\S]*?)\s*```" js_pattern = r"```javascript\s*([\s\S]*?)\s*```" svg_pattern = r"" html_blocks = self.extract_content(content, html_pattern) css_blocks = self.extract_content(content, css_pattern) js_blocks = self.extract_content(content, js_pattern) standalone_svg_blocks = self.extract_content(content, svg_pattern) if not self.current_artifact: self.current_artifact = {"html": "", "css": "", "js": "", "raw_html": ""} if html_blocks: self.current_artifact["html"] = html_blocks[0] self.current_artifact["raw_html"] = html_blocks[0] if css_blocks: self.current_artifact["css"] = css_blocks[0] if js_blocks: self.current_artifact["js"] = js_blocks[0] if standalone_svg_blocks: self.current_artifact["html"] = standalone_svg_blocks[0] self.current_artifact["raw_html"] = standalone_svg_blocks[0] return [self.current_artifact] if any(self.current_artifact.values()) else [] def create_middleware_html(self, pages): return MiddlewareHTMLGenerator.create_middleware_html(pages) def inlet(self, body: dict, user: Optional[dict] = None) -> dict: return body def outlet(self, body: dict, __user__: Optional[dict] = None) -> dict: if "messages" in body and body["messages"] and __user__ and "id" in __user__: last_message = body["messages"][-1]["content"] chat_id = body.get("chat_id") if chat_id: try: pages = self.parse_content(last_message) if pages: middleware_content = self.create_middleware_html(pages) middleware_id = self.write_content_to_file( middleware_content, __user__["id"], chat_id, self.html_dir, ) body["messages"][-1][ "content" ] += f"\n\n{{{{HTML_FILE_ID_{middleware_id}}}}}" except Exception as e: error_msg = ( f"Error processing content: {str(e)}\n{traceback.format_exc()}" ) print(error_msg) body["messages"][-1][ "content" ] += f"\n\nError: Failed to process content. Details: {error_msg}" else: print("chat_id is missing") return body