akhaliq HF Staff commited on
Commit
df4152a
·
1 Parent(s): da4de0c

multi page apps

Browse files
Files changed (1) hide show
  1. app.py +176 -10
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
- system_prompt = HTML_SYSTEM_PROMPT_WITH_SEARCH if enable_search else HTML_SYSTEM_PROMPT
 
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
- preview_val = send_to_sandbox(clean_code)
 
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
- preview_val = send_to_sandbox(final_content)
 
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
- preview_val = send_to_sandbox(clean_code)
 
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
- preview_val = send_to_sandbox(clean_code)
 
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
- preview_val = send_to_sandbox(clean_code)
 
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
- preview_val = send_to_sandbox(clean_content)
 
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
- preview_val = send_to_sandbox(clean_code)
 
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
- preview_val = send_to_sandbox(final_content)
 
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>"