Godheritage's picture
Update app.py
904b866 verified
raw
history blame
20.3 kB
import gradio as gr
import os
from openai import OpenAI
from get_machine_from_json import string_to_bsg
import json
# Read system prompt
with open('user_system_prompt.txt', 'r', encoding='utf-8') as f:
SYSTEM_PROMPT = f.read()
# Read examples (if exists)
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 configuration - Using HF Router with DeepSeek
MODEL_NAME = "deepseek-ai/DeepSeek-V3.2-Exp"
HF_ROUTER_BASE_URL = "https://router.huggingface.co/v1"
# Initialize OpenAI-compatible client for HF Router
def create_client():
"""Create OpenAI-compatible client using HF Router"""
# Get token from environment variable
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()
# Build messages
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_prompt}
]
# Call HF Router API
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[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}"
)
raise
# Try to convert to XML
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 Exception as e:
return "", "", f"โŒ Generation failed: {str(e)}"
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 matching index.html style
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;
}
"""
# Create Gradio interface
with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="๐ŸŽฎ BesiegeField Machine Generator") as demo:
# Header
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">โœจ Powered by DeepSeek AI</span>
</div>
""")
with gr.Tabs():
# Tab 1: AI Generation
with gr.Tab("AI Generation"):
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>
</ul>
</div>
""")
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
)
# Add examples
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"
)
with gr.Row():
generate_btn = gr.Button("๐Ÿš€ Generate Machine", variant="primary", size="lg")
help_btn = gr.Button("๐Ÿ“– Help Video", variant="secondary")
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")
# Bind generate button
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
# Help Modal
help_modal = gr.HTML(visible=False, value="""
<div id="help-modal" style="
position: fixed;
z-index: 2000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
">
<div style="
background: white;
border-radius: 15px;
width: 90%;
max-width: 900px;
padding: 30px;
">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="color: #333; margin: 0;">๐Ÿ“– Help Tutorial</h2>
<button onclick="this.closest('#help-modal').style.display='none'" style="
background: none;
border: none;
font-size: 30px;
color: #666;
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 50%;
">&times;</button>
</div>
<div style="position: relative; width: 100%; padding-bottom: 56.25%; background: #000; border-radius: 10px; overflow: hidden;">
<video controls style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">
<source src="help.mp4" type="video/mp4">
Your browser does not support video playback.
</video>
</div>
</div>
</div>
""")
def show_help():
return gr.HTML(visible=True, value="""
<div id="help-modal" style="
position: fixed;
z-index: 2000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
">
<div style="
background: white;
border-radius: 15px;
width: 90%;
max-width: 900px;
padding: 30px;
">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="color: #333; margin: 0;">๐Ÿ“– Help Tutorial</h2>
<button onclick="this.closest('#help-modal').style.display='none'" style="
background: none;
border: none;
font-size: 30px;
color: #666;
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 50%;
">&times;</button>
</div>
<div style="position: relative; width: 100%; padding-bottom: 56.25%; background: #000; border-radius: 10px; overflow: hidden;">
<video controls autoplay style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">
<source src="help.mp4" type="video/mp4">
Your browser does not support video playback.
</video>
</div>
</div>
</div>
""")
help_btn.click(fn=show_help, outputs=help_modal)
generate_btn.click(
fn=generate_and_save,
inputs=[user_input, temperature, max_tokens],
outputs=[ai_response, xml_output, status_output, download_btn]
)
# Tab 2: Manual Conversion
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")
# Bind convert button
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]
)
# Footer
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>
</div>
""")
# Launch app
if __name__ == "__main__":
demo.launch()