openuiX / function-Artifacts.json
iridescentX's picture
Update function-Artifacts.json
001fdf1 verified
raw
history blame
31.6 kB
[{"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}]