Spaces:
Running
Running
multi page apps
Browse files
app.py
CHANGED
@@ -380,6 +380,54 @@ Always respond with code that can be executed or rendered directly.
|
|
380 |
|
381 |
Always output only the HTML code inside a ```html ... ``` code block, and do not include any explanations or extra text. Do NOT add the language name at the top of the code output."""
|
382 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
383 |
GENERIC_SYSTEM_PROMPT_WITH_SEARCH = """You are an expert {language} developer. You have access to real-time web search. When needed, use web search to find the latest information, best practices, or specific technologies for {language}.
|
384 |
|
385 |
Write clean, idiomatic, and runnable {language} code for the user's request. If possible, include comments and best practices. Output ONLY the code inside a ``` code block, and do not include any explanations or extra text. If the user provides a file or other context, use it as a reference. If the code is for a script or app, make it as self-contained as possible. Do NOT add the language name at the top of the code output."""
|
@@ -1170,6 +1218,110 @@ def send_transformers_to_sandbox(files: dict) -> str:
|
|
1170 |
merged_html = build_transformers_inline_html(files)
|
1171 |
return send_to_sandbox(merged_html)
|
1172 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1173 |
def parse_svelte_output(text):
|
1174 |
"""Parse Svelte output to extract individual files"""
|
1175 |
files = {
|
@@ -3129,7 +3281,8 @@ def generation_code(query: Optional[str], vlm_image: Optional[gr.Image], gen_ima
|
|
3129 |
else:
|
3130 |
# Use language-specific prompt
|
3131 |
if language == "html":
|
3132 |
-
|
|
|
3133 |
elif language == "transformers.js":
|
3134 |
system_prompt = TRANSFORMERS_JS_SYSTEM_PROMPT_WITH_SEARCH if enable_search else TRANSFORMERS_JS_SYSTEM_PROMPT
|
3135 |
elif language == "svelte":
|
@@ -3199,7 +3352,8 @@ This will help me create a better design for you."""
|
|
3199 |
# Live streaming preview
|
3200 |
preview_val = None
|
3201 |
if language == "html":
|
3202 |
-
|
|
|
3203 |
elif language == "python" and is_streamlit_code(clean_code):
|
3204 |
preview_val = send_streamlit_to_stlite(clean_code)
|
3205 |
yield {
|
@@ -3428,7 +3582,8 @@ This will help me create a better design for you."""
|
|
3428 |
|
3429 |
preview_val = None
|
3430 |
if language == "html":
|
3431 |
-
|
|
|
3432 |
elif language == "python" and is_streamlit_code(final_content):
|
3433 |
preview_val = send_streamlit_to_stlite(final_content)
|
3434 |
yield {
|
@@ -3494,7 +3649,8 @@ This will help me create a better design for you."""
|
|
3494 |
clean_code = clean_code.replace("\\t", "\t")
|
3495 |
preview_val = None
|
3496 |
if language == "html":
|
3497 |
-
|
|
|
3498 |
elif language == "python" and is_streamlit_code(clean_code):
|
3499 |
preview_val = send_streamlit_to_stlite(clean_code)
|
3500 |
yield {
|
@@ -3513,7 +3669,8 @@ This will help me create a better design for you."""
|
|
3513 |
_history.append([query, clean_code])
|
3514 |
preview_val = None
|
3515 |
if language == "html":
|
3516 |
-
|
|
|
3517 |
elif language == "python" and is_streamlit_code(clean_code):
|
3518 |
preview_val = send_streamlit_to_stlite(clean_code)
|
3519 |
yield {
|
@@ -3689,7 +3846,8 @@ This will help me create a better design for you."""
|
|
3689 |
# Model returned a complete HTML file
|
3690 |
preview_val = None
|
3691 |
if language == "html":
|
3692 |
-
|
|
|
3693 |
elif language == "python" and is_streamlit_code(clean_code):
|
3694 |
preview_val = send_streamlit_to_stlite(clean_code)
|
3695 |
elif language == "gradio" or (language == "python" and is_gradio_code(clean_code)):
|
@@ -3706,7 +3864,8 @@ This will help me create a better design for you."""
|
|
3706 |
clean_content = remove_code_block(modified_content)
|
3707 |
preview_val = None
|
3708 |
if language == "html":
|
3709 |
-
|
|
|
3710 |
elif language == "python" and is_streamlit_code(clean_content):
|
3711 |
preview_val = send_streamlit_to_stlite(clean_content)
|
3712 |
elif language == "gradio" or (language == "python" and is_gradio_code(clean_content)):
|
@@ -3719,7 +3878,8 @@ This will help me create a better design for you."""
|
|
3719 |
else:
|
3720 |
preview_val = None
|
3721 |
if language == "html":
|
3722 |
-
|
|
|
3723 |
elif language == "python" and is_streamlit_code(clean_code):
|
3724 |
preview_val = send_streamlit_to_stlite(clean_code)
|
3725 |
elif language == "gradio" or (language == "python" and is_gradio_code(clean_code)):
|
@@ -3835,7 +3995,7 @@ This will help me create a better design for you."""
|
|
3835 |
yield {
|
3836 |
code_output: clean_content,
|
3837 |
history: _history,
|
3838 |
-
sandbox: (send_to_sandbox(clean_content) if language == "html" else (send_streamlit_to_stlite(clean_content) if (language == "python" and is_streamlit_code(clean_content)) else "<div style='padding:1em;color:#888;text-align:center;'>Preview is only available for HTML or Streamlit-in-Python.</div>")),
|
3839 |
history_output: history_to_chatbot_messages(_history),
|
3840 |
}
|
3841 |
else:
|
@@ -3864,7 +4024,8 @@ This will help me create a better design for you."""
|
|
3864 |
_history.append([query, final_content])
|
3865 |
preview_val = None
|
3866 |
if language == "html":
|
3867 |
-
|
|
|
3868 |
elif language == "python" and is_streamlit_code(final_content):
|
3869 |
preview_val = send_streamlit_to_stlite(final_content)
|
3870 |
elif language == "gradio" or (language == "python" and is_gradio_code(final_content)):
|
@@ -5323,6 +5484,11 @@ with gr.Blocks(
|
|
5323 |
|
5324 |
def preview_logic(code, language, html_part=None, js_part=None, css_part=None):
|
5325 |
if language == "html":
|
|
|
|
|
|
|
|
|
|
|
5326 |
return send_to_sandbox(code)
|
5327 |
if language == "streamlit":
|
5328 |
return send_streamlit_to_stlite(code) if is_streamlit_code(code) else "<div style='padding:1em;color:#888;text-align:center;'>Add `import streamlit as st` to enable Streamlit preview.</div>"
|
|
|
380 |
|
381 |
Always output only the HTML code inside a ```html ... ``` code block, and do not include any explanations or extra text. Do NOT add the language name at the top of the code output."""
|
382 |
|
383 |
+
# Multi-page static HTML project prompt (generic, production-style structure)
|
384 |
+
MULTIPAGE_HTML_SYSTEM_PROMPT = """You are an expert front-end developer.
|
385 |
+
|
386 |
+
Create a production-ready MULTI-PAGE website using ONLY HTML, CSS, and vanilla JavaScript. Do NOT use SPA frameworks.
|
387 |
+
|
388 |
+
Output MUST be a multi-file project with at least:
|
389 |
+
- index.html (home)
|
390 |
+
- about.html (secondary page)
|
391 |
+
- contact.html (secondary page)
|
392 |
+
- assets/css/styles.css (global styles)
|
393 |
+
- assets/js/main.js (site-wide JS)
|
394 |
+
|
395 |
+
Navigation requirements:
|
396 |
+
- A consistent header with a nav bar on every page
|
397 |
+
- Highlight current nav item
|
398 |
+
- Responsive layout and accessibility best practices
|
399 |
+
|
400 |
+
Output format requirements (CRITICAL):
|
401 |
+
- Return ONLY a series of file sections, each starting with a filename line:
|
402 |
+
=== index.html ===
|
403 |
+
...file content...
|
404 |
+
|
405 |
+
=== about.html ===
|
406 |
+
...file content...
|
407 |
+
|
408 |
+
(repeat for all files)
|
409 |
+
- Do NOT wrap files in Markdown code fences
|
410 |
+
- Use relative paths between files (e.g., assets/css/styles.css)
|
411 |
+
|
412 |
+
General requirements:
|
413 |
+
- Use modern, semantic HTML
|
414 |
+
- Mobile-first responsive design
|
415 |
+
- Include basic SEO meta tags in <head>
|
416 |
+
- Include a footer on all pages
|
417 |
+
- Avoid external CSS/JS frameworks (optional: CDN fonts/icons allowed)
|
418 |
+
"""
|
419 |
+
|
420 |
+
# Multi-page with search augmentation
|
421 |
+
MULTIPAGE_HTML_SYSTEM_PROMPT_WITH_SEARCH = """You are an expert front-end developer. You have access to real-time web search.
|
422 |
+
|
423 |
+
Create a production-ready MULTI-PAGE website using ONLY HTML, CSS, and vanilla JavaScript. Do NOT use SPA frameworks.
|
424 |
+
|
425 |
+
Follow the same file output format and project structure as specified:
|
426 |
+
=== filename === blocks for each file (no Markdown fences)
|
427 |
+
|
428 |
+
Use search results to apply current best practices in accessibility, semantics, responsive meta tags, and performance (preconnect, responsive images).
|
429 |
+
"""
|
430 |
+
|
431 |
GENERIC_SYSTEM_PROMPT_WITH_SEARCH = """You are an expert {language} developer. You have access to real-time web search. When needed, use web search to find the latest information, best practices, or specific technologies for {language}.
|
432 |
|
433 |
Write clean, idiomatic, and runnable {language} code for the user's request. If possible, include comments and best practices. Output ONLY the code inside a ``` code block, and do not include any explanations or extra text. If the user provides a file or other context, use it as a reference. If the code is for a script or app, make it as self-contained as possible. Do NOT add the language name at the top of the code output."""
|
|
|
1218 |
merged_html = build_transformers_inline_html(files)
|
1219 |
return send_to_sandbox(merged_html)
|
1220 |
|
1221 |
+
def parse_multipage_html_output(text: str) -> Dict[str, str]:
|
1222 |
+
"""Parse multi-page HTML output formatted as repeated "=== filename ===" sections.
|
1223 |
+
|
1224 |
+
Returns a mapping of filename → file content. Supports nested paths like assets/css/styles.css.
|
1225 |
+
"""
|
1226 |
+
if not text:
|
1227 |
+
return {}
|
1228 |
+
# First, strip any markdown fences
|
1229 |
+
cleaned = remove_code_block(text)
|
1230 |
+
files: Dict[str, str] = {}
|
1231 |
+
import re as _re
|
1232 |
+
pattern = _re.compile(r"^===\s*([^=\n]+?)\s*===\s*\n([\s\S]*?)(?=\n===\s*[^=\n]+?\s*===|\Z)", _re.MULTILINE)
|
1233 |
+
for m in pattern.finditer(cleaned):
|
1234 |
+
name = m.group(1).strip()
|
1235 |
+
content = m.group(2).strip()
|
1236 |
+
# Remove accidental trailing fences if present
|
1237 |
+
content = _re.sub(r"^```\w*\s*\n|\n```\s*$", "", content)
|
1238 |
+
files[name] = content
|
1239 |
+
return files
|
1240 |
+
|
1241 |
+
def inline_multipage_into_single_preview(files: Dict[str, str]) -> str:
|
1242 |
+
"""Inline local CSS/JS referenced by index.html for preview inside a data: iframe.
|
1243 |
+
|
1244 |
+
- Uses index.html as the base document
|
1245 |
+
- Inlines <link href="..."> if the target exists in files
|
1246 |
+
- Inlines <script src="..."> if the target exists in files
|
1247 |
+
- Leaves other links (e.g., about.html) untouched; preview covers the home page
|
1248 |
+
"""
|
1249 |
+
import re as _re
|
1250 |
+
html = files.get('index.html', '')
|
1251 |
+
if not html:
|
1252 |
+
return ""
|
1253 |
+
doc = html
|
1254 |
+
# Inline CSS links that point to known files
|
1255 |
+
def _inline_css(match):
|
1256 |
+
href = match.group(1)
|
1257 |
+
if href in files:
|
1258 |
+
return f"<style>\n{files[href]}\n</style>"
|
1259 |
+
return match.group(0)
|
1260 |
+
doc = _re.sub(r"<link[^>]+href=\"([^\"]+)\"[^>]*/?>", _inline_css, doc, flags=_re.IGNORECASE)
|
1261 |
+
|
1262 |
+
# Inline JS scripts that point to known files
|
1263 |
+
def _inline_js(match):
|
1264 |
+
src = match.group(1)
|
1265 |
+
if src in files:
|
1266 |
+
return f"<script>\n{files[src]}\n</script>"
|
1267 |
+
return match.group(0)
|
1268 |
+
doc = _re.sub(r"<script[^>]+src=\"([^\"]+)\"[^>]*>\s*</script>", _inline_js, doc, flags=_re.IGNORECASE)
|
1269 |
+
|
1270 |
+
# Inject a lightweight in-iframe client-side navigator to load other HTML files
|
1271 |
+
try:
|
1272 |
+
import json as _json
|
1273 |
+
import base64 as _b64
|
1274 |
+
import re as _re
|
1275 |
+
html_pages = {k: v for k, v in files.items() if k.lower().endswith('.html')}
|
1276 |
+
# Ensure index.html entry restores the current body's HTML
|
1277 |
+
_m_body = _re.search(r"<body[^>]*>([\s\S]*?)</body>", doc, flags=_re.IGNORECASE)
|
1278 |
+
_index_body = _m_body.group(1) if _m_body else doc
|
1279 |
+
html_pages['index.html'] = _index_body
|
1280 |
+
encoded = _b64.b64encode(_json.dumps(html_pages).encode('utf-8')).decode('ascii')
|
1281 |
+
nav_script = (
|
1282 |
+
"<script>\n" # Simple client-side loader for internal links
|
1283 |
+
"(function(){\n"
|
1284 |
+
f" const MP_FILES = JSON.parse(atob('{encoded}'));\n"
|
1285 |
+
" function extractBody(html){\n"
|
1286 |
+
" try {\n"
|
1287 |
+
" const doc = new DOMParser().parseFromString(html, 'text/html');\n"
|
1288 |
+
" const title = doc.querySelector('title'); if (title) document.title = title.textContent || document.title;\n"
|
1289 |
+
" return doc.body ? doc.body.innerHTML : html;\n"
|
1290 |
+
" } catch(e){ return html; }\n"
|
1291 |
+
" }\n"
|
1292 |
+
" function loadPage(path){\n"
|
1293 |
+
" if (!MP_FILES[path]) return false;\n"
|
1294 |
+
" const bodyHTML = extractBody(MP_FILES[path]);\n"
|
1295 |
+
" document.body.innerHTML = bodyHTML;\n"
|
1296 |
+
" attach();\n"
|
1297 |
+
" try { history.replaceState({}, '', '#'+path); } catch(e){}\n"
|
1298 |
+
" return true;\n"
|
1299 |
+
" }\n"
|
1300 |
+
" function clickHandler(e){\n"
|
1301 |
+
" const a = e.target && e.target.closest ? e.target.closest('a') : null;\n"
|
1302 |
+
" if (!a) return;\n"
|
1303 |
+
" const href = a.getAttribute('href') || '';\n"
|
1304 |
+
" if (!href || href.startsWith('#') || /^https?:/i.test(href) || href.startsWith('mailto:') || href.startsWith('tel:')) return;\n"
|
1305 |
+
" const clean = href.split('#')[0].split('?')[0];\n"
|
1306 |
+
" if (MP_FILES[clean]) { e.preventDefault(); loadPage(clean); }\n"
|
1307 |
+
" }\n"
|
1308 |
+
" function attach(){ document.removeEventListener('click', clickHandler, true); document.addEventListener('click', clickHandler, true); }\n"
|
1309 |
+
" document.addEventListener('DOMContentLoaded', function(){ attach(); const initial = (location.hash||'').slice(1); if (initial && MP_FILES[initial]) loadPage(initial); }, { once:true });\n"
|
1310 |
+
"})();\n"
|
1311 |
+
"</script>"
|
1312 |
+
)
|
1313 |
+
m = _re.search(r"</body>", doc, flags=_re.IGNORECASE)
|
1314 |
+
if m:
|
1315 |
+
i = m.start()
|
1316 |
+
doc = doc[:i] + nav_script + doc[i:]
|
1317 |
+
else:
|
1318 |
+
doc = doc + nav_script
|
1319 |
+
except Exception:
|
1320 |
+
# Non-fatal in preview
|
1321 |
+
pass
|
1322 |
+
|
1323 |
+
return doc
|
1324 |
+
|
1325 |
def parse_svelte_output(text):
|
1326 |
"""Parse Svelte output to extract individual files"""
|
1327 |
files = {
|
|
|
3281 |
else:
|
3282 |
# Use language-specific prompt
|
3283 |
if language == "html":
|
3284 |
+
# Use multi-page format prompt to encourage production multi-file output
|
3285 |
+
system_prompt = MULTIPAGE_HTML_SYSTEM_PROMPT_WITH_SEARCH if enable_search else MULTIPAGE_HTML_SYSTEM_PROMPT
|
3286 |
elif language == "transformers.js":
|
3287 |
system_prompt = TRANSFORMERS_JS_SYSTEM_PROMPT_WITH_SEARCH if enable_search else TRANSFORMERS_JS_SYSTEM_PROMPT
|
3288 |
elif language == "svelte":
|
|
|
3352 |
# Live streaming preview
|
3353 |
preview_val = None
|
3354 |
if language == "html":
|
3355 |
+
_mp = parse_multipage_html_output(clean_code)
|
3356 |
+
preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mp)) if _mp.get('index.html') else send_to_sandbox(clean_code)
|
3357 |
elif language == "python" and is_streamlit_code(clean_code):
|
3358 |
preview_val = send_streamlit_to_stlite(clean_code)
|
3359 |
yield {
|
|
|
3582 |
|
3583 |
preview_val = None
|
3584 |
if language == "html":
|
3585 |
+
_mpf2 = parse_multipage_html_output(final_content)
|
3586 |
+
preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mpf2)) if _mpf2.get('index.html') else send_to_sandbox(final_content)
|
3587 |
elif language == "python" and is_streamlit_code(final_content):
|
3588 |
preview_val = send_streamlit_to_stlite(final_content)
|
3589 |
yield {
|
|
|
3649 |
clean_code = clean_code.replace("\\t", "\t")
|
3650 |
preview_val = None
|
3651 |
if language == "html":
|
3652 |
+
_mpc = parse_multipage_html_output(clean_code)
|
3653 |
+
preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mpc)) if _mpc.get('index.html') else send_to_sandbox(clean_code)
|
3654 |
elif language == "python" and is_streamlit_code(clean_code):
|
3655 |
preview_val = send_streamlit_to_stlite(clean_code)
|
3656 |
yield {
|
|
|
3669 |
_history.append([query, clean_code])
|
3670 |
preview_val = None
|
3671 |
if language == "html":
|
3672 |
+
_mpc2 = parse_multipage_html_output(clean_code)
|
3673 |
+
preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mpc2)) if _mpc2.get('index.html') else send_to_sandbox(clean_code)
|
3674 |
elif language == "python" and is_streamlit_code(clean_code):
|
3675 |
preview_val = send_streamlit_to_stlite(clean_code)
|
3676 |
yield {
|
|
|
3846 |
# Model returned a complete HTML file
|
3847 |
preview_val = None
|
3848 |
if language == "html":
|
3849 |
+
_mpc3 = parse_multipage_html_output(clean_code)
|
3850 |
+
preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mpc3)) if _mpc3.get('index.html') else send_to_sandbox(clean_code)
|
3851 |
elif language == "python" and is_streamlit_code(clean_code):
|
3852 |
preview_val = send_streamlit_to_stlite(clean_code)
|
3853 |
elif language == "gradio" or (language == "python" and is_gradio_code(clean_code)):
|
|
|
3864 |
clean_content = remove_code_block(modified_content)
|
3865 |
preview_val = None
|
3866 |
if language == "html":
|
3867 |
+
_mpc4 = parse_multipage_html_output(clean_content)
|
3868 |
+
preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mpc4)) if _mpc4.get('index.html') else send_to_sandbox(clean_content)
|
3869 |
elif language == "python" and is_streamlit_code(clean_content):
|
3870 |
preview_val = send_streamlit_to_stlite(clean_content)
|
3871 |
elif language == "gradio" or (language == "python" and is_gradio_code(clean_content)):
|
|
|
3878 |
else:
|
3879 |
preview_val = None
|
3880 |
if language == "html":
|
3881 |
+
_mpc5 = parse_multipage_html_output(clean_code)
|
3882 |
+
preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mpc5)) if _mpc5.get('index.html') else send_to_sandbox(clean_code)
|
3883 |
elif language == "python" and is_streamlit_code(clean_code):
|
3884 |
preview_val = send_streamlit_to_stlite(clean_code)
|
3885 |
elif language == "gradio" or (language == "python" and is_gradio_code(clean_code)):
|
|
|
3995 |
yield {
|
3996 |
code_output: clean_content,
|
3997 |
history: _history,
|
3998 |
+
sandbox: ((send_to_sandbox(inline_multipage_into_single_preview(parse_multipage_html_output(clean_content))) if parse_multipage_html_output(clean_content).get('index.html') else send_to_sandbox(clean_content)) if language == "html" else (send_streamlit_to_stlite(clean_content) if (language == "python" and is_streamlit_code(clean_content)) else "<div style='padding:1em;color:#888;text-align:center;'>Preview is only available for HTML or Streamlit-in-Python.</div>")),
|
3999 |
history_output: history_to_chatbot_messages(_history),
|
4000 |
}
|
4001 |
else:
|
|
|
4024 |
_history.append([query, final_content])
|
4025 |
preview_val = None
|
4026 |
if language == "html":
|
4027 |
+
_mpf = parse_multipage_html_output(final_content)
|
4028 |
+
preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mpf)) if _mpf.get('index.html') else send_to_sandbox(final_content)
|
4029 |
elif language == "python" and is_streamlit_code(final_content):
|
4030 |
preview_val = send_streamlit_to_stlite(final_content)
|
4031 |
elif language == "gradio" or (language == "python" and is_gradio_code(final_content)):
|
|
|
5484 |
|
5485 |
def preview_logic(code, language, html_part=None, js_part=None, css_part=None):
|
5486 |
if language == "html":
|
5487 |
+
# If the content is a multi-page block, inline for preview; else render directly
|
5488 |
+
files = parse_multipage_html_output(code)
|
5489 |
+
if files and files.get('index.html'):
|
5490 |
+
merged = inline_multipage_into_single_preview(files)
|
5491 |
+
return send_to_sandbox(merged)
|
5492 |
return send_to_sandbox(code)
|
5493 |
if language == "streamlit":
|
5494 |
return send_streamlit_to_stlite(code) if is_streamlit_code(code) else "<div style='padding:1em;color:#888;text-align:center;'>Add `import streamlit as st` to enable Streamlit preview.</div>"
|