theguywhosucks commited on
Commit
aba9687
Β·
verified Β·
1 Parent(s): 6568dcc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +454 -125
app.py CHANGED
@@ -1,142 +1,471 @@
1
  import gradio as gr
2
  from gradio_client import Client
3
- import threading
4
  import time
 
 
 
5
 
6
- # -------------------------
7
- # Backend info
8
- # -------------------------
9
- BACKENDS = {
10
- "Sandbox Backend 1": "ChocoLaboratory/SANDBOX_BACKEND",
11
- "Sandbox Backend 2": "ChocoLaboratory/SANDBOXBACKEND2"
12
- }
13
- backend_load = {name: 0 for name in BACKENDS.keys()}
14
- user_sandboxes = {}
15
- SANDBOX_TTL = 2*3600 # 2 hours
16
-
17
- # -------------------------
18
- # API functions
19
- # -------------------------
20
- def choose_backend():
21
- return min(backend_load, key=lambda k: backend_load[k])
22
-
23
- def launch_sandbox_api(code, requirements):
24
- backend_name = choose_backend()
25
- client = Client(BACKENDS[backend_name])
26
- try:
27
- sandbox_id = client.predict(code=code, requirements=requirements, api_name="/launch_sandbox")
28
- backend_load[backend_name] += 1
29
- user_sandboxes[sandbox_id] = {
30
- "backend": backend_name, "start_time": time.time(), "client": client
31
- }
32
- return sandbox_id, backend_name
33
- except Exception as e:
34
- return None, str(e)
35
-
36
- def fetch_logs_api(sandbox_id):
37
- info = user_sandboxes.get(sandbox_id)
38
- if not info:
39
- return "Sandbox not found"
40
- try:
41
- return info["client"].predict(api_name="/fetch_logs", sandbox_id=sandbox_id)
42
- except Exception as e:
43
- return str(e)
44
-
45
- def kill_sandbox_api(sandbox_id):
46
- info = user_sandboxes.get(sandbox_id)
47
- if not info:
48
- return "Sandbox not found"
49
- try:
50
- result = info["client"].predict(api_name="/kill_sandbox", sandbox_id=sandbox_id)
51
- backend_load[info["backend"]] -= 1
52
- del user_sandboxes[sandbox_id]
53
- return result
54
- except Exception as e:
55
- return str(e)
56
-
57
- def status_sandbox_api(sandbox_id):
58
- info = user_sandboxes.get(sandbox_id)
59
- if not info:
60
- return "Sandbox not found"
61
- elapsed = int(time.time() - info["start_time"])
62
- return f"Running for {elapsed} seconds"
63
-
64
- # -------------------------
65
- # Dark-themed CSS
66
- # -------------------------
67
- DARK_CSS = """
68
- body {
69
- background-color: #1e1e1e;
70
- color: #f0f0f0;
71
- }
72
- .sandbox-card {
73
- border-radius: 12px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  padding: 15px;
75
- margin: 10px;
76
- background-color: #2b2b2b;
77
- box-shadow: 0 4px 12px rgba(0,0,0,0.4);
 
 
 
78
  }
79
- .gr-button {
80
- background-color: #3a3a3a;
81
- color: #f0f0f0;
82
- border: 1px solid #555;
 
 
 
 
 
 
83
  }
84
- .gr-button:hover {
85
- background-color: #505050;
 
 
 
 
 
86
  }
87
- .gr-textbox, .gr-markdown {
88
- background-color: #1e1e1e;
89
- color: #f0f0f0;
90
- border: 1px solid #444;
 
 
 
91
  }
92
- """
93
 
94
- # -------------------------
95
- # Main Gradio UI
96
- # -------------------------
97
- with gr.Blocks(css=DARK_CSS) as demo:
98
- gr.Markdown("# πŸ›‘ Sandbox Dashboard")
 
 
 
 
 
 
99
 
