akhaliq HF Staff commited on
Commit
1cf27de
·
1 Parent(s): 65b486c
Files changed (1) hide show
  1. app.py +169 -23
app.py CHANGED
@@ -69,6 +69,40 @@ Always respond with code that can be executed or rendered directly.
69
 
70
  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."""
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  # Stricter prompt for GLM-4.5V to ensure a complete, runnable HTML document with no escaped characters
73
  GLM45V_HTML_SYSTEM_PROMPT = """You are an expert front-end developer.
74
 
@@ -794,7 +828,6 @@ for _m in AVAILABLE_MODELS:
794
  break
795
  if DEFAULT_MODEL is None and AVAILABLE_MODELS:
796
  DEFAULT_MODEL = AVAILABLE_MODELS[0]
797
-
798
  DEMO_LIST = [
799
  {
800
  "title": "Todo App",
@@ -1335,6 +1368,27 @@ def parse_multipage_html_output(text: str) -> Dict[str, str]:
1335
  files[name] = content
1336
  return files
1337
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1338
  def validate_and_autofix_files(files: Dict[str, str]) -> Dict[str, str]:
1339
  """Ensure minimal contract for multi-file sites; auto-fix missing pieces.
1340
 
@@ -1486,6 +1540,19 @@ def inline_multipage_into_single_preview(files: Dict[str, str]) -> str:
1486
 
1487
  return doc
1488
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1489
  def parse_svelte_output(text):
1490
  """Parse Svelte output to extract individual files"""
1491
  files = {
@@ -1560,9 +1627,8 @@ def process_image_for_model(image):
1560
 
1561
  buffer = io.BytesIO()
1562
  image.save(buffer, format='PNG')
1563
- img_str = base64.b64encode(buffer.getvalue()).decode()
1564
  return f"data:image/png;base64,{img_str}"
1565
-
1566
  def generate_image_with_qwen(prompt: str, image_index: int = 0) -> str:
1567
  """Generate image using Qwen image model via Hugging Face InferenceClient with optimized data URL"""
1568
  try:
@@ -2307,7 +2373,6 @@ def create_music_replacement_blocks_text_to_music(html_content: str, prompt: str
2307
 
2308
  # If no <body>, just append
2309
  return f"{SEARCH_START}\n\n{DIVIDER}\n{audio_html}\n{REPLACE_END}"
2310
-
2311
  def create_image_replacement_blocks_from_input_image(html_content: str, user_prompt: str, input_image_data, max_images: int = 1) -> str:
2312
  """Create search/replace blocks using image-to-image generation with a provided input image.
2313
 
@@ -2480,12 +2545,33 @@ def create_video_replacement_blocks_from_input_image(html_content: str, user_pro
2480
  print("[Image2Video] No <body> tag; appending video via replacement block")
2481
  return f"{SEARCH_START}\n\n{DIVIDER}\n{video_html}\n{REPLACE_END}"
2482
 
2483
- def apply_generated_media_to_html(html_content: str, user_prompt: str, enable_text_to_image: bool, enable_image_to_image: bool, input_image_data, image_to_image_prompt: str | None = None, text_to_image_prompt: str | None = None, enable_image_to_video: bool = False, image_to_video_prompt: str | None = None, session_id: Optional[str] = None, enable_text_to_video: bool = False, text_to_video_prompt: str | None = None, enable_text_to_music: bool = False, text_to_music_prompt: str | None = None) -> str:
2484
- """Apply text-to-image and/or image-to-image replacements to HTML content.
2485
 
2486
- If both toggles are enabled, text-to-image replacements run first, then image-to-image.
 
 
 
2487
  """
2488
- result = html_content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2489
  try:
2490
  print(
2491
  f"[MediaApply] enable_i2v={enable_image_to_video}, enable_i2i={enable_image_to_image}, "
@@ -2495,7 +2581,16 @@ def apply_generated_media_to_html(html_content: str, user_prompt: str, enable_te
2495
  if enable_image_to_video and input_image_data is not None and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
2496
  i2v_prompt = (image_to_video_prompt or user_prompt or "").strip()
2497
  print(f"[MediaApply] Running image-to-video with prompt len={len(i2v_prompt)}")
2498
- blocks_v = create_video_replacement_blocks_from_input_image(result, i2v_prompt, input_image_data, session_id=session_id)
 
 
 
 
 
 
 
 
 
2499
  if blocks_v:
2500
  print("[MediaApply] Applying image-to-video replacement blocks")
2501
  before_len = len(result)
@@ -2513,46 +2608,94 @@ def apply_generated_media_to_html(html_content: str, user_prompt: str, enable_te
2513
  result = result_after
2514
  else:
2515
  print("[MediaApply] No i2v replacement blocks generated")
 
 
 
2516
  return result
2517
 
2518
  # If text-to-video is enabled, insert a generated video (no input image required) and return.
2519
  if enable_text_to_video and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
2520
  t2v_prompt = (text_to_video_prompt or user_prompt or "").strip()
2521
  print(f"[MediaApply] Running text-to-video with prompt len={len(t2v_prompt)}")
2522
- blocks_tv = create_video_replacement_blocks_text_to_video(result, t2v_prompt, session_id=session_id)
 
 
 
 
 
 
 
 
 
2523
  if blocks_tv:
2524
  print("[MediaApply] Applying text-to-video replacement blocks")
2525
  result = apply_search_replace_changes(result, blocks_tv)
2526
  else:
2527
  print("[MediaApply] No t2v replacement blocks generated")
 
 
 
2528
  return result
2529
 
2530
  # If text-to-music is enabled, insert a generated audio player near the top of body and return.
2531
  if enable_text_to_music and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
2532
  t2m_prompt = (text_to_music_prompt or user_prompt or "").strip()
2533
  print(f"[MediaApply] Running text-to-music with prompt len={len(t2m_prompt)}")
2534
- blocks_tm = create_music_replacement_blocks_text_to_music(result, t2m_prompt, session_id=session_id)
 
 
 
 
 
 
 
 
 
2535
  if blocks_tm:
2536
  print("[MediaApply] Applying text-to-music replacement blocks")
2537
  result = apply_search_replace_changes(result, blocks_tm)
2538
  else:
2539
  print("[MediaApply] No t2m replacement blocks generated")
 
 
 
2540
  return result
2541
 
2542
  # If an input image is provided and image-to-image is enabled, we only replace one image
2543
  # and skip text-to-image to satisfy the requirement to replace exactly the number of uploaded images.
2544
  if enable_image_to_image and input_image_data is not None and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
2545
  i2i_prompt = (image_to_image_prompt or user_prompt or "").strip()
2546
- blocks2 = create_image_replacement_blocks_from_input_image(result, i2i_prompt, input_image_data, max_images=1)
 
 
 
 
 
 
 
 
 
2547
  if blocks2:
2548
  result = apply_search_replace_changes(result, blocks2)
 
 
 
2549
  return result
2550
 
2551
  if enable_text_to_image and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
2552
  t2i_prompt = (text_to_image_prompt or user_prompt or "").strip()
2553
  print(f"[MediaApply] Running text-to-image with prompt len={len(t2i_prompt)}")
2554
- # Single-image flow for text-to-image
2555
- blocks = create_image_replacement_blocks_text_to_image_single(result, t2i_prompt)
 
 
 
 
 
 
 
 
 
2556
  if blocks:
2557
  print("[MediaApply] Applying text-to-image replacement blocks")
2558
  result = apply_search_replace_changes(result, blocks)
@@ -2561,6 +2704,9 @@ def apply_generated_media_to_html(html_content: str, user_prompt: str, enable_te
2561
  print("[MediaApply] Exception during media application:")
2562
  traceback.print_exc()
2563
  return html_content
 
 
 
2564
  return result
2565
 
2566
  def create_multimodal_message(text, image=None):
@@ -2990,7 +3136,6 @@ def demo_card_click(e: gr.EventData):
2990
  except (KeyError, IndexError, AttributeError) as e:
2991
  # Return the first demo description as fallback
2992
  return DEMO_LIST[0]['description']
2993
-
2994
  def extract_text_from_image(image_path):
2995
  """Extract text from image using OCR"""
2996
  try:
@@ -3535,7 +3680,7 @@ This will help me create a better design for you."""
3535
  # Apply media generation (images/video/music)
3536
  print("[Generate] Applying post-generation media to GLM-4.5 HTML output")
3537
  final_content = apply_generated_media_to_html(
3538
- content,
3539
  query,
3540
  enable_text_to_image=enable_image_generation,
3541
  enable_image_to_image=enable_image_to_image,
@@ -3747,9 +3892,10 @@ This will help me create a better design for you."""
3747
 
3748
  preview_val = None
3749
  if language == "html":
3750
- _mpf2 = parse_multipage_html_output(final_content)
 
3751
  _mpf2 = validate_and_autofix_files(_mpf2)
3752
- preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mpf2)) if _mpf2.get('index.html') else send_to_sandbox(final_content)
3753
  elif language == "python" and is_streamlit_code(final_content):
3754
  preview_val = send_streamlit_to_stlite(final_content)
3755
  yield {
@@ -4200,9 +4346,10 @@ This will help me create a better design for you."""
4200
  _history.append([query, final_content])
4201
  preview_val = None
4202
  if language == "html":
4203
- _mpf = parse_multipage_html_output(final_content)
 
4204
  _mpf = validate_and_autofix_files(_mpf)
4205
- preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mpf)) if _mpf.get('index.html') else send_to_sandbox(final_content)
4206
  elif language == "python" and is_streamlit_code(final_content):
4207
  preview_val = send_streamlit_to_stlite(final_content)
4208
  elif language == "gradio" or (language == "python" and is_gradio_code(final_content)):
@@ -4457,7 +4604,6 @@ def wrap_html_in_gradio_app(html_code):
4457
  'if __name__ == "__main__":\n'
4458
  ' demo.launch()\n'
4459
  )
4460
-
4461
  def deploy_to_spaces(code):
4462
  if not code or not code.strip():
4463
  return # Do nothing if code is empty
@@ -5406,6 +5552,8 @@ with gr.Blocks(
5406
  visible=False
5407
  )
5408
 
 
 
5409
  def on_image_to_image_toggle(toggled, beta_enabled):
5410
  # Only show in classic mode (beta disabled)
5411
  vis = bool(toggled) and not bool(beta_enabled)
@@ -5827,7 +5975,6 @@ with gr.Blocks(
5827
  import re
5828
  match = re.search(r"https?://[^\s]+", text or "")
5829
  return match.group(0) if match else None
5830
-
5831
  def apply_chat_command(message, chat_messages):
5832
  # Support plain text or dict from MultimodalTextbox
5833
  text = message if isinstance(message, str) else (message.get("text", "") if isinstance(message, dict) else "")
@@ -6148,7 +6295,6 @@ with gr.Blocks(
6148
 
6149
  restart_message = f"""
6150
  🎨 **Theme saved:** {theme_name}
6151
-
6152
  ⚠️ **Restart required** to fully apply the new theme.
6153
 
6154
  **Why restart is needed:** Gradio themes are set during application startup and cannot be changed dynamically at runtime. This ensures all components are properly styled with consistent theming.
 
69
 
70
  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."""
71
 
72
+ def llm_place_media(html_content: str, media_html_tag: str, media_kind: str = "image") -> str:
73
+ """Ask a lightweight model to produce search/replace blocks that insert media_html_tag in the best spot.
74
+
75
+ The model must return ONLY our block format using SEARCH_START/DIVIDER/REPLACE_END.
76
+ """
77
+ try:
78
+ client = get_inference_client("Qwen/Qwen3-Coder-480B-A35B-Instruct", "auto")
79
+ system_prompt = (
80
+ "You are a code editor. Insert the provided media tag into the given HTML in the most semantically appropriate place.\n"
81
+ "Prefer replacing a placeholder <img> or a hero area; otherwise insert inside <body> near primary content.\n"
82
+ "Return ONLY search/replace blocks using the exact markers: <<<<<<< SEARCH, =======, >>>>>>> REPLACE.\n"
83
+ "Do NOT include any commentary. Ensure the SEARCH block matches exact lines from the input.\n"
84
+ )
85
+ user_payload = (
86
+ "HTML Document:\n" + html_content + "\n\n" +
87
+ f"Media ({media_kind}):\n" + media_html_tag + "\n\n" +
88
+ "Produce search/replace blocks now."
89
+ )
90
+ messages = [
91
+ {"role": "system", "content": system_prompt},
92
+ {"role": "user", "content": user_payload},
93
+ ]
94
+ completion = client.chat.completions.create(
95
+ model="Qwen/Qwen3-Coder-480B-A35B-Instruct",
96
+ messages=messages,
97
+ max_tokens=2000,
98
+ temperature=0.2,
99
+ )
100
+ text = (completion.choices[0].message.content or "") if completion and completion.choices else ""
101
+ return text.strip()
102
+ except Exception as e:
103
+ print(f"[LLMPlaceMedia] Fallback due to error: {e}")
104
+ return ""
105
+
106
  # Stricter prompt for GLM-4.5V to ensure a complete, runnable HTML document with no escaped characters
107
  GLM45V_HTML_SYSTEM_PROMPT = """You are an expert front-end developer.
108
 
 
828
  break
829
  if DEFAULT_MODEL is None and AVAILABLE_MODELS:
830
  DEFAULT_MODEL = AVAILABLE_MODELS[0]
 
831
  DEMO_LIST = [
832
  {
833
  "title": "Todo App",
 
1368
  files[name] = content
1369
  return files
1370
 
1371
+ def format_multipage_output(files: Dict[str, str]) -> str:
1372
+ """Format a dict of files back into === filename === sections.
1373
+
1374
+ Ensures `index.html` appears first if present; others follow sorted by path.
1375
+ """
1376
+ if not isinstance(files, dict) or not files:
1377
+ return ""
1378
+ ordered_paths = []
1379
+ if 'index.html' in files:
1380
+ ordered_paths.append('index.html')
1381
+ for path in sorted(files.keys()):
1382
+ if path == 'index.html':
1383
+ continue
1384
+ ordered_paths.append(path)
1385
+ parts: list[str] = []
1386
+ for path in ordered_paths:
1387
+ parts.append(f"=== {path} ===")
1388
+ # Avoid trailing extra newlines to keep blocks compact
1389
+ parts.append((files.get(path) or '').rstrip())
1390
+ return "\n".join(parts)
1391
+
1392
  def validate_and_autofix_files(files: Dict[str, str]) -> Dict[str, str]:
1393
  """Ensure minimal contract for multi-file sites; auto-fix missing pieces.
1394
 
 
1540
 
1541
  return doc
1542
 
1543
+ def extract_html_document(text: str) -> str:
1544
+ """Return substring starting from the first <!DOCTYPE html> or <html> if present, else original text.
1545
+
1546
+ This ignores prose or planning notes before the actual HTML so previews don't break.
1547
+ """
1548
+ if not text:
1549
+ return text
1550
+ lower = text.lower()
1551
+ idx = lower.find("<!doctype html")
1552
+ if idx == -1:
1553
+ idx = lower.find("<html")
1554
+ return text[idx:] if idx != -1 else text
1555
+
1556
  def parse_svelte_output(text):
1557
  """Parse Svelte output to extract individual files"""
1558
  files = {
 
1627
 
1628
  buffer = io.BytesIO()
1629
  image.save(buffer, format='PNG')
1630
+ img_str = base64.b64encode(buffer.getvalue()).decode('utf-8')
1631
  return f"data:image/png;base64,{img_str}"
 
1632
  def generate_image_with_qwen(prompt: str, image_index: int = 0) -> str:
1633
  """Generate image using Qwen image model via Hugging Face InferenceClient with optimized data URL"""
1634
  try:
 
2373
 
2374
  # If no <body>, just append
2375
  return f"{SEARCH_START}\n\n{DIVIDER}\n{audio_html}\n{REPLACE_END}"
 
2376
  def create_image_replacement_blocks_from_input_image(html_content: str, user_prompt: str, input_image_data, max_images: int = 1) -> str:
2377
  """Create search/replace blocks using image-to-image generation with a provided input image.
2378
 
 
2545
  print("[Image2Video] No <body> tag; appending video via replacement block")
2546
  return f"{SEARCH_START}\n\n{DIVIDER}\n{video_html}\n{REPLACE_END}"
2547
 
2548
+ def apply_generated_media_to_html(html_content: str, user_prompt: str, enable_text_to_image: bool, enable_image_to_image: bool, input_image_data, image_to_image_prompt: str | None = None, text_to_image_prompt: str | None = None, enable_image_to_video: bool = False, image_to_video_prompt: str | None = None, session_id: Optional[str] = None, enable_text_to_video: bool = False, text_to_video_prompt: Optional[str] = None, enable_text_to_music: bool = False, text_to_music_prompt: Optional[str] = None) -> str:
2549
+ """Apply text/image/video/music replacements to HTML content.
2550
 
2551
+ - Works with single-document HTML strings
2552
+ - Also supports multi-page outputs formatted as === filename === sections by
2553
+ applying changes to the HTML entrypoint (index.html if present) and
2554
+ returning the updated multi-page text.
2555
  """
2556
+ # Detect multi-page sections and choose an entry HTML to modify
2557
+ is_multipage = False
2558
+ multipage_files: Dict[str, str] = {}
2559
+ entry_html_path: Optional[str] = None
2560
+ try:
2561
+ multipage_files = parse_multipage_html_output(html_content) or {}
2562
+ if multipage_files:
2563
+ is_multipage = True
2564
+ if 'index.html' in multipage_files:
2565
+ entry_html_path = 'index.html'
2566
+ else:
2567
+ html_paths = [p for p in multipage_files.keys() if p.lower().endswith('.html')]
2568
+ entry_html_path = html_paths[0] if html_paths else None
2569
+ except Exception:
2570
+ is_multipage = False
2571
+ multipage_files = {}
2572
+ entry_html_path = None
2573
+
2574
+ result = multipage_files.get(entry_html_path, html_content) if is_multipage and entry_html_path else html_content
2575
  try:
2576
  print(
2577
  f"[MediaApply] enable_i2v={enable_image_to_video}, enable_i2i={enable_image_to_image}, "
 
2581
  if enable_image_to_video and input_image_data is not None and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
2582
  i2v_prompt = (image_to_video_prompt or user_prompt or "").strip()
2583
  print(f"[MediaApply] Running image-to-video with prompt len={len(i2v_prompt)}")
2584
+ try:
2585
+ video_html_tag = generate_video_from_image(input_image_data, i2v_prompt, session_id=session_id)
2586
+ if not (video_html_tag or "").startswith("Error"):
2587
+ blocks_v = llm_place_media(result, video_html_tag, media_kind="video")
2588
+ else:
2589
+ blocks_v = ""
2590
+ except Exception:
2591
+ blocks_v = ""
2592
+ if not blocks_v:
2593
+ blocks_v = create_video_replacement_blocks_from_input_image(result, i2v_prompt, input_image_data, session_id=session_id)
2594
  if blocks_v:
2595
  print("[MediaApply] Applying image-to-video replacement blocks")
2596
  before_len = len(result)
 
2608
  result = result_after
2609
  else:
2610
  print("[MediaApply] No i2v replacement blocks generated")
2611
+ if is_multipage and entry_html_path:
2612
+ multipage_files[entry_html_path] = result
2613
+ return format_multipage_output(multipage_files)
2614
  return result
2615
 
2616
  # If text-to-video is enabled, insert a generated video (no input image required) and return.
2617
  if enable_text_to_video and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
2618
  t2v_prompt = (text_to_video_prompt or user_prompt or "").strip()
2619
  print(f"[MediaApply] Running text-to-video with prompt len={len(t2v_prompt)}")
2620
+ try:
2621
+ video_html_tag = generate_video_from_text(t2v_prompt, session_id=session_id)
2622
+ if not (video_html_tag or "").startswith("Error"):
2623
+ blocks_tv = llm_place_media(result, video_html_tag, media_kind="video")
2624
+ else:
2625
+ blocks_tv = ""
2626
+ except Exception:
2627
+ blocks_tv = ""
2628
+ if not blocks_tv:
2629
+ blocks_tv = create_video_replacement_blocks_text_to_video(result, t2v_prompt, session_id=session_id)
2630
  if blocks_tv:
2631
  print("[MediaApply] Applying text-to-video replacement blocks")
2632
  result = apply_search_replace_changes(result, blocks_tv)
2633
  else:
2634
  print("[MediaApply] No t2v replacement blocks generated")
2635
+ if is_multipage and entry_html_path:
2636
+ multipage_files[entry_html_path] = result
2637
+ return format_multipage_output(multipage_files)
2638
  return result
2639
 
2640
  # If text-to-music is enabled, insert a generated audio player near the top of body and return.
2641
  if enable_text_to_music and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
2642
  t2m_prompt = (text_to_music_prompt or user_prompt or "").strip()
2643
  print(f"[MediaApply] Running text-to-music with prompt len={len(t2m_prompt)}")
2644
+ try:
2645
+ audio_html_tag = generate_music_from_text(t2m_prompt, session_id=session_id)
2646
+ if not (audio_html_tag or "").startswith("Error"):
2647
+ blocks_tm = llm_place_media(result, audio_html_tag, media_kind="audio")
2648
+ else:
2649
+ blocks_tm = ""
2650
+ except Exception:
2651
+ blocks_tm = ""
2652
+ if not blocks_tm:
2653
+ blocks_tm = create_music_replacement_blocks_text_to_music(result, t2m_prompt, session_id=session_id)
2654
  if blocks_tm:
2655
  print("[MediaApply] Applying text-to-music replacement blocks")
2656
  result = apply_search_replace_changes(result, blocks_tm)
2657
  else:
2658
  print("[MediaApply] No t2m replacement blocks generated")
2659
+ if is_multipage and entry_html_path:
2660
+ multipage_files[entry_html_path] = result
2661
+ return format_multipage_output(multipage_files)
2662
  return result
2663
 
2664
  # If an input image is provided and image-to-image is enabled, we only replace one image
2665
  # and skip text-to-image to satisfy the requirement to replace exactly the number of uploaded images.
2666
  if enable_image_to_image and input_image_data is not None and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
2667
  i2i_prompt = (image_to_image_prompt or user_prompt or "").strip()
2668
+ try:
2669
+ image_html_tag = generate_image_to_image(input_image_data, i2i_prompt)
2670
+ if not (image_html_tag or "").startswith("Error"):
2671
+ blocks2 = llm_place_media(result, image_html_tag, media_kind="image")
2672
+ else:
2673
+ blocks2 = ""
2674
+ except Exception:
2675
+ blocks2 = ""
2676
+ if not blocks2:
2677
+ blocks2 = create_image_replacement_blocks_from_input_image(result, i2i_prompt, input_image_data, max_images=1)
2678
  if blocks2:
2679
  result = apply_search_replace_changes(result, blocks2)
2680
+ if is_multipage and entry_html_path:
2681
+ multipage_files[entry_html_path] = result
2682
+ return format_multipage_output(multipage_files)
2683
  return result
2684
 
2685
  if enable_text_to_image and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
2686
  t2i_prompt = (text_to_image_prompt or user_prompt or "").strip()
2687
  print(f"[MediaApply] Running text-to-image with prompt len={len(t2i_prompt)}")
2688
+ # Single-image flow for text-to-image (LLM placement first, fallback deterministic)
2689
+ try:
2690
+ image_html_tag = generate_image_with_qwen(t2i_prompt, 0)
2691
+ if not (image_html_tag or "").startswith("Error"):
2692
+ blocks = llm_place_media(result, image_html_tag, media_kind="image")
2693
+ else:
2694
+ blocks = ""
2695
+ except Exception:
2696
+ blocks = ""
2697
+ if not blocks:
2698
+ blocks = create_image_replacement_blocks_text_to_image_single(result, t2i_prompt)
2699
  if blocks:
2700
  print("[MediaApply] Applying text-to-image replacement blocks")
2701
  result = apply_search_replace_changes(result, blocks)
 
2704
  print("[MediaApply] Exception during media application:")
2705
  traceback.print_exc()
2706
  return html_content
2707
+ if is_multipage and entry_html_path:
2708
+ multipage_files[entry_html_path] = result
2709
+ return format_multipage_output(multipage_files)
2710
  return result
2711
 
2712
  def create_multimodal_message(text, image=None):
 
3136
  except (KeyError, IndexError, AttributeError) as e:
3137
  # Return the first demo description as fallback
3138
  return DEMO_LIST[0]['description']
 
3139
  def extract_text_from_image(image_path):
3140
  """Extract text from image using OCR"""
3141
  try:
 
3680
  # Apply media generation (images/video/music)
3681
  print("[Generate] Applying post-generation media to GLM-4.5 HTML output")
3682
  final_content = apply_generated_media_to_html(
3683
+ clean_code,
3684
  query,
3685
  enable_text_to_image=enable_image_generation,
3686
  enable_image_to_image=enable_image_to_image,
 
3892
 
3893
  preview_val = None
3894
  if language == "html":
3895
+ safe_preview = extract_html_document(final_content)
3896
+ _mpf2 = parse_multipage_html_output(safe_preview)
3897
  _mpf2 = validate_and_autofix_files(_mpf2)
3898
+ preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mpf2)) if _mpf2.get('index.html') else send_to_sandbox(safe_preview)
3899
  elif language == "python" and is_streamlit_code(final_content):
3900
  preview_val = send_streamlit_to_stlite(final_content)
3901
  yield {
 
4346
  _history.append([query, final_content])
4347
  preview_val = None
4348
  if language == "html":
4349
+ safe_preview = extract_html_document(final_content)
4350
+ _mpf = parse_multipage_html_output(safe_preview)
4351
  _mpf = validate_and_autofix_files(_mpf)
4352
+ preview_val = send_to_sandbox(inline_multipage_into_single_preview(_mpf)) if _mpf.get('index.html') else send_to_sandbox(safe_preview)
4353
  elif language == "python" and is_streamlit_code(final_content):
4354
  preview_val = send_streamlit_to_stlite(final_content)
4355
  elif language == "gradio" or (language == "python" and is_gradio_code(final_content)):
 
4604
  'if __name__ == "__main__":\n'
4605
  ' demo.launch()\n'
4606
  )
 
4607
  def deploy_to_spaces(code):
4608
  if not code or not code.strip():
4609
  return # Do nothing if code is empty
 
5552
  visible=False
5553
  )
5554
 
5555
+ # LLM-guided media placement is now always on (no toggle in UI)
5556
+
5557
  def on_image_to_image_toggle(toggled, beta_enabled):
5558
  # Only show in classic mode (beta disabled)
5559
  vis = bool(toggled) and not bool(beta_enabled)
 
5975
  import re
5976
  match = re.search(r"https?://[^\s]+", text or "")
5977
  return match.group(0) if match else None
 
5978
  def apply_chat_command(message, chat_messages):
5979
  # Support plain text or dict from MultimodalTextbox
5980
  text = message if isinstance(message, str) else (message.get("text", "") if isinstance(message, dict) else "")
 
6295
 
6296
  restart_message = f"""
6297
  🎨 **Theme saved:** {theme_name}
 
6298
  ⚠️ **Restart required** to fully apply the new theme.
6299
 
6300
  **Why restart is needed:** Gradio themes are set during application startup and cannot be changed dynamically at runtime. This ensures all components are properly styled with consistent theming.