[{"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