100
- with gr.Row():
101
- code_input = gr.Textbox(label="Main Code", lines=10)
102
- req_input = gr.Textbox(label="requirements.txt", lines=5)
103
- launch_btn = gr.Button("Launch Sandbox")
104
- launch_status = gr.Textbox(label="Launch Status")
105
-
106
- sandbox_container = gr.Column()
107
-
108
- # -------------------------
109
- # Handlers for dynamic sandbox cards
110
- # -------------------------
111
- def on_launch(code, requirements):
112
- sandbox_id, backend_or_error = launch_sandbox_api(code, requirements)
113
- if not sandbox_id:
114
- return f"Error: {backend_or_error}"
115
-
116
- # Create a new card in the container
117
- with sandbox_container:
118
- with gr.Column(elem_classes="sandbox-card") as card:
119
- gr.Markdown(f"**Sandbox ID:** {sandbox_id}")
120
- gr.Markdown(f"**Backend:** {backend_or_error}")
121
- runtime_box = gr.Textbox(label="Runtime (sec)", value="0", interactive=False)
122
- logs_box = gr.Textbox(label="Logs", lines=10, interactive=False)
123
 
124
- with gr.Row():
125
- code_box = gr.Textbox(label="Main Code", value=code, lines=10, interactive=False)
126
- req_box = gr.Textbox(label="requirements.txt", value=requirements, lines=10, interactive=False)
 
127
 
128
- # Buttons
129
- logs_btn = gr.Button("Fetch Logs")
130
- kill_btn = gr.Button("Kill Sandbox")
131
- status_btn = gr.Button("Check Status")
132
 
133
- # Assign clicks using lambdas
134
- logs_btn.click(lambda sid=sandbox_id: fetch_logs_api(sid), inputs=[], outputs=logs_box)
135
- kill_btn.click(lambda sid=sandbox_id: kill_sandbox_api(sid), inputs=[], outputs=logs_box)
136
- status_btn.click(lambda sid=sandbox_id: status_sandbox_api(sid), inputs=[], outputs=runtime_box)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
- return f"Sandbox {sandbox_id} launched on {backend_or_error}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
- launch_btn.click(on_launch, inputs=[code_input, req_input], outputs=launch_status)
 
 
 
 
 
 
 
 
141
 
142
- demo.launch()
 
 
 
 
 
 
 
1
  import gradio as gr
2
  from gradio_client import Client
3
+ import json
4
  import time
5
+ import threading
6
+ import random
7
+ from datetime import datetime
8
 
