File size: 31,634 Bytes
001fdf1
1
[{"id":"artifacts","user_id":"942d0349-0aca-4bf7-aad9-94489c71ce32","name":"Artifacts","type":"filter","content":"\"\"\"\ntitle: OpenWebUI Artifacts\nauthor: open-webui, atgehrhardt\nauthor_url: https://github.com/atgehrhardt\nfunding_url: https://github.com/open-webui\nversion: 1.2.7\nrequired_open_webui_version: 0.3.10\n\"\"\"\n\nimport os\nimport re\nimport uuid\nimport html\nimport traceback\nimport json\nfrom typing import Optional, List, Dict\nfrom pydantic import BaseModel, Field\nfrom bs4 import BeautifulSoup\nfrom apps.webui.models.files import Files, FileForm\nfrom config import UPLOAD_DIR\n\n\nclass MiddlewareHTMLGenerator:\n    @staticmethod\n    def generate_style():\n        return \"\"\"\n        body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #1e1e1e; color: #ffffff; }\n        .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; }\n        .content-wrapper { padding: 20px; }\n        .content-item { width: 100%; margin-bottom: 20px; border: 1px solid #444; background-color: #2d2d2d; }\n        .content-item.code-view { padding: 10px; }\n        .render-view .rendered-content { margin: 0; padding: 0; }\n        pre { white-space: pre-wrap; word-wrap: break-word; background-color: #1e1e1e; padding: 10px; border-radius: 5px; margin: 0; }\n        code { font-family: 'Courier New', Courier, monospace; }\n        .hidden { display: none; }\n        h2 { margin: 0; padding: 10px; background-color: #3d3d3d; }\n        .iframe-wrapper { width: 100%; height: 600px; overflow: hidden; position: relative; resize: both; background-color: transparent; }\n        .content-frame { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; background-color: transparent; }\n        .resize-handle { position: absolute; bottom: 0; right: 0; width: 20px; height: 20px; cursor: se-resize; }\n        .responsive-controls { display: flex; justify-content: center; margin-bottom: 10px; margin-top: 15px; }\n        .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; }\n        .device-button:hover { background-color: rgba(255, 255, 255, 0.1); }\n        .device-button.active { background-color: rgba(255, 255, 255, 0.2); font-weight: bold; }\n        .switch { position: relative; display: inline-block; width: 60px; height: 24px; }\n        .switch input { opacity: 0; width: 0; height: 0; }\n        .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 24px; }\n        .slider:before { position: absolute; content: \"\"; height: 16px; width: 16px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; }\n        input:checked + .slider { background-color: #2196F3; }\n        input:checked + .slider:before { transform: translateX(36px); }\n        .slider-text { position: absolute; color: white; top: 50%; transform: translateY(-50%); text-align: center; left: 0; right: 0; font-size: 12px; }\n        .nav-buttons { display: flex; align-items: center; }\n        .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; }\n        .select-button { border: none; padding: 0; }\n        .select-button svg { width: 30px; height: 30px; }\n        .nav-button:hover, .select-button:hover, .fullscreen-button:hover { background-color: rgba(255, 255, 255, 0.1); }\n        .nav-button:disabled { color: #666666; cursor: not-allowed; }\n        .nav-button:disabled:hover { background-color: transparent; }\n        .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); }\n        .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; }\n        .close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }\n        .close:hover, .close:focus { color: #fff; text-decoration: none; cursor: pointer; }\n        .artifact-list { list-style-type: none; padding: 0; }\n        .artifact-list li { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #444; cursor: pointer; }\n        .artifact-list li:hover { background-color: rgba(255, 255, 255, 0.1); }\n        .artifact-info { flex: 1; margin-right: 10px; }\n        .artifact-preview { width: 200px; height: 120px; overflow: hidden; background-color: transparent; }\n        .artifact-preview iframe { width: 400px; height: 240px; border: none; transform: scale(0.5); transform-origin: top left; pointer-events: none; }\n        .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;}\n        .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; }\n        .copy-button:hover { background-color: #45a049; }\n        .code-container { position: relative; }\n        .iframe-wrapper:-webkit-full-screen { width: 100%; height: 100%; }\n        .iframe-wrapper:-moz-full-screen { width: 100%; height: 100%; }\n        .iframe-wrapper:-ms-fullscreen { width: 100%; height: 100%; }\n        .iframe-wrapper:fullscreen { width: 100%; height: 100%; }\n        \"\"\"\n\n    @staticmethod\n    def generate_script():\n        return \"\"\"\n        const totalArtifacts = document.querySelectorAll('.render-view').length;\n        let currentArtifact = 1;\n        let isCodeView = false;\n\n        const modal = document.getElementById(\"artifactModal\");\n        const selectButton = document.getElementById(\"selectArtifact\");\n        const closeButton = document.getElementsByClassName(\"close\")[0];\n        const artifactList = document.getElementById(\"artifactList\");\n        const fullscreenButton = document.getElementById('fullscreenButton');\n        const body = document.body;\n\n        window.addEventListener('load', () => {\n            for (let i = 0; i < totalArtifacts; i++) {\n                ['html', 'css', 'js'].forEach(type => {\n                    const storedContent = localStorage.getItem(`artifact_${i}_${type}`);\n                    if (storedContent) {\n                        const editor = document.getElementById(`${type}-editor-${i}`);\n                        if (editor) {\n                            editor.value = storedContent;\n                        }\n                    }\n                });\n            }\n            reloadCurrentArtifact();\n        });\n\n        function applyStoredChanges(artifactNumber) {\n            ['html', 'css', 'js'].forEach(type => {\n                const storedContent = localStorage.getItem(`artifact_${artifactNumber - 1}_${type}`);\n                if (storedContent) {\n                    updateContent(type, artifactNumber - 1, true);\n                }\n            });\n        }\n\n        document.getElementById('toggleView').addEventListener('change', function() {\n            isCodeView = this.checked;\n            const sliderText = document.querySelector('.slider-text');\n            sliderText.textContent = isCodeView ? 'Code' : 'Render';\n            updateArtifactVisibility();\n        });\n\n        function updateArtifactVisibility() {\n            document.querySelectorAll('.content-item').forEach(item => {\n                const isCorrectArtifact = item.dataset.artifact == currentArtifact;\n                const isCorrectView = (item.classList.contains('render-view') && !isCodeView) || \n                                      (item.classList.contains('code-view') && isCodeView);\n                item.classList.toggle('hidden', !(isCorrectArtifact && isCorrectView));\n            });\n            document.getElementById('prevArtifact').disabled = currentArtifact === 1;\n            document.getElementById('nextArtifact').disabled = currentArtifact === totalArtifacts;\n        }\n\n        function navigateToArtifact(artifactNumber) {\n            currentArtifact = artifactNumber;\n            updateArtifactVisibility();\n            reloadCurrentArtifact();\n            modal.style.display = \"none\";\n        }\n\n        function reloadCurrentArtifact() {\n            const frame = document.querySelector(`.content-item[data-artifact=\"${currentArtifact}\"] .content-frame`);\n            if (frame) {\n                const currentSrcdoc = frame.getAttribute('data-original-content');\n                frame.srcdoc = '';\n                setTimeout(() => {\n                    frame.srcdoc = currentSrcdoc;\n                }, 0);\n            }\n        }\n\n        document.getElementById('prevArtifact').addEventListener('click', () => {\n            if (currentArtifact > 1) {\n                currentArtifact--;\n                updateArtifactVisibility();\n                reloadCurrentArtifact();\n            }\n        });\n\n        document.getElementById('nextArtifact').addEventListener('click', () => {\n            if (currentArtifact < totalArtifacts) {\n                currentArtifact++;\n                updateArtifactVisibility();\n                reloadCurrentArtifact();\n            }\n        });\n\n        function updateContent(type, index, skipReload = false) {\n            const frame = document.querySelector(`.content-item[data-artifact=\"${index + 1}\"] .content-frame`);\n            const editor = document.getElementById(`${type}-editor-${index}`);\n            const content = editor.value;\n            \n            let updatedSrcdoc = frame.getAttribute('data-original-content');\n            const parser = new DOMParser();\n            const doc = parser.parseFromString(updatedSrcdoc, 'text/html');\n            \n            if (type === 'html') {\n                doc.body.innerHTML = content;\n            } else if (type === 'css') {\n                let styleTag = doc.querySelector('style');\n                if (!styleTag) {\n                    styleTag = doc.createElement('style');\n                    doc.head.appendChild(styleTag);\n                }\n                styleTag.textContent = content;\n            } else if (type === 'js') {\n                let scriptTag = doc.querySelector('script:not([src])');\n                if (!scriptTag) {\n                    scriptTag = doc.createElement('script');\n                    doc.body.appendChild(scriptTag);\n                }\n                scriptTag.textContent = content;\n            }\n        \n            updatedSrcdoc = new XMLSerializer().serializeToString(doc);\n            \n            frame.setAttribute('data-original-content', updatedSrcdoc);\n            \n            if (!skipReload) {\n                frame.srcdoc = '';\n                setTimeout(() => {\n                    frame.srcdoc = updatedSrcdoc;\n                }, 0);\n            }\n        \n            localStorage.setItem(`artifact_${index}_${type}`, content);\n            console.log(`Content updated for artifact ${index + 1}, type ${type}`);\n        }\n\n        function copyToClipboard(button, elementId) {\n            const codeElement = document.getElementById(elementId);\n            const textArea = document.createElement('textarea');\n            textArea.value = codeElement.textContent;\n            document.body.appendChild(textArea);\n            textArea.select();\n            document.execCommand('copy');\n            document.body.removeChild(textArea);\n            \n            const originalText = button.textContent;\n            button.textContent = 'Copied!';\n            setTimeout(() => {\n                button.textContent = originalText;\n            }, 2000);\n        }\n\n        selectButton.onclick = function() {\n            const makeTransparent = (doc) => {\n                doc.body.style.background = 'transparent';\n                const styleEl = doc.createElement('style');\n                styleEl.textContent = 'body { background: transparent !important; }';\n                doc.head.appendChild(styleEl);\n            };\n\n            artifactList.innerHTML = '';\n            document.querySelectorAll('.content-frame').forEach((frame, index) => {\n                const li = document.createElement('li');\n                const previewContent = frame.getAttribute('srcdoc');\n                \n                li.innerHTML = `\n                    <div class=\"artifact-info\">\n                        <strong>Artifact ${index + 1}</strong>\n                    </div>\n                    <div class=\"artifact-preview\">\n                        <iframe sandbox=\"allow-scripts allow-same-origin\"></iframe>\n                    </div>\n                `;\n                li.onclick = function() { navigateToArtifact(index + 1); };\n                artifactList.appendChild(li);\n\n                const previewIframe = li.querySelector('.artifact-preview iframe');\n                previewIframe.onload = function() {\n                    makeTransparent(this.contentDocument);\n                    this.contentDocument.body.style.transform = 'scale(0.5)';\n                    this.contentDocument.body.style.transformOrigin = 'top left';\n                    this.style.pointerEvents = 'none';\n                };\n                previewIframe.srcdoc = previewContent;\n            });\n            modal.style.display = \"block\";\n        }\n\n        closeButton.onclick = function() {\n            modal.style.display = \"none\";\n        }\n\n        window.onclick = function(event) {\n            if (event.target == modal) {\n                modal.style.display = \"none\";\n            }\n        }\n\n        document.querySelectorAll('.device-button').forEach(button => {\n            button.addEventListener('click', function() {\n                const width = this.getAttribute('data-width');\n                const wrapper = this.closest('.content-item').querySelector('.iframe-wrapper');\n                const iframe = wrapper.querySelector('.content-frame');\n                \n                if (width === '100%') {\n                    wrapper.style.width = '100%';\n                    wrapper.style.height = '600px';\n                    iframe.style.width = '100%';\n                    iframe.style.height = '100%';\n                } else {\n                    wrapper.style.width = width;\n                    wrapper.style.height = '80vh';\n                    iframe.style.width = width;\n                    iframe.style.height = '100%';\n                }\n                \n                this.closest('.responsive-controls').querySelectorAll('.device-button').forEach(btn => {\n                    btn.classList.remove('active');\n                });\n                this.classList.add('active');\n            });\n        });\n\n        document.querySelectorAll('.resize-handle').forEach(handle => {\n            handle.addEventListener('mousedown', initResize, false);\n        });\n\n        function initResize(e) {\n            window.addEventListener('mousemove', resize, false);\n            window.addEventListener('mouseup', stopResize, false);\n        }\n\n        function resize(e) {\n            if (!body.classList.contains('fullscreen')) {\n                const wrapper = e.target.closest('.iframe-wrapper');\n                wrapper.style.width = (e.clientX - wrapper.offsetLeft) + 'px';\n                wrapper.style.height = (e.clientY - wrapper.offsetTop) + 'px';\n            }\n        }\n\n        function stopResize(e) {\n            window.removeEventListener('mousemove', resize, false);\n            window.removeEventListener('mouseup', stopResize, false);\n        }\n\n        function toggleFullscreen() {\n            const currentFrame = document.querySelector(`.content-item[data-artifact=\"${currentArtifact}\"] .iframe-wrapper`);\n            \n            if (!document.fullscreenElement) {\n                if (currentFrame.requestFullscreen) {\n                    currentFrame.requestFullscreen();\n                } else if (currentFrame.mozRequestFullScreen) {\n                    currentFrame.mozRequestFullScreen();\n                } else if (currentFrame.webkitRequestFullscreen) {\n                    currentFrame.webkitRequestFullscreen();\n                } else if (currentFrame.msRequestFullscreen) {\n                    currentFrame.msRequestFullscreen();\n                }\n            } else {\n                if (document.exitFullscreen) {\n                    document.exitFullscreen();\n                } else if (document.mozCancelFullScreen) {\n                    document.mozCancelFullScreen();\n                } else if (document.webkitExitFullscreen) {\n                    document.webkitExitFullscreen();\n                } else if (document.msExitFullscreen) {\n                    document.msExitFullscreen();\n                }\n            }\n        }\n        \n        fullscreenButton.addEventListener('click', toggleFullscreen);\n\n        document.addEventListener('fullscreenchange', updateFullscreenButtonIcon);\n        document.addEventListener('webkitfullscreenchange', updateFullscreenButtonIcon);\n        document.addEventListener('mozfullscreenchange', updateFullscreenButtonIcon);\n        document.addEventListener('MSFullscreenChange', updateFullscreenButtonIcon);\n\n        function updateFullscreenButtonIcon() {\n            if (document.fullscreenElement) {\n                fullscreenButton.innerHTML = `\n                    <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M3 12h7v2H5v5H3v-7zm18 0h-7v2h5v5h2v-7zM3 7h2V5h5V3H3v4zm18 0h-2V5h-5V3h7v4z\" fill=\"currentColor\"/>\n                    </svg>\n                `;\n            } else {\n                fullscreenButton.innerHTML = `\n                    <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M3 3h7v2H5v5H3V3zm18 0h-7v2h5v5h2V3zM3 21h7v-2H5v-5H3v7zm18 0h-7v-2h5v-5h2v7z\" fill=\"currentColor\"/>\n                    </svg>\n                `;\n            }\n        }\n\n        updateArtifactVisibility();\n        \"\"\"\n\n    @staticmethod\n    def generate_content_item(i, page):\n        html_content = page.get(\"html\", \"\")\n        raw_html = page.get(\"raw_html\", \"\")\n        css_content = page.get(\"css\", \"\")\n        js_content = page.get(\"js\", \"\")\n\n        escaped_html = html.escape(raw_html)\n        escaped_css = html.escape(css_content)\n        escaped_js = html.escape(js_content)\n\n        base_html = f\"\"\"\n            <!DOCTYPE html>\n            <html>\n            <head>\n                <meta charset=\"UTF-8\">\n                <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n                <style>\n                    {css_content}\n                </style>\n            </head>\n            <body>\n                {html_content}\n                <script>\n                    {js_content}\n                </script>\n            </body>\n            </html>\n        \"\"\"\n        escaped_base_html = html.escape(base_html)\n\n        # CodePen data preparation\n        codepen_data = {\"html\": html_content, \"css\": css_content, \"js\": js_content}\n        # 对 JSONstring 进行 HTML 转义\n        JSONstring = html.escape(json.dumps(codepen_data))\n\n        return f\"\"\"\n            <div class='content-item render-view' data-artifact=\"{i+1}\">\n                <h2>HTML Content {i+1}</h2>\n                <div class=\"responsive-controls\">\n                    <button class=\"device-button\" data-width=\"320px\">Mobile</button>\n                    <button class=\"device-button\" data-width=\"768px\">Tablet</button>\n                    <button class=\"device-button active\" data-width=\"100%\">Desktop</button>\n                    <!-- CodePen Button -->\n                    <div class=\"codepen-button-container\" style=\"float: right; margin-left: 30px;\">\n                        <form action=\"https://codepen.io/pen/define\" method=\"POST\" target=\"_blank\">\n                            <input type=\"hidden\" name=\"data\" value='{JSONstring}'>\n                            <input type=\"submit\" value=\"Edit on CodePen\" class=\"codepen-button\" style=\"background-color: #2196F3; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;\"> \n                        </form>\n                    </div>\n                </div>\n                <div class=\"iframe-wrapper\">\n                    <iframe class=\"content-frame\" sandbox=\"allow-scripts\" srcdoc=\"{escaped_base_html}\" data-original-content=\"{escaped_base_html}\"></iframe>\n                    <div class=\"resize-handle\"></div>\n                </div>\n            </div>\n            <div class='content-item code-view hidden' data-artifact=\"{i+1}\">\n                <h2>HTML Content {i+1}</h2>\n                <div class=\"code-container\">\n                    <button class=\"copy-button\" onclick=\"copyToClipboard(this, 'html-editor-{i}')\">Copy</button>\n                    <pre class=\"editor\" id=\"html-editor-{i}\">{escaped_html}</pre>\n                </div>\n            </div>\n            {\"\" if not css_content else f'''\n            <div class='content-item code-view hidden' data-artifact=\"{i+1}\">\n                <h2>CSS Content {i+1}</h2>\n                <div class=\"code-container\">\n                    <button class=\"copy-button\" onclick=\"copyToClipboard(this, 'css-editor-{i}')\">Copy</button>\n                    <pre class=\"editor\" id=\"css-editor-{i}\">{escaped_css}</pre>\n                </div>\n            </div>\n            '''}\n            {\"\" if not js_content else f'''\n            <div class='content-item code-view hidden' data-artifact=\"{i+1}\">\n                <h2>JavaScript Content {i+1}</h2>\n                <div class=\"code-container\">\n                    <button class=\"copy-button\" onclick=\"copyToClipboard(this, 'js-editor-{i}')\">Copy</button>\n                    <pre class=\"editor\" id=\"js-editor-{i}\">{escaped_js}</pre>\n                </div>\n            </div>\n            '''}\n        \"\"\"\n\n    @classmethod\n    def create_middleware_html(cls, pages):\n        content_items = \"\".join(\n            cls.generate_content_item(i, page) for i, page in enumerate(pages)\n        )\n\n        return f\"\"\"\n        <!DOCTYPE html>\n        <html lang=\"en\">\n        <head>\n            <meta charset=\"UTF-8\">\n            <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n            <title>Generated Content</title>\n            <style>\n                {cls.generate_style()}\n            </style>\n        </head>\n        <body>\n            <div class=\"header\">\n                <label class=\"switch\">\n                    <input type=\"checkbox\" id=\"toggleView\">\n                    <span class=\"slider\">\n                        <span class=\"slider-text\">Render</span>\n                    </span>\n                </label>\n                <div class=\"nav-buttons\">\n                    <button id=\"prevArtifact\" class=\"nav-button\" aria-label=\"Previous artifact\">&#10094;</button>\n                    <button id=\"selectArtifact\" class=\"select-button\" aria-label=\"Select artifact\">\n                        <svg width=\"30\" height=\"30\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n                          <text x=\"50%\" y=\"60%\" dominant-baseline=\"middle\" text-anchor=\"middle\" font-size=\"20\" font-weight=\"bold\" fill=\"url(#gradient)\" filter=\"url(#shadow)\">A</text>\n                          <defs>\n                            <linearGradient id=\"gradient\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">\n                              <stop offset=\"0%\" stop-color=\"#2196F3\"/>\n                              <stop offset=\"100%\" stop-color=\"#FFFFFF\"/>\n                            </linearGradient>\n                            <filter id=\"shadow\">\n                              <feDropShadow dx=\"2\" dy=\"2\" stdDeviation=\"1\" flood-color=\"rgba(0, 0, 0, 0.3)\"/>\n                            </filter>\n                          </defs>\n                        </svg>\n                    </button>\n                    <button id=\"nextArtifact\" class=\"nav-button\" aria-label=\"Next artifact\">&#10095;</button>\n                    <button id=\"fullscreenButton\" class=\"fullscreen-button\" aria-label=\"Toggle fullscreen\">\n                        <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n                            <path d=\"M3 3h7v2H5v5H3V3zm18 0h-7v2h5v5h2V3zM3 21h7v-2H5v-5H3v7zm18 0h-7v-2h5v-5h2v7z\" fill=\"currentColor\"/>\n                        </svg>\n                    </button>\n                </div>\n            </div>\n            <div class='content-wrapper'>\n                {content_items}\n            </div>\n            <div id=\"artifactModal\" class=\"modal\">\n                <div class=\"modal-content\">\n                    <span class=\"close\">&times;</span>\n                    <h2>Select Artifact</h2>\n                    <ul class=\"artifact-list\" id=\"artifactList\"></ul>\n                </div>\n            </div>\n            <script>\n                {cls.generate_script()}\n            </script>\n        </body>\n        </html>\n        \"\"\"\n\n\nclass Filter:\n    class Valves(BaseModel):\n        priority: int = Field(\n            default=0, description=\"Priority level for the filter operations.\"\n        )\n\n    def __init__(self):\n        self.valves = self.Valves()\n        self.viz_dir = \"visualizations\"\n        self.html_dir = \"html\"\n        self.middleware_file = \"middleware.html\"\n        self.current_artifact = None\n\n    def ensure_chat_directory(self, chat_id, content_type):\n        chat_dir = os.path.join(UPLOAD_DIR, self.viz_dir, content_type, chat_id)\n        os.makedirs(chat_dir, exist_ok=True)\n        return chat_dir\n\n    def extract_content(self, content, pattern):\n        return re.findall(pattern, content, re.IGNORECASE | re.DOTALL)\n\n    def write_content_to_file(self, content, user_id, chat_id, content_type):\n        chat_dir = self.ensure_chat_directory(chat_id, content_type)\n        filename = f\"{content_type}_{uuid.uuid4()}.html\"\n        file_path = os.path.join(chat_dir, filename)\n\n        with open(file_path, \"w\") as f:\n            f.write(content)\n\n        relative_path = os.path.join(self.viz_dir, content_type, chat_id, filename)\n        file_form = FileForm(\n            id=str(uuid.uuid4()),\n            filename=relative_path,\n            meta={\n                \"name\": filename,\n                \"content_type\": \"text/html\",\n                \"size\": len(content),\n                \"path\": file_path,\n            },\n        )\n        return Files.insert_new_file(user_id, file_form).id\n\n    def parse_content(self, content):\n        html_pattern = r\"```(?:html|xml)\\s*([\\s\\S]*?)\\s*```\"\n        css_pattern = r\"```css\\s*([\\s\\S]*?)\\s*```\"\n        js_pattern = r\"```javascript\\s*([\\s\\S]*?)\\s*```\"\n        svg_pattern = r\"<svg[\\s\\S]*?</svg>\"\n\n        html_blocks = self.extract_content(content, html_pattern)\n        css_blocks = self.extract_content(content, css_pattern)\n        js_blocks = self.extract_content(content, js_pattern)\n        standalone_svg_blocks = self.extract_content(content, svg_pattern)\n\n        artifacts = []\n        for i in range(\n            max(\n                len(html_blocks),\n                len(css_blocks),\n                len(js_blocks),\n                len(standalone_svg_blocks),\n            )\n        ):\n            artifact = {\"html\": \"\", \"css\": \"\", \"js\": \"\", \"raw_html\": \"\"}\n            if i < len(html_blocks):\n                artifact[\"html\"] = html_blocks[i]\n                artifact[\"raw_html\"] = html_blocks[i]\n            if i < len(css_blocks):\n                artifact[\"css\"] = css_blocks[i]\n            if i < len(js_blocks):\n                artifact[\"js\"] = js_blocks[i]\n            if i < len(standalone_svg_blocks):\n                artifact[\"html\"] = standalone_svg_blocks[i]\n                artifact[\"raw_html\"] = standalone_svg_blocks[i]\n            artifacts.append(artifact)\n\n        return artifacts\n\n    def create_middleware_html(self, pages):\n        return MiddlewareHTMLGenerator.create_middleware_html(pages)\n\n    def inlet(self, body: dict, user: Optional[dict] = None) -> dict:\n        return body\n\n    def outlet(self, body: dict, __user__: Optional[dict] = None) -> dict:\n        if \"messages\" in body and body[\"messages\"] and __user__ and \"id\" in __user__:\n            last_message = body[\"messages\"][-1][\"content\"]\n            chat_id = body.get(\"chat_id\")\n\n            if chat_id:\n                try:\n                    pages = self.parse_content(last_message)\n\n                    if pages:\n                        middleware_content = self.create_middleware_html(pages)\n                        middleware_id = self.write_content_to_file(\n                            middleware_content,\n                            __user__[\"id\"],\n                            chat_id,\n                            self.html_dir,\n                        )\n\n                        body[\"messages\"][-1][\n                            \"content\"\n                        ] += f\"\\n\\n{{{{HTML_FILE_ID_{middleware_id}}}}}\"\n\n                except Exception as e:\n                    error_msg = (\n                        f\"Error processing content: {str(e)}\\n{traceback.format_exc()}\"\n                    )\n                    print(error_msg)\n                    body[\"messages\"][-1][\n                        \"content\"\n                    ] += f\"\\n\\nError: Failed to process content. Details: {error_msg}\"\n            else:\n                print(\"chat_id is missing\")\n\n        return body\n","meta":{"description":"Parses all HTML, CSS, JavaScript, and SVG code in the LLM output and renders it in real time in your chats.","manifest":{"title":"OpenWebUI Artifacts","author":"open-webui, atgehrhardt","author_url":"https://github.com/atgehrhardt","funding_url":"https://github.com/open-webui","version":"1.2.7","required_open_webui_version":"0.3.10"}},"is_active":true,"is_global":true,"updated_at":1724579187,"created_at":1723386585}]