|
|
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() |
|
|
|