9
+ # Backend engines
10
+ BACKEND_ENGINES = [
11
+ "ChocoLaboratory/SANDBOXBACKEND2",
12
+ "ChocoLaboratory/SANDBOX_BACKEND"
13
+ ]
14
+
15
+ class SandboxManager:
16
+ def __init__(self):
17
+ self.client = None
18
+ self.current_engine = None
19
+ self.is_connected = False
20
+ self.last_status = "Disconnected"
21
+
22
+ def select_random_engine(self):
23
+ """Randomly select an engine"""
24
+ self.current_engine = random.choice(BACKEND_ENGINES)
25
+ return self.current_engine
26
+
27
+ def connect(self):
28
+ engine = self.select_random_engine()
29
+ try:
30
+ self.client = Client(engine)
31
+ self.is_connected = True
32
+ return f"βœ… Connected to: {engine}", engine
33
+ except Exception as e:
34
+ self.is_connected = False
35
+ self.current_engine = None
36
+ return f"❌ Connection failed to {engine}: {str(e)}", "Not connected"
37
+
38
+ def launch_sandbox(self, main_py_code, requirements_txt):
39
+ if not self.is_connected:
40
+ return "❌ Not connected to backend. Please connect first.", ""
41
+
42
+ try:
43
+ result = self.client.predict(
44
+ code=main_py_code,
45
+ requirements=requirements_txt,
46
+ api_name="/launch_sandbox"
47
+ )
48
+ return f"πŸš€ Sandbox launched successfully on {self.current_engine}!\n\nResponse: {result}", self.get_status()
49
+ except Exception as e:
50
+ return f"❌ Launch failed: {str(e)}", ""
51
+
52
+ def fetch_logs(self):
53
+ if not self.is_connected:
54
+ return "❌ Not connected to backend"
55
+
56
+ try:
57
+ result = self.client.predict(api_name="/fetch_logs")
58
+ timestamp = datetime.now().strftime("%H:%M:%S")
59
+ return f"πŸ“‹ Logs fetched at {timestamp} from {self.current_engine}:\n\n{result}"
60
+ except Exception as e:
61
+ return f"❌ Failed to fetch logs: {str(e)}"
62
+
63
+ def kill_sandbox(self):
64
+ if not self.is_connected:
65
+ return "❌ Not connected to backend", ""
66
+
67
+ try:
68
+ result = self.client.predict(api_name="/kill_sandbox")
69
+ return f"πŸ›‘ Sandbox terminated on {self.current_engine}\n\nResponse: {result}", self.get_status()
70
+ except Exception as e:
71
+ return f"❌ Kill failed: {str(e)}", ""
72
+
73
+ def get_status(self):
74
+ if not self.is_connected:
75
+ return "πŸ”΄ Disconnected"
76
+
77
+ try:
78
+ result = self.client.predict(api_name="/status_sandbox")
79
+ status_emoji = "🟒" if "running" in str(result).lower() else "🟑"
80
+ return f"{status_emoji} Status: {result}"
81
+ except Exception as e:
82
+ return f"πŸ”΄ Status check failed: {str(e)}"
83
+
84
+ # Initialize sandbox manager
85
+ sandbox = SandboxManager()
86
+
87
+ # Sample code templates
88
+ SAMPLE_MAIN_PY = """#!/usr/bin/env python3
89
+ \"\"\"
90
+ Welcome to Sandbox Cloud IDE
91
+ Write your Python code here
92
+ \"\"\"
93
+
94
+ import os
95
+ import sys
96
+ from datetime import datetime
97
+
98
+ def main():
99
+ print("πŸš€ Sandbox Environment Started!")
100
+ print(f"⏰ Current time: {datetime.now()}")
101
+ print(f"🐍 Python version: {sys.version}")
102
+ print(f"πŸ“ Working directory: {os.getcwd()}")
103
+
104
+ # Your code here
105
+ print("\\n✨ Hello from the cloud sandbox! ✨")
106
+
107
+ # Example: Simple calculation
108
+ numbers = [1, 2, 3, 4, 5]
109
+ result = sum(numbers)
110
+ print(f"πŸ“Š Sum of {numbers} = {result}")
111
+
112
+ if __name__ == "__main__":
113
+ main()
114
+ """
115
+
116
+ SAMPLE_REQUIREMENTS_TXT = """# Python package dependencies
117
+ # Add one package per line
118
+
119
+ # Data science libraries
120
+ numpy>=1.21.0
121
+ pandas>=1.3.0
122
+
123
+ # Web frameworks
124
+ # flask>=2.0.0
125
+ # fastapi>=0.68.0
126
+
127
+ # Machine learning
128
+ # scikit-learn>=1.0.0
129
+ # tensorflow>=2.6.0
130
+
131
+ # Utilities
132
+ requests>=2.25.0
133
+ python-dateutil>=2.8.0
134
+
135
+ # Add your packages here...
136
+ """
137
+
138
+ # Custom CSS for modern IDE styling
139
+ custom_css = """
140
+ .gradio-container {
141
+ font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
142
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
143
+ min-height: 100vh;
144
+ }
145
+
146
+ .ide-container {
147
+ background: rgba(255, 255, 255, 0.98);
148
+ backdrop-filter: blur(20px);
149
+ border-radius: 20px;
150
+ padding: 25px;
151
+ margin: 20px;
152
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
153
+ }
154
+
155
+ .engine-display {
156
+ background: linear-gradient(135deg, #00c6ff 0%, #0072ff 100%);
157
+ color: white;
158
  padding: 15px;
159
+ border-radius: 12px;
160
+ text-align: center;
161
+ font-weight: 600;
162
+ margin: 10px 0;
163
+ font-family: 'JetBrains Mono', monospace;
164
+ box-shadow: 0 8px 20px rgba(0, 114, 255, 0.3);
165
  }
166
+
167
+ .status-card {
168
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
169
+ color: white;
170
+ padding: 18px;
171
+ border-radius: 15px;
172
+ margin: 10px 0;
173
+ font-weight: 600;
174
+ text-align: center;
175
+ box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);
176
  }
177
+
178
+ .code-editor {
179
+ border-radius: 12px !important;
180
+ border: 2px solid #e1e8ed !important;
181
+ font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', monospace !important;
182
+ background: #1e1e1e !important;
183
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1) !important;
184
  }
185
+
186
+ .requirements-editor {
187
+ border-radius: 12px !important;
188
+ border: 2px solid #e1e8ed !important;
189
+ font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', monospace !important;
190
+ background: #282828 !important;
191
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1) !important;
192
  }
 
193
 
194
+ .action-button {
195
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%) !important;
196
+ border: none !important;
197
+ border-radius: 12px !important;
198
+ padding: 14px 28px !important;
199
+ color: white !important;
200
+ font-weight: 600 !important;
201
+ transition: all 0.3s ease !important;
202
+ box-shadow: 0 6px 18px rgba(79, 172, 254, 0.4) !important;
203
+ font-size: 15px !important;
204
+ }
205
 
206
+ .action-button:hover {
207
+ transform: translateY(-3px) !important;
208
+ box-shadow: 0 12px 30px rgba(79, 172, 254, 0.6) !important;
209
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
+ .stop-button {
212
+ background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%) !important;
213
+ box-shadow: 0 6px 18px rgba(255, 107, 107, 0.4) !important;
214
+ }
215
 
216
+ .stop-button:hover {
217
+ box-shadow: 0 12px 30px rgba(255, 107, 107, 0.6) !important;
218
+ }
 
219
 
220
+ .logs-output {
221
+ background: #0d1117 !important;
222
+ color: #c9d1d9 !important;
223
+ border-radius: 12px !important;
224
+ font-family: 'JetBrains Mono', 'Fira Code', monospace !important;
225
+ font-size: 13px !important;
226
+ line-height: 1.6 !important;
227
+ border: 1px solid #30363d !important;
228
+ box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.3) !important;
229
+ }
230
+
231
+ .header-title {
232
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
233
+ -webkit-background-clip: text;
234
+ -webkit-text-fill-color: transparent;
235
+ background-clip: text;
236
+ font-size: 3rem;
237
+ font-weight: 900;
238
+ text-align: center;
239
+ margin-bottom: 10px;
240
+ text-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
241
+ }
242
 
243
+ .header-subtitle {
244
+ text-align: center;
245
+ color: rgba(255, 255, 255, 0.8);
246
+ font-size: 1.2rem;
247
+ margin-bottom: 40px;
248
+ font-weight: 300;
249
+ }
250
+
251
+ .ide-tabs {
252
+ border-radius: 15px;
253
+ overflow: hidden;
254
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
255
+ }
256
+
257
+ .file-icon {
258
+ font-size: 1.2em;
259
+ margin-right: 8px;
260
+ }
261
+
262
+ .section-header {
263
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
264
+ -webkit-background-clip: text;
265
+ -webkit-text-fill-color: transparent;
266
+ background-clip: text;
267
+ font-weight: 700;
268
+ font-size: 1.3rem;
269
+ margin: 20px 0 15px 0;
270
+ }
271
+ """
272
+
273
+ def auto_refresh_status():
274
+ return sandbox.get_status()
275
+
276
+ def auto_refresh_logs():
277
+ return sandbox.fetch_logs()
278
+
279
+ def load_sample_files():
280
+ """Load sample files into the editors"""
281
+ return SAMPLE_MAIN_PY, SAMPLE_REQUIREMENTS_TXT
282
+
283
+ # Build the IDE interface
284
+ with gr.Blocks(css=custom_css, title="Sandbox Cloud IDE", theme=gr.themes.Soft()) as demo:
285
+ gr.HTML("""
286
+ <div class="header-title">☁️ Sandbox Cloud IDE</div>
287
+ <div class="header-subtitle">Professional cloud development environment with dual-engine support</div>
288
+ """)
289
+
290
+ with gr.Row():
291
+ with gr.Column(scale=3):
292
+ # Engine & Connection Section
293
+ with gr.Group(elem_classes=["ide-container"]):
294
+ gr.HTML('<div class="section-header">πŸ”Œ Engine Connection</div>')
295
+
296
+ with gr.Row():
297
+ connect_btn = gr.Button("πŸ”„ Connect to Random Engine", variant="primary", elem_classes=["action-button"])
298
+ load_samples_btn = gr.Button("πŸ“„ Load Sample Files", elem_classes=["action-button"])
299
+
300
+ current_engine_display = gr.Textbox(
301
+ value="No engine selected",
302
+ label="Current Engine",
303
+ interactive=False,
304
+ elem_classes=["engine-display"]
305
+ )
306
+
307
+ connection_status = gr.Textbox(
308
+ value="πŸ”΄ Not connected",
309
+ label="Connection Status",
310
+ interactive=False,
311
+ elem_classes=["status-card"]
312
+ )
313
+
314
+ # IDE Section
315
+ with gr.Group(elem_classes=["ide-container"]):
316
+ gr.HTML('<div class="section-header">πŸ’» Cloud IDE</div>')
317
+
318
+ with gr.Tab("πŸ“„ main.py"):
319
+ main_py_editor = gr.Code(
320
+ value="# Write your Python code here\nprint('Hello, Sandbox Cloud!')",
321
+ label="main.py",
322
+ language="python",
323
+ lines=20,
324
+ elem_classes=["code-editor"],
325
+ show_label=False
326
+ )
327
+
328
+ with gr.Tab("πŸ“¦ requirements.txt"):
329
+ requirements_editor = gr.Textbox(
330
+ value="# Add your Python package dependencies here\n# One package per line\n",
331
+ label="requirements.txt",
332
+ lines=20,
333
+ elem_classes=["requirements-editor"],
334
+ show_label=False,
335
+ max_lines=20
336
+ )
337
+
338
+ # Control Buttons
339
+ with gr.Row():
340
+ launch_btn = gr.Button("πŸš€ Deploy & Run", variant="primary", elem_classes=["action-button"], size="lg")
341
+ kill_btn = gr.Button("πŸ›‘ Stop Sandbox", elem_classes=["action-button", "stop-button"], size="lg")
342
+
343
+ # Launch Output
344
+ launch_output = gr.Textbox(
345
+ label="Deployment Output",
346
+ lines=6,
347
+ interactive=False,
348
+ placeholder="Deployment status will appear here...",
349
+ elem_classes=["logs-output"]
350
+ )
351
+
352
+ with gr.Column(scale=1):
353
+ # Status Monitor
354
+ with gr.Group(elem_classes=["ide-container"]):
355
+ gr.HTML('<div class="section-header">πŸ“Š Status Monitor</div>')
356
+
357
+ status_display = gr.Textbox(
358
+ value="πŸ”΄ Disconnected",
359
+ label="Sandbox Status",
360
+ interactive=False,
361
+ elem_classes=["status-card"]
362
+ )
363
+
364
+ status_refresh_btn = gr.Button("πŸ”„ Refresh", elem_classes=["action-button"])
365
+
366
+ # Auto-refresh Settings
367
+ gr.Markdown("#### βš™οΈ Auto-Refresh")
368
+ auto_refresh_status_cb = gr.Checkbox(
369
+ label="Status (5s)",
370
+ value=False
371
+ )
372
+ auto_refresh_logs_cb = gr.Checkbox(
373
+ label="Logs (10s)",
374
+ value=False
375
+ )
376
+
377
+ # System Logs Section
378
+ with gr.Group(elem_classes=["ide-container"]):
379
+ gr.HTML('<div class="section-header">πŸ“‹ System Logs</div>')
380
+
381
+ with gr.Row():
382
+ fetch_logs_btn = gr.Button("πŸ“‹ Fetch Latest Logs", elem_classes=["action-button"])
383
+ gr.HTML('<div style="flex-grow: 1;"></div>') # Spacer
384
+
385
+ logs_display = gr.Textbox(
386
+ label="System Logs",
387
+ lines=15,
388
+ interactive=False,
389
+ elem_classes=["logs-output"],
390
+ placeholder="πŸ“‹ System logs will appear here...\n\nClick 'Fetch Latest Logs' to view output from your running code.",
391
+ show_label=False
392
+ )
393
+
394
+ # Event Handlers
395
+ connect_btn.click(
396
+ fn=sandbox.connect,
397
+ outputs=[connection_status, current_engine_display]
398
+ )
399
+
400
+ load_samples_btn.click(
401
+ fn=load_sample_files,
402
+ outputs=[main_py_editor, requirements_editor]
403
+ )
404
+
405
+ launch_btn.click(
406
+ fn=sandbox.launch_sandbox,
407
+ inputs=[main_py_editor, requirements_editor],
408
+ outputs=[launch_output, status_display]
409
+ )
410
+
411
+ kill_btn.click(
412
+ fn=sandbox.kill_sandbox,
413
+ outputs=[launch_output, status_display]
414
+ )
415
+
416
+ status_refresh_btn.click(
417
+ fn=sandbox.get_status,
418
+ outputs=[status_display]
419
+ )
420
+
421
+ fetch_logs_btn.click(
422
+ fn=sandbox.fetch_logs,
423
+ outputs=[logs_display]
424
+ )
425
+
426
+ # Auto-refresh Timers
427
+ status_timer = gr.Timer(5)
428
+ logs_timer = gr.Timer(10)
429
+
430
+ status_timer.tick(
431
+ fn=auto_refresh_status,
432
+ outputs=[status_display],
433
+ show_progress=False
434
+ )
435
+
436
+ logs_timer.tick(
437
+ fn=auto_refresh_logs,
438
+ outputs=[logs_display],
439
+ show_progress=False
440
+ )
441
+
442
+ # Timer Controls
443
+ auto_refresh_status_cb.change(
444
+ lambda enabled: gr.Timer(5 if enabled else None),
445
+ inputs=[auto_refresh_status_cb],
446
+ outputs=[status_timer]
447
+ )
448
+
449
+ auto_refresh_logs_cb.change(
450
+ lambda enabled: gr.Timer(10 if enabled else None),
451
+ inputs=[auto_refresh_logs_cb],
452
+ outputs=[logs_timer]
453
+ )
454
 
455
+ # Footer
456
+ gr.HTML("""
457
+ <div style="text-align: center; margin-top: 50px; color: rgba(255,255,255,0.7); font-size: 0.95rem;">
458
+ <p>☁️ Sandbox Cloud Service β€’ Dual-Engine Architecture β€’ Built with ❀️ by ChocoLaboratory</p>
459
+ <p style="font-size: 0.8rem; margin-top: 10px;">
460
+ Available Engines: SANDBOXBACKEND1, SANDBOXBACKEND2
461
+ </p>
462
+ </div>
463
+ """)
464
 
465
+ if __name__ == "__main__":
466
+ demo.launch(
467
+ share=True,
468
+ server_name="0.0.0.0",
469
+ server_port=7860,
470
+ show_error=True
471
+ )