| import gradio as gr |
| import os |
| from openai import OpenAI |
| from get_machine_from_json import string_to_bsg |
| import json |
|
|
| |
| with open('user_system_prompt.txt', 'r', encoding='utf-8') as f: |
| SYSTEM_PROMPT = f.read() |
|
|
| |
| EXAMPLES = [] |
| try: |
| with open('examples.json', 'r', encoding='utf-8') as f: |
| examples_data = json.load(f) |
| EXAMPLES = [[ex["description"]] for ex in examples_data.get("examples", [])] |
| except: |
| pass |
|
|
| |
| MODEL_NAME = "deepseek-ai/DeepSeek-V3.2-Exp" |
| HF_ROUTER_BASE_URL = "https://router.huggingface.co/v1" |
|
|
| |
| def create_client(): |
| """Create OpenAI-compatible client using HF Router""" |
| |
| hf_token = os.environ.get("HF_TOKEN") |
| |
| if not hf_token: |
| raise ValueError( |
| "โ ๏ธ HF_TOKEN not set!\n\n" |
| "Please add your Hugging Face token in Space Settings:\n\n" |
| "1. Go to your Space Settings\n" |
| "2. Find 'Repository secrets' section\n" |
| "3. Click 'Add a secret'\n" |
| "4. Name: HF_TOKEN\n" |
| "5. Value: Your token (get from https://huggingface.co/settings/tokens)\n" |
| "6. Save and restart Space\n\n" |
| "Token should have READ permission and start with 'hf_'" |
| ) |
| |
| return OpenAI( |
| base_url=HF_ROUTER_BASE_URL, |
| api_key=hf_token |
| ) |
|
|
| def generate_machine(user_prompt, temperature=0.7, max_tokens=4096): |
| """ |
| Generate machine design JSON using AI, then convert to XML |
| |
| Args: |
| user_prompt: User's machine description |
| temperature: Generation temperature |
| max_tokens: Maximum tokens |
| |
| Returns: |
| tuple: (ai_response, xml_string, status_message) |
| """ |
| if not user_prompt.strip(): |
| return "", "", "โ Please enter a machine description!" |
| |
| try: |
| client = create_client() |
| |
| |
| messages = [ |
| {"role": "system", "content": SYSTEM_PROMPT}, |
| {"role": "user", "content": user_prompt} |
| ] |
| |
| |
| response = "" |
| try: |
| stream = client.chat.completions.create( |
| model=MODEL_NAME, |
| messages=messages, |
| temperature=temperature, |
| max_tokens=max_tokens, |
| stream=True, |
| ) |
| |
| for chunk in stream: |
| if chunk.choices and len(chunk.choices) > 0: |
| if chunk.choices[0].delta and chunk.choices[0].delta.content: |
| response += chunk.choices[0].delta.content |
| |
| except Exception as api_error: |
| error_msg = str(api_error) |
| if "401" in error_msg or "Unauthorized" in error_msg or "authentication" in error_msg.lower(): |
| return "", "", ( |
| "โ Authentication Failed!\n\n" |
| "Please set your HF Token in Space Settings:\n\n" |
| "1. Get token from https://huggingface.co/settings/tokens\n" |
| "2. Go to Space Settings โ Repository secrets\n" |
| "3. Add secret:\n" |
| " - Name: HF_TOKEN\n" |
| " - Value: your token (starts with 'hf_')\n" |
| "4. Restart Space\n\n" |
| "๐ก Token is FREE and only needs READ permission!\n\n" |
| f"Error details: {error_msg}" |
| ) |
| elif "404" in error_msg: |
| return "", "", ( |
| "โ Model Not Found!\n\n" |
| "The current model is not available on Inference API.\n" |
| "Trying backup models automatically...\n\n" |
| f"Error details: {error_msg}" |
| ) |
| elif "list index out of range" in error_msg: |
| return "", "", ( |
| "โ API Response Error!\n\n" |
| "The model returned an unexpected response format.\n" |
| "This might be due to:\n" |
| "- Model is still loading\n" |
| "- Temporary API issue\n" |
| "- Rate limiting\n\n" |
| "Please try again in a moment.\n\n" |
| f"Error details: {error_msg}" |
| ) |
| else: |
| return "", "", f"โ API Error: {error_msg}" |
| |
| |
| try: |
| xml_string = string_to_bsg(response) |
| status = "โ
Generation successful! You can now download the .bsg file." |
| return response, xml_string, status |
| except Exception as e: |
| return response, "", f"โ ๏ธ AI generation completed, but XML conversion failed: {str(e)}" |
| |
| except ValueError as e: |
| return "", "", str(e) |
| except IndexError as e: |
| return "", "", ( |
| "โ Response parsing error!\n\n" |
| "The API response format was unexpected.\n" |
| "Please try again. If this persists, the model may be temporarily unavailable.\n\n" |
| f"Error: {str(e)}" |
| ) |
| except Exception as e: |
| import traceback |
| error_details = traceback.format_exc() |
| return "", "", f"โ Generation failed: {str(e)}\n\nDetails:\n{error_details}" |
|
|
| def convert_json_to_xml(json_input): |
| """ |
| Manually convert JSON to XML |
| |
| Args: |
| json_input: JSON string or JSON data |
| |
| Returns: |
| tuple: (xml_string, status_message) |
| """ |
| if not json_input.strip(): |
| return "", "โ Please enter JSON data!" |
| |
| try: |
| xml_string = string_to_bsg(json_input) |
| return xml_string, "โ
Conversion successful!" |
| except Exception as e: |
| return "", f"โ Conversion failed: {str(e)}" |
|
|
| def save_xml_to_file(xml_content): |
| """Save XML to .bsg file""" |
| if not xml_content: |
| return None |
| |
| output_path = "generated_machine.bsg" |
| with open(output_path, 'w', encoding='utf-8') as f: |
| f.write(xml_content) |
| return output_path |
|
|
| |
| custom_css = """ |
| /* Global Styles */ |
| .gradio-container { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important; |
| } |
| |
| /* Remove default padding/margin for better control */ |
| .contain { |
| max-width: 1200px !important; |
| margin: 0 auto !important; |
| } |
| |
| /* Header styles matching index.html */ |
| .header-box { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| color: white; |
| padding: 30px; |
| text-align: center; |
| border-radius: 10px; |
| margin-bottom: 20px; |
| } |
| |
| .header-box h1 { |
| font-size: 2.5em; |
| margin-bottom: 10px; |
| font-weight: 600; |
| } |
| |
| .header-box p { |
| font-size: 1.1em; |
| opacity: 0.9; |
| } |
| |
| .badge { |
| display: inline-block; |
| background: rgba(255, 255, 255, 0.2); |
| padding: 5px 15px; |
| border-radius: 20px; |
| font-size: 0.9em; |
| margin-top: 10px; |
| } |
| |
| /* Info box styles */ |
| .info-box { |
| background: #e7f3ff; |
| border-left: 4px solid #2196F3; |
| padding: 15px; |
| margin-bottom: 20px; |
| border-radius: 4px; |
| } |
| |
| .info-box h4 { |
| margin-bottom: 10px; |
| color: #1976D2; |
| font-weight: 600; |
| } |
| |
| .info-box ul { |
| margin-left: 20px; |
| line-height: 1.8; |
| color: #333; |
| } |
| |
| /* Input styles */ |
| .gr-textbox, .gr-text-input { |
| border: 2px solid #e0e0e0 !important; |
| border-radius: 8px !important; |
| font-size: 16px !important; |
| } |
| |
| .gr-textbox:focus, .gr-text-input:focus { |
| border-color: #667eea !important; |
| } |
| |
| /* Button styles matching index.html */ |
| .gr-button { |
| padding: 15px 30px !important; |
| font-size: 16px !important; |
| font-weight: 600 !important; |
| border: none !important; |
| border-radius: 8px !important; |
| cursor: pointer !important; |
| transition: all 0.3s !important; |
| text-transform: uppercase !important; |
| letter-spacing: 0.5px !important; |
| min-width: 150px !important; |
| } |
| |
| .gr-button-primary { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; |
| color: white !important; |
| } |
| |
| .gr-button-primary:hover { |
| transform: translateY(-2px) !important; |
| box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4) !important; |
| } |
| |
| .gr-button-secondary { |
| background: #6c757d !important; |
| color: white !important; |
| } |
| |
| .gr-button-secondary:hover { |
| background: #5a6268 !important; |
| transform: translateY(-2px) !important; |
| } |
| |
| /* Tab styles */ |
| .tabs { |
| border-bottom: 2px solid #e0e0e0 !important; |
| margin-bottom: 20px !important; |
| } |
| |
| button[role="tab"] { |
| padding: 12px 24px !important; |
| font-size: 16px !important; |
| font-weight: 500 !important; |
| color: #666 !important; |
| border: none !important; |
| background: transparent !important; |
| cursor: pointer !important; |
| transition: all 0.3s !important; |
| border-bottom: 3px solid transparent !important; |
| } |
| |
| button[role="tab"][aria-selected="true"] { |
| color: #667eea !important; |
| border-bottom-color: #667eea !important; |
| } |
| |
| button[role="tab"]:hover { |
| color: #667eea !important; |
| } |
| |
| /* Footer styles */ |
| .footer-box { |
| text-align: center; |
| margin-top: 30px; |
| padding: 20px; |
| background: #f8f9fa; |
| border-radius: 10px; |
| } |
| |
| .footer-box p { |
| color: #666; |
| margin: 5px 0; |
| font-size: 0.95em; |
| } |
| |
| .footer-box a { |
| color: #667eea; |
| text-decoration: none; |
| } |
| |
| .footer-box a:hover { |
| text-decoration: underline; |
| } |
| |
| /* Accordion styles */ |
| .gr-accordion { |
| border: 2px solid #e0e0e0 !important; |
| border-radius: 8px !important; |
| margin-top: 15px !important; |
| } |
| |
| /* Output text area styles */ |
| .gr-text-input textarea { |
| font-family: 'Consolas', 'Monaco', monospace !important; |
| font-size: 14px !important; |
| line-height: 1.6 !important; |
| } |
| |
| /* Markdown output styles */ |
| .gr-markdown { |
| padding: 15px !important; |
| background: #f8f9fa !important; |
| border-radius: 8px !important; |
| border: 1px solid #e0e0e0 !important; |
| } |
| |
| /* Label styles */ |
| label { |
| font-weight: 500 !important; |
| color: #555 !important; |
| margin-bottom: 8px !important; |
| } |
| |
| /* Examples section */ |
| .gr-examples { |
| margin-top: 15px !important; |
| } |
| |
| /* Make sure content doesn't overflow */ |
| .gr-row, .gr-column { |
| width: 100% !important; |
| } |
| |
| /* Adjust spacing */ |
| .gr-box { |
| padding: 20px !important; |
| } |
| |
| /* File component styles */ |
| .gr-file { |
| margin-top: 15px !important; |
| padding: 15px !important; |
| border: 2px dashed #e0e0e0 !important; |
| border-radius: 8px !important; |
| } |
| |
| /* Mobile responsive styles */ |
| @media (max-width: 768px) { |
| .header-box h1 { |
| font-size: 1.8em !important; |
| } |
| |
| .header-box p { |
| font-size: 0.95em !important; |
| } |
| |
| .badge { |
| font-size: 0.85em !important; |
| padding: 4px 12px !important; |
| } |
| |
| .info-box { |
| padding: 12px !important; |
| font-size: 0.9em !important; |
| } |
| |
| .gr-button { |
| padding: 12px 20px !important; |
| font-size: 14px !important; |
| } |
| |
| .footer-box { |
| padding: 15px !important; |
| font-size: 0.9em !important; |
| } |
| |
| /* Mobile link buttons */ |
| .header-box a { |
| padding: 8px 16px !important; |
| font-size: 0.85em !important; |
| } |
| } |
| |
| /* Dark mode support - ensures text is always readable */ |
| @media (prefers-color-scheme: dark) { |
| /* Keep header gradient but ensure text contrast */ |
| .header-box { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; |
| } |
| |
| .header-box h1, |
| .header-box p, |
| .header-box a, |
| .badge { |
| color: white !important; |
| } |
| |
| /* Adjust info box for dark mode */ |
| .info-box { |
| background: #2d3748 !important; |
| border-left-color: #4299e1 !important; |
| color: #e2e8f0 !important; |
| } |
| |
| .info-box h4 { |
| color: #63b3ed !important; |
| } |
| |
| .info-box ul { |
| color: #e2e8f0 !important; |
| } |
| |
| .info-box a { |
| color: #90cdf4 !important; |
| } |
| |
| /* Footer dark mode */ |
| .footer-box { |
| background: #2d3748 !important; |
| color: #e2e8f0 !important; |
| } |
| |
| .footer-box p { |
| color: #e2e8f0 !important; |
| } |
| |
| .footer-box a { |
| color: #90cdf4 !important; |
| } |
| |
| /* Input fields dark mode */ |
| .gr-textbox, |
| .gr-text-input { |
| background: #2d3748 !important; |
| color: #e2e8f0 !important; |
| border-color: #4a5568 !important; |
| } |
| |
| /* Labels dark mode */ |
| label { |
| color: #e2e8f0 !important; |
| } |
| |
| /* Markdown output dark mode */ |
| .gr-markdown { |
| background: #2d3748 !important; |
| color: #e2e8f0 !important; |
| border-color: #4a5568 !important; |
| } |
| } |
| |
| /* Light mode explicit styles - ensures text is always readable */ |
| @media (prefers-color-scheme: light) { |
| .header-box h1, |
| .header-box p, |
| .header-box a, |
| .badge { |
| color: white !important; |
| text-shadow: 0 1px 2px rgba(0,0,0,0.1); |
| } |
| |
| .info-box { |
| background: #e7f3ff !important; |
| color: #1a202c !important; |
| } |
| |
| .info-box h4 { |
| color: #1976D2 !important; |
| } |
| |
| .info-box ul { |
| color: #333 !important; |
| } |
| |
| .info-box a { |
| color: #667eea !important; |
| } |
| |
| .footer-box { |
| background: #f8f9fa !important; |
| color: #333 !important; |
| } |
| |
| .footer-box a { |
| color: #667eea !important; |
| } |
| } |
| |
| /* High contrast mode for accessibility */ |
| @media (prefers-contrast: high) { |
| .header-box h1, |
| .header-box p, |
| .badge { |
| text-shadow: 0 2px 4px rgba(0,0,0,0.3); |
| font-weight: 600 !important; |
| } |
| |
| .info-box { |
| border-left-width: 6px !important; |
| } |
| |
| .gr-button { |
| border: 2px solid currentColor !important; |
| } |
| } |
| """ |
|
|
| |
| with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="๐ฎ BesiegeField Machine Generator") as demo: |
| |
| |
| gr.HTML(""" |
| <div class="header-box"> |
| <h1 style="color: white;">๐ฎ BesiegeField Machine Generator</h1> |
| <p style="color: white; opacity: 0.9;">Generate your Besiege machine designs with AI</p> |
| <span class="badge" style="font-weight: bold;">โจ single-agent only</span> |
| <div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.3);"> |
| <p style="color: white; margin: 0 0 12px 0; font-size: 1em; text-align: center;">๐ <strong>Links</strong></p> |
| <div style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 10px;"> |
| <a href="https://besiegefield.github.io/" target="_blank" style="color: white; text-decoration: none; padding: 10px 20px; background: rgba(139, 126, 234, 0.5); border-radius: 20px; font-size: 0.95em; transition: all 0.3s; display: inline-flex; align-items: center; gap: 6px; font-weight: 500;" onmouseover="this.style.background='rgba(139, 126, 234, 0.7)'" onmouseout="this.style.background='rgba(139, 126, 234, 0.5)'"> |
| <span style="font-size: 1.1em;">๐</span> Project Page |
| </a> |
| <a href="https://github.com/Godheritage/BesiegeField" target="_blank" style="color: white; text-decoration: none; padding: 10px 20px; background: rgba(139, 126, 234, 0.5); border-radius: 20px; font-size: 0.95em; transition: all 0.3s; display: inline-flex; align-items: center; gap: 6px; font-weight: 500;" onmouseover="this.style.background='rgba(139, 126, 234, 0.7)'" onmouseout="this.style.background='rgba(139, 126, 234, 0.5)'"> |
| <svg style="width: 16px; height: 16px; flex-shrink: 0;" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg> |
| GitHub |
| </a> |
| <a href="https://arxiv.org/abs/2510.14980" target="_blank" style="color: white; text-decoration: none; padding: 10px 20px; background: rgba(139, 126, 234, 0.5); border-radius: 20px; font-size: 0.95em; transition: all 0.3s; display: inline-flex; align-items: center; gap: 6px; font-weight: 500;" onmouseover="this.style.background='rgba(139, 126, 234, 0.7)'" onmouseout="this.style.background='rgba(139, 126, 234, 0.5)'"> |
| <span style="font-size: 1.1em;">๐</span> arXiv Paper |
| </a> |
| </div> |
| </div> |
| </div> |
| """) |
| |
| with gr.Tabs(): |
| |
| with gr.Tab("AI Generation"): |
| with gr.Row(): |
| with gr.Column(scale=5): |
| gr.HTML(""" |
| <div class="info-box"> |
| <h4>๐ก How to Use</h4> |
| <ul> |
| <li>Describe the machine you want to create</li> |
| <li>Click "Generate Machine"</li> |
| <li>Wait for AI to generate (30-120 seconds)</li> |
| <li>Download the generated .bsg file</li> |
| <li>Put yout .bsg file into GameRoot/Besiege_Data/SavedMachines</li> |
| <li>Open the game, open costume scene, load machine</li> |
| <li>For detailed guidance, please refer to the ๐ Help Tutorial video</li> |
| <li>You can check our <a href="https://github.com/Godheritage/BesiegeField" target="_blank" style="color: #667eea; text-decoration: underline; font-weight: 500;">GitHub repo</a> for more LLMs and agentic workflows</li> |
| </ul> |
| </div> |
| """) |
| with gr.Column(scale=1): |
| help_video = gr.Video( |
| label="๐ Help Tutorial", |
| value="help.mp4" if os.path.exists("help.mp4") else None, |
| autoplay=False, |
| visible=os.path.exists("help.mp4") |
| ) |
| |
| with gr.Row(): |
| with gr.Column(): |
| user_input = gr.Textbox( |
| label="Describe Your Machine *", |
| placeholder="e.g., Create a four-wheeled vehicle with powered wheels...", |
| lines=5 |
| ) |
| |
| |
| if EXAMPLES: |
| gr.Examples( |
| examples=EXAMPLES, |
| inputs=user_input, |
| label="๐ก Example Prompts" |
| ) |
| |
| with gr.Accordion("โ๏ธ Advanced Settings", open=False): |
| temperature = gr.Slider( |
| minimum=0.1, |
| maximum=1.5, |
| value=0.7, |
| step=0.1, |
| label="Temperature", |
| info="Higher values produce more creative but less stable results" |
| ) |
| max_tokens = gr.Slider( |
| minimum=1024, |
| maximum=8192, |
| value=4096, |
| step=512, |
| label="Max Tokens", |
| info="Maximum length of generation" |
| ) |
| |
| generate_btn = gr.Button("๐ Generate Machine", variant="primary", size="lg") |
| |
| status_output = gr.Markdown(label="Status") |
| |
| with gr.Row(): |
| with gr.Column(): |
| ai_response = gr.Textbox( |
| label="AI Response (JSON)", |
| lines=10, |
| max_lines=20, |
| show_copy_button=True |
| ) |
| |
| with gr.Column(): |
| xml_output = gr.Textbox( |
| label="XML Output", |
| lines=10, |
| max_lines=20, |
| show_copy_button=True |
| ) |
| |
| download_btn = gr.File(label="๐ฅ Download .bsg File") |
| |
| |
| def generate_and_save(user_prompt, temp, max_tok): |
| ai_resp, xml_str, status = generate_machine(user_prompt, temp, max_tok) |
| file_path = save_xml_to_file(xml_str) if xml_str else None |
| return ai_resp, xml_str, status, file_path |
| |
| generate_btn.click( |
| fn=generate_and_save, |
| inputs=[user_input, temperature, max_tokens], |
| outputs=[ai_response, xml_output, status_output, download_btn] |
| ) |
| |
| |
| with gr.Tab("Manual Conversion"): |
| gr.HTML(""" |
| <div class="info-box"> |
| <h4>๐ก How to Use</h4> |
| <ul> |
| <li>Paste your JSON machine data</li> |
| <li>Click "Convert to XML"</li> |
| <li>Download the generated .bsg file</li> |
| </ul> |
| </div> |
| """) |
| |
| json_input = gr.Textbox( |
| label="JSON Input", |
| placeholder='Paste your JSON data here...', |
| lines=10, |
| max_lines=20 |
| ) |
| |
| convert_btn = gr.Button("๐ Convert to XML", variant="primary", size="lg") |
| |
| status_manual = gr.Markdown(label="Status") |
| |
| xml_output_manual = gr.Textbox( |
| label="XML Output", |
| lines=10, |
| max_lines=20, |
| show_copy_button=True |
| ) |
| |
| download_manual_btn = gr.File(label="๐ฅ Download .bsg File") |
| |
| |
| def convert_and_save(json_str): |
| xml_str, status = convert_json_to_xml(json_str) |
| file_path = save_xml_to_file(xml_str) if xml_str else None |
| return xml_str, status, file_path |
| |
| convert_btn.click( |
| fn=convert_and_save, |
| inputs=[json_input], |
| outputs=[xml_output_manual, status_manual, download_manual_btn] |
| ) |
| |
| |
| gr.HTML(f""" |
| <div class="footer-box"> |
| <p> |
| ๐ค Using <a href="https://huggingface.co/{MODEL_NAME}" target="_blank">{MODEL_NAME}</a> (HF Router) |
| </p> |
| <p> |
| โ ๏ธ Note: Generated machines may require adjustments in-game |
| </p> |
| <p style="color: #999;"> |
| ๐ก Free to use with HF Token | ๐ Get token: <a href="https://huggingface.co/settings/tokens" target="_blank">huggingface.co/settings/tokens</a> |
| </p> |
| <hr style="margin: 20px 0; border: none; border-top: 1px solid #e0e0e0;"> |
| <p style="color: #666; font-size: 0.95em; margin-bottom: 10px;"> |
| <strong>๐ Project Links:</strong> |
| </p> |
| <div style="display: flex; gap: 15px; justify-content: center; flex-wrap: wrap;"> |
| <a href="https://besiegefield.github.io/" target="_blank" style="color: #667eea; text-decoration: none; font-weight: 500;"> |
| ๐ Project Page |
| </a> |
| <span style="color: #ccc;">|</span> |
| <a href="https://github.com/Godheritage/BesiegeField" target="_blank" style="color: #667eea; text-decoration: none; font-weight: 500;"> |
| GitHub Repository |
| </a> |
| <span style="color: #ccc;">|</span> |
| <a href="https://arxiv.org/abs/2510.14980" target="_blank" style="color: #667eea; text-decoration: none; font-weight: 500;"> |
| ๐ arXiv: 2510.14980 |
| </a> |
| </div> |
| </div> |
| """) |
|
|
| |
| if __name__ == "__main__": |
| demo.launch() |
|
|