Godheritage commited on
Commit
a46c2ff
·
verified ·
1 Parent(s): c610b12

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +463 -306
app.py CHANGED
@@ -1,306 +1,463 @@
1
- import gradio as gr
2
- import os
3
- from huggingface_hub import InferenceClient
4
- from get_machine_from_json import string_to_bsg
5
- import json
6
-
7
- # 读取系统提示词
8
- with open('user_system_prompt.txt', 'r', encoding='utf-8') as f:
9
- SYSTEM_PROMPT = f.read()
10
-
11
- # 读取示例(如果存在)
12
- EXAMPLES = []
13
- try:
14
- with open('examples.json', 'r', encoding='utf-8') as f:
15
- examples_data = json.load(f)
16
- EXAMPLES = [[ex["description"]] for ex in examples_data.get("examples", [])]
17
- except:
18
- pass
19
-
20
- # 初始化 HF 客户端 - 使用 DeepSeek 免费模型
21
- def create_client():
22
- """创建 HF Inference 客户端"""
23
- return InferenceClient(
24
- model="deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
25
- token=os.environ.get("HF_TOKEN") # 从环境变量读取 token
26
- )
27
-
28
- def generate_machine(user_prompt, temperature=0.7, max_tokens=4096):
29
- """
30
- 使用 AI 生成机器设计的 JSON,然后转换为 XML
31
-
32
- Args:
33
- user_prompt: 用户输入的机器描述
34
- temperature: 生成温度
35
- max_tokens: 最大 token 数
36
-
37
- Returns:
38
- tuple: (ai_response, xml_string, status_message)
39
- """
40
- if not user_prompt.strip():
41
- return "", "", "❌ 请输入机器描述!"
42
-
43
- try:
44
- client = create_client()
45
-
46
- # 构建消息
47
- messages = [
48
- {"role": "system", "content": SYSTEM_PROMPT},
49
- {"role": "user", "content": user_prompt}
50
- ]
51
-
52
- # 调用 API
53
- response = ""
54
- for message in client.chat_completion(
55
- messages=messages,
56
- temperature=temperature,
57
- max_tokens=max_tokens,
58
- stream=True,
59
- ):
60
- if message.choices[0].delta.content:
61
- response += message.choices[0].delta.content
62
-
63
- # 尝试转换为 XML
64
- try:
65
- xml_string = string_to_bsg(response)
66
- status = "✅ 生成成功!可以下载 .bsg 文件了。"
67
- return response, xml_string, status
68
- except Exception as e:
69
- return response, "", f"⚠️ AI 生成完成,但转换 XML 时出错:{str(e)}"
70
-
71
- except Exception as e:
72
- return "", "", f"❌ 生成失败:{str(e)}"
73
-
74
- def convert_json_to_xml(json_input):
75
- """
76
- 手动转换 JSON 到 XML
77
-
78
- Args:
79
- json_input: JSON 字符串或 JSON 数据
80
-
81
- Returns:
82
- tuple: (xml_string, status_message)
83
- """
84
- if not json_input.strip():
85
- return "", "❌ 请输入 JSON 数据!"
86
-
87
- try:
88
- xml_string = string_to_bsg(json_input)
89
- return xml_string, "✅ 转换成功!"
90
- except Exception as e:
91
- return "", f"❌ 转换失败:{str(e)}"
92
-
93
- def save_xml_to_file(xml_content):
94
- """保存 XML .bsg 文件"""
95
- if not xml_content:
96
- return None
97
-
98
- output_path = "generated_machine.bsg"
99
- with open(output_path, 'w', encoding='utf-8') as f:
100
- f.write(xml_content)
101
- return output_path
102
-
103
- # 自定义 CSS 样式,参考 index.html
104
- custom_css = """
105
- .gradio-container {
106
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
107
- }
108
-
109
- .header {
110
- text-align: center;
111
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
112
- padding: 30px;
113
- border-radius: 10px;
114
- color: white;
115
- margin-bottom: 20px;
116
- }
117
-
118
- .header h1 {
119
- font-size: 2.5em;
120
- margin-bottom: 10px;
121
- }
122
-
123
- .info-box {
124
- background: #e7f3ff;
125
- border-left: 4px solid #2196F3;
126
- padding: 15px;
127
- margin-bottom: 20px;
128
- border-radius: 4px;
129
- }
130
-
131
- .success-box {
132
- background: #d4edda;
133
- color: #155724;
134
- padding: 15px;
135
- border-radius: 8px;
136
- border: 1px solid #c3e6cb;
137
- }
138
-
139
- .error-box {
140
- background: #f8d7da;
141
- color: #721c24;
142
- padding: 15px;
143
- border-radius: 8px;
144
- border: 1px solid #f5c6cb;
145
- }
146
- """
147
-
148
- # 创建 Gradio 界面
149
- with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="🎮 Besiege Machine Generator") as demo:
150
-
151
- # 头部
152
- gr.HTML("""
153
- <div class="header">
154
- <h1>🎮 Besiege Machine Generator</h1>
155
- <p style="font-size: 1.1em; opacity: 0.9;">使用 AI 生成你的 Besiege 机器设计</p>
156
- <span style="background: rgba(255, 255, 255, 0.2); padding: 5px 15px; border-radius: 20px; font-size: 0.9em;">
157
- ✨ Powered by DeepSeek AI
158
- </span>
159
- </div>
160
- """)
161
-
162
- with gr.Tabs():
163
- # Tab 1: AI 生成
164
- with gr.Tab("🤖 AI 生成"):
165
- gr.HTML("""
166
- <div class="info-box">
167
- <h4>💡 使用说明</h4>
168
- <ul>
169
- <li>描述你想要创建的机器</li>
170
- <li>点击"生成机器"按钮</li>
171
- <li>等待 AI 生成完成</li>
172
- <li>下载生成的 .bsg 文件</li>
173
- </ul>
174
- </div>
175
- """)
176
-
177
- with gr.Row():
178
- with gr.Column():
179
- user_input = gr.Textbox(
180
- label="描述你的机器 *",
181
- placeholder="例如:创建一个四轮车,能够向前移动...",
182
- lines=5
183
- )
184
-
185
- # 添加示例
186
- if EXAMPLES:
187
- gr.Examples(
188
- examples=EXAMPLES,
189
- inputs=user_input,
190
- label="💡 示例提示"
191
- )
192
-
193
- with gr.Accordion("⚙️ 高级设置", open=False):
194
- temperature = gr.Slider(
195
- minimum=0.1,
196
- maximum=1.5,
197
- value=0.7,
198
- step=0.1,
199
- label="Temperature(温度)",
200
- info="较高的值会产生更有创意但可能不太稳定的结果"
201
- )
202
- max_tokens = gr.Slider(
203
- minimum=1024,
204
- maximum=8192,
205
- value=4096,
206
- step=512,
207
- label="Max Tokens(最大令牌数)",
208
- info="生成的最大长度"
209
- )
210
-
211
- generate_btn = gr.Button("🚀 生成机器", variant="primary", size="lg")
212
-
213
- status_output = gr.Markdown(label="状态")
214
-
215
- with gr.Row():
216
- with gr.Column():
217
- ai_response = gr.Textbox(
218
- label="AI 响应(JSON)",
219
- lines=10,
220
- max_lines=20,
221
- show_copy_button=True
222
- )
223
-
224
- with gr.Column():
225
- xml_output = gr.Textbox(
226
- label="XML 输出",
227
- lines=10,
228
- max_lines=20,
229
- show_copy_button=True
230
- )
231
-
232
- download_btn = gr.File(label="📥 下载 .bsg 文件")
233
-
234
- # 绑定生成按钮
235
- def generate_and_save(user_prompt, temp, max_tok):
236
- ai_resp, xml_str, status = generate_machine(user_prompt, temp, max_tok)
237
- file_path = save_xml_to_file(xml_str) if xml_str else None
238
- return ai_resp, xml_str, status, file_path
239
-
240
- generate_btn.click(
241
- fn=generate_and_save,
242
- inputs=[user_input, temperature, max_tokens],
243
- outputs=[ai_response, xml_output, status_output, download_btn]
244
- )
245
-
246
- # Tab 2: 手动转换
247
- with gr.Tab("🔄 手动转换"):
248
- gr.HTML("""
249
- <div class="info-box">
250
- <h4>💡 使用说明</h4>
251
- <ul>
252
- <li>粘贴你的 JSON 机器数据</li>
253
- <li>点击"转换为 XML"按钮</li>
254
- <li>下载生成的 .bsg 文件</li>
255
- </ul>
256
- </div>
257
- """)
258
-
259
- json_input = gr.Textbox(
260
- label="JSON 输入",
261
- placeholder='粘贴你的 JSON 数据...',
262
- lines=10,
263
- max_lines=20
264
- )
265
-
266
- convert_btn = gr.Button("🔄 转换为 XML", variant="primary", size="lg")
267
-
268
- status_manual = gr.Markdown(label="状态")
269
-
270
- xml_output_manual = gr.Textbox(
271
- label="XML 输出",
272
- lines=10,
273
- max_lines=20,
274
- show_copy_button=True
275
- )
276
-
277
- download_manual_btn = gr.File(label="📥 下载 .bsg 文件")
278
-
279
- # 绑定转换按钮
280
- def convert_and_save(json_str):
281
- xml_str, status = convert_json_to_xml(json_str)
282
- file_path = save_xml_to_file(xml_str) if xml_str else None
283
- return xml_str, status, file_path
284
-
285
- convert_btn.click(
286
- fn=convert_and_save,
287
- inputs=[json_input],
288
- outputs=[xml_output_manual, status_manual, download_manual_btn]
289
- )
290
-
291
- # 底部信息
292
- gr.HTML("""
293
- <div style="text-align: center; margin-top: 30px; padding: 20px; background: #f8f9fa; border-radius: 10px;">
294
- <p style="color: #666; margin: 5px 0;">
295
- 🤖 使用 <a href="https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-8B" target="_blank">DeepSeek-R1-Distill-Llama-8B</a> 模型
296
- </p>
297
- <p style="color: #666; margin: 5px 0;">
298
- ⚠️ 注意:生成的机器可能需要在游戏中进行调整
299
- </p>
300
- </div>
301
- """)
302
-
303
- # 启动应用
304
- if __name__ == "__main__":
305
- demo.launch()
306
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ from huggingface_hub import InferenceClient
4
+ from get_machine_from_json import string_to_bsg
5
+ import json
6
+
7
+ # Read system prompt
8
+ with open('user_system_prompt.txt', 'r', encoding='utf-8') as f:
9
+ SYSTEM_PROMPT = f.read()
10
+
11
+ # Read examples (if exists)
12
+ EXAMPLES = []
13
+ try:
14
+ with open('examples.json', 'r', encoding='utf-8') as f:
15
+ examples_data = json.load(f)
16
+ EXAMPLES = [[ex["description"]] for ex in examples_data.get("examples", [])]
17
+ except:
18
+ pass
19
+
20
+ # Available AI models (priority order)
21
+ AVAILABLE_MODELS = [
22
+ "deepseek-ai/DeepSeek-V3.2-Exp", # DeepSeek V3 - Most powerful
23
+ "deepseek-ai/DeepSeek-V3.1",
24
+ "deepseek-ai/deepseek-llm-67b-chat", # DeepSeek alternative
25
+ "Qwen/Qwen2.5-72B-Instruct", # Qwen backup
26
+ ]
27
+
28
+ # Initialize HF client
29
+ def create_client():
30
+ """Create HF Inference Client"""
31
+ # Get token from environment variable
32
+ hf_token = os.environ.get("HF_TOKEN")
33
+
34
+ if not hf_token:
35
+ raise ValueError(
36
+ "⚠️ HF_TOKEN not set!\n\n"
37
+ "Please add your Hugging Face token in Space Settings:\n\n"
38
+ "1. Go to your Space Settings\n"
39
+ "2. Find 'Repository secrets' section\n"
40
+ "3. Click 'Add a secret'\n"
41
+ "4. Name: HF_TOKEN\n"
42
+ "5. Value: Your token (get from https://huggingface.co/settings/tokens)\n"
43
+ "6. Save and restart Space\n\n"
44
+ "The token should have READ permission and start with 'hf_'"
45
+ )
46
+
47
+ # Try models in order
48
+ for model in AVAILABLE_MODELS:
49
+ try:
50
+ return InferenceClient(model=model, token=hf_token)
51
+ except:
52
+ continue
53
+
54
+ # Fallback to first model
55
+ return InferenceClient(model=AVAILABLE_MODELS[0], token=hf_token)
56
+
57
+ def generate_machine(user_prompt, temperature=0.7, max_tokens=4096):
58
+ """
59
+ Generate machine design JSON using AI, then convert to XML
60
+
61
+ Args:
62
+ user_prompt: User's machine description
63
+ temperature: Generation temperature
64
+ max_tokens: Maximum tokens
65
+
66
+ Returns:
67
+ tuple: (ai_response, xml_string, status_message)
68
+ """
69
+ if not user_prompt.strip():
70
+ return "", "", "❌ Please enter a machine description!"
71
+
72
+ try:
73
+ client = create_client()
74
+
75
+ # Build messages
76
+ messages = [
77
+ {"role": "system", "content": SYSTEM_PROMPT},
78
+ {"role": "user", "content": user_prompt}
79
+ ]
80
+
81
+ # Try API call
82
+ response = ""
83
+ try:
84
+ for message in client.chat_completion(
85
+ messages=messages,
86
+ temperature=temperature,
87
+ max_tokens=max_tokens,
88
+ stream=True,
89
+ ):
90
+ if message.choices[0].delta.content:
91
+ response += message.choices[0].delta.content
92
+ except Exception as api_error:
93
+ error_msg = str(api_error)
94
+ if "401" in error_msg or "Unauthorized" in error_msg:
95
+ return "", "", (
96
+ "❌ Authentication Failed!\n\n"
97
+ "Please ensure HF_TOKEN is correctly set in Space Settings:\n\n"
98
+ "1. Visit https://huggingface.co/settings/tokens\n"
99
+ "2. Create a new token (Read permission)\n"
100
+ "3. Add in Space Settings → Repository secrets:\n"
101
+ " - Name: HF_TOKEN\n"
102
+ " - Value: your token\n"
103
+ "4. Restart Space\n\n"
104
+ f"Error details: {error_msg}"
105
+ )
106
+ raise
107
+
108
+ # Try to convert to XML
109
+ try:
110
+ xml_string = string_to_bsg(response)
111
+ status = "✅ Generation successful! You can now download the .bsg file."
112
+ return response, xml_string, status
113
+ except Exception as e:
114
+ return response, "", f"⚠️ AI generation completed, but XML conversion failed: {str(e)}"
115
+
116
+ except ValueError as e:
117
+ return "", "", str(e)
118
+ except Exception as e:
119
+ return "", "", f"❌ Generation failed: {str(e)}"
120
+
121
+ def convert_json_to_xml(json_input):
122
+ """
123
+ Manually convert JSON to XML
124
+
125
+ Args:
126
+ json_input: JSON string or JSON data
127
+
128
+ Returns:
129
+ tuple: (xml_string, status_message)
130
+ """
131
+ if not json_input.strip():
132
+ return "", "❌ Please enter JSON data!"
133
+
134
+ try:
135
+ xml_string = string_to_bsg(json_input)
136
+ return xml_string, "✅ Conversion successful!"
137
+ except Exception as e:
138
+ return "", f"❌ Conversion failed: {str(e)}"
139
+
140
+ def save_xml_to_file(xml_content):
141
+ """Save XML to .bsg file"""
142
+ if not xml_content:
143
+ return None
144
+
145
+ output_path = "generated_machine.bsg"
146
+ with open(output_path, 'w', encoding='utf-8') as f:
147
+ f.write(xml_content)
148
+ return output_path
149
+
150
+ # Custom CSS matching index.html
151
+ custom_css = """
152
+ * {
153
+ margin: 0;
154
+ padding: 0;
155
+ box-sizing: border-box;
156
+ }
157
+
158
+ .gradio-container {
159
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
160
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
161
+ min-height: 100vh;
162
+ }
163
+
164
+ .main {
165
+ max-width: 1200px !important;
166
+ margin: 20px auto !important;
167
+ background: white !important;
168
+ border-radius: 20px !important;
169
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3) !important;
170
+ overflow: hidden !important;
171
+ }
172
+
173
+ .header {
174
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
175
+ color: white;
176
+ padding: 30px;
177
+ text-align: center;
178
+ border-radius: 20px 20px 0 0;
179
+ margin-bottom: 20px;
180
+ }
181
+
182
+ .header h1 {
183
+ font-size: 2.5em;
184
+ margin-bottom: 10px;
185
+ }
186
+
187
+ .header p {
188
+ font-size: 1.1em;
189
+ opacity: 0.9;
190
+ }
191
+
192
+ .badge {
193
+ display: inline-block;
194
+ background: rgba(255, 255, 255, 0.2);
195
+ padding: 5px 15px;
196
+ border-radius: 20px;
197
+ font-size: 0.9em;
198
+ margin-top: 10px;
199
+ }
200
+
201
+ .info-box {
202
+ background: #e7f3ff;
203
+ border-left: 4px solid #2196F3;
204
+ padding: 15px;
205
+ margin-bottom: 20px;
206
+ border-radius: 4px;
207
+ }
208
+
209
+ .info-box h4 {
210
+ margin-bottom: 10px;
211
+ color: #1976D2;
212
+ }
213
+
214
+ .info-box ul {
215
+ margin-left: 20px;
216
+ line-height: 1.8;
217
+ }
218
+
219
+ textarea, input {
220
+ width: 100% !important;
221
+ padding: 12px 15px !important;
222
+ border: 2px solid #e0e0e0 !important;
223
+ border-radius: 8px !important;
224
+ font-size: 16px !important;
225
+ transition: border-color 0.3s !important;
226
+ }
227
+
228
+ textarea:focus, input:focus {
229
+ outline: none !important;
230
+ border-color: #667eea !important;
231
+ }
232
+
233
+ .gr-button {
234
+ padding: 15px 30px !important;
235
+ font-size: 16px !important;
236
+ font-weight: 600 !important;
237
+ border: none !important;
238
+ border-radius: 8px !important;
239
+ cursor: pointer !important;
240
+ transition: all 0.3s !important;
241
+ text-transform: uppercase !important;
242
+ letter-spacing: 0.5px !important;
243
+ }
244
+
245
+ .gr-button-primary {
246
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
247
+ color: white !important;
248
+ }
249
+
250
+ .gr-button-primary:hover {
251
+ transform: translateY(-2px) !important;
252
+ box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4) !important;
253
+ }
254
+
255
+ .gr-button-secondary {
256
+ background: #6c757d !important;
257
+ color: white !important;
258
+ }
259
+
260
+ .gr-button-secondary:hover {
261
+ background: #5a6268 !important;
262
+ transform: translateY(-2px) !important;
263
+ }
264
+
265
+ .output-section {
266
+ background: #f8f9fa;
267
+ border: 2px solid #e0e0e0;
268
+ border-radius: 8px;
269
+ padding: 20px;
270
+ margin-top: 15px;
271
+ }
272
+
273
+ .footer {
274
+ text-align: center;
275
+ margin-top: 30px;
276
+ padding: 20px;
277
+ background: #f8f9fa;
278
+ border-radius: 10px;
279
+ }
280
+
281
+ .footer p {
282
+ color: #666;
283
+ margin: 5px 0;
284
+ }
285
+
286
+ .tabs {
287
+ border-bottom: 2px solid #e0e0e0 !important;
288
+ }
289
+
290
+ .tab {
291
+ padding: 12px 24px !important;
292
+ font-size: 16px !important;
293
+ font-weight: 500 !important;
294
+ color: #666 !important;
295
+ border-bottom: 3px solid transparent !important;
296
+ transition: all 0.3s !important;
297
+ }
298
+
299
+ .tab-active {
300
+ color: #667eea !important;
301
+ border-bottom-color: #667eea !important;
302
+ }
303
+ """
304
+
305
+ # Create Gradio interface
306
+ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="🎮 Besiege Machine Generator") as demo:
307
+
308
+ # Header
309
+ gr.HTML("""
310
+ <div class="header">
311
+ <h1>🎮 Besiege Machine Generator</h1>
312
+ <p>Generate your Besiege machine designs with AI</p>
313
+ <span class="badge">✨ Powered by DeepSeek AI</span>
314
+ </div>
315
+ """)
316
+
317
+ with gr.Tabs():
318
+ # Tab 1: AI Generation
319
+ with gr.Tab("AI Generation"):
320
+ gr.HTML("""
321
+ <div class="info-box">
322
+ <h4>💡 How to Use</h4>
323
+ <ul>
324
+ <li>Describe the machine you want to create</li>
325
+ <li>Click "Generate Machine"</li>
326
+ <li>Wait for AI to generate (30-120 seconds)</li>
327
+ <li>Download the generated .bsg file</li>
328
+ </ul>
329
+ </div>
330
+ """)
331
+
332
+ with gr.Row():
333
+ with gr.Column():
334
+ user_input = gr.Textbox(
335
+ label="Describe Your Machine *",
336
+ placeholder="e.g., Create a four-wheeled vehicle with powered wheels...",
337
+ lines=5
338
+ )
339
+
340
+ # Add examples
341
+ if EXAMPLES:
342
+ gr.Examples(
343
+ examples=EXAMPLES,
344
+ inputs=user_input,
345
+ label="💡 Example Prompts"
346
+ )
347
+
348
+ with gr.Accordion("⚙️ Advanced Settings", open=False):
349
+ temperature = gr.Slider(
350
+ minimum=0.1,
351
+ maximum=1.5,
352
+ value=0.7,
353
+ step=0.1,
354
+ label="Temperature",
355
+ info="Higher values produce more creative but less stable results"
356
+ )
357
+ max_tokens = gr.Slider(
358
+ minimum=1024,
359
+ maximum=8192,
360
+ value=4096,
361
+ step=512,
362
+ label="Max Tokens",
363
+ info="Maximum length of generation"
364
+ )
365
+
366
+ generate_btn = gr.Button("🚀 Generate Machine", variant="primary", size="lg")
367
+
368
+ status_output = gr.Markdown(label="Status")
369
+
370
+ with gr.Row():
371
+ with gr.Column():
372
+ ai_response = gr.Textbox(
373
+ label="AI Response (JSON)",
374
+ lines=10,
375
+ max_lines=20,
376
+ show_copy_button=True
377
+ )
378
+
379
+ with gr.Column():
380
+ xml_output = gr.Textbox(
381
+ label="XML Output",
382
+ lines=10,
383
+ max_lines=20,
384
+ show_copy_button=True
385
+ )
386
+
387
+ download_btn = gr.File(label="📥 Download .bsg File")
388
+
389
+ # Bind generate button
390
+ def generate_and_save(user_prompt, temp, max_tok):
391
+ ai_resp, xml_str, status = generate_machine(user_prompt, temp, max_tok)
392
+ file_path = save_xml_to_file(xml_str) if xml_str else None
393
+ return ai_resp, xml_str, status, file_path
394
+
395
+ generate_btn.click(
396
+ fn=generate_and_save,
397
+ inputs=[user_input, temperature, max_tokens],
398
+ outputs=[ai_response, xml_output, status_output, download_btn]
399
+ )
400
+
401
+ # Tab 2: Manual Conversion
402
+ with gr.Tab("Manual Conversion"):
403
+ gr.HTML("""
404
+ <div class="info-box">
405
+ <h4>💡 How to Use</h4>
406
+ <ul>
407
+ <li>Paste your JSON machine data</li>
408
+ <li>Click "Convert to XML"</li>
409
+ <li>Download the generated .bsg file</li>
410
+ </ul>
411
+ </div>
412
+ """)
413
+
414
+ json_input = gr.Textbox(
415
+ label="JSON Input",
416
+ placeholder='Paste your JSON data here...',
417
+ lines=10,
418
+ max_lines=20
419
+ )
420
+
421
+ convert_btn = gr.Button("🔄 Convert to XML", variant="primary", size="lg")
422
+
423
+ status_manual = gr.Markdown(label="Status")
424
+
425
+ xml_output_manual = gr.Textbox(
426
+ label="XML Output",
427
+ lines=10,
428
+ max_lines=20,
429
+ show_copy_button=True
430
+ )
431
+
432
+ download_manual_btn = gr.File(label="📥 Download .bsg File")
433
+
434
+ # Bind convert button
435
+ def convert_and_save(json_str):
436
+ xml_str, status = convert_json_to_xml(json_str)
437
+ file_path = save_xml_to_file(xml_str) if xml_str else None
438
+ return xml_str, status, file_path
439
+
440
+ convert_btn.click(
441
+ fn=convert_and_save,
442
+ inputs=[json_input],
443
+ outputs=[xml_output_manual, status_manual, download_manual_btn]
444
+ )
445
+
446
+ # Footer
447
+ gr.HTML(f"""
448
+ <div class="footer">
449
+ <p>
450
+ 🤖 Using <a href="https://huggingface.co/{AVAILABLE_MODELS[0]}" target="_blank">{AVAILABLE_MODELS[0]}</a> model
451
+ </p>
452
+ <p>
453
+ ⚠️ Note: Generated machines may require adjustments in-game
454
+ </p>
455
+ <p style="color: #999; font-size: 0.9em;">
456
+ 💡 AI generation requires HF Token - Free to use
457
+ </p>
458
+ </div>
459
+ """)
460
+
461
+ # Launch app
462
+ if __name__ == "__main__":
463
+ demo.launch()