Spaces:
Running
Running
fixes
Browse files
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
|
| 2484 |
-
"""Apply text
|
| 2485 |
|
| 2486 |
-
|
|
|
|
|
|
|
|
|
|
| 2487 |
"""
|
| 2488 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
|
|
|
| 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(
|
| 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 |
-
|
|
|
|
| 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(
|
| 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.
|