DarkDriftz commited on
Commit
de878f4
·
verified ·
1 Parent(s): 175c150

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +147 -1202
app.py CHANGED
@@ -1,1212 +1,157 @@
1
- """
2
- HuggingChat MCP Importer - Gradio 6.0.2 MCP Server
3
- ===================================================
4
- A comprehensive MCP server built with Gradio SDK 6.0.2 for bulk importing
5
- MCP server URLs from .txt files into HuggingChat.
6
-
7
- Features:
8
- - Parse MCP URLs from various file formats
9
- - Validate MCP server endpoints
10
- - Generate HuggingChat-compatible configurations
11
- - Health check MCP servers
12
- - List popular MCP servers
13
-
14
- MCP Endpoint: /gradio_api/mcp/
15
- Transport: Streamable HTTP (Gradio 6.0+)
16
-
17
- Author: DarkDriftz
18
- Version: 2.0.0
19
- """
20
-
21
  import gradio as gr
22
- import httpx
23
- import json
24
- import asyncio
25
- import re
26
- import ssl
27
- from datetime import datetime
28
- from typing import Optional
29
- from urllib.parse import urlparse
30
- from concurrent.futures import ThreadPoolExecutor
31
-
32
- # ============================================================================
33
- # CONFIGURATION
34
- # ============================================================================
35
-
36
- APP_TITLE = "🔗 HuggingChat MCP Importer"
37
- APP_DESCRIPTION = """
38
- ### Bulk Import MCP Servers to HuggingChat
39
-
40
- This tool helps you parse, validate, and configure MCP server URLs for use with HuggingChat.
41
-
42
- **Supported File Formats:**
43
- - Plain URLs (one per line)
44
- - `Name: URL` format
45
- - `Name|URL` format
46
- - JSON arrays with optional headers
47
- - Comments with `#` or `//`
48
-
49
- **MCP Server Endpoint:** `/gradio_api/mcp/`
50
- """
51
-
52
- # Popular MCP servers database
53
- POPULAR_MCP_SERVERS = {
54
- "Official": [
55
- {"name": "Hugging Face", "url": "https://huggingface.co/mcp", "description": "Official HuggingFace MCP server"},
56
- {"name": "Anthropic MCP", "url": "https://mcp.anthropic.com", "description": "Anthropic's official MCP endpoint"},
57
- ],
58
- "Search & Research": [
59
- {"name": "Exa Search", "url": "https://mcp.exa.ai/mcp", "description": "AI-powered search engine"},
60
- {"name": "Tavily", "url": "https://mcp.tavily.com/mcp", "description": "Research and search API"},
61
- {"name": "Brave Search", "url": "https://mcp.brave.com/mcp", "description": "Privacy-focused search"},
62
- ],
63
- "Development": [
64
- {"name": "GitHub", "url": "https://mcp.github.com/mcp", "description": "GitHub integration"},
65
- {"name": "GitLab", "url": "https://mcp.gitlab.com/mcp", "description": "GitLab integration"},
66
- {"name": "Linear", "url": "https://mcp.linear.app/mcp", "description": "Project management"},
67
- ],
68
- "Productivity": [
69
- {"name": "Notion", "url": "https://mcp.notion.so/mcp", "description": "Notion workspace integration"},
70
- {"name": "Slack", "url": "https://mcp.slack.com/mcp", "description": "Slack messaging"},
71
- {"name": "Google Drive", "url": "https://mcp.google.com/drive/mcp", "description": "Google Drive access"},
72
- ],
73
- "AI & ML": [
74
- {"name": "Replicate", "url": "https://mcp.replicate.com/mcp", "description": "ML model hosting"},
75
- {"name": "OpenAI", "url": "https://mcp.openai.com/mcp", "description": "OpenAI integration"},
76
- {"name": "Stability AI", "url": "https://mcp.stability.ai/mcp", "description": "Image generation"},
77
- ],
78
- "Data & Analytics": [
79
- {"name": "Snowflake", "url": "https://mcp.snowflake.com/mcp", "description": "Data warehouse"},
80
- {"name": "BigQuery", "url": "https://mcp.google.com/bigquery/mcp", "description": "Google BigQuery"},
81
- {"name": "PostgreSQL", "url": "https://mcp.postgresql.org/mcp", "description": "PostgreSQL database"},
82
- ],
83
- }
84
-
85
- # ============================================================================
86
- # HELPER FUNCTIONS
87
- # ============================================================================
88
-
89
- def extract_name_from_url(url: str) -> str:
90
- """Extract a readable name from URL."""
91
- try:
92
- parsed = urlparse(url)
93
- hostname = parsed.hostname or ""
94
- path = parsed.path.strip("/")
95
-
96
- # Try to get name from path first
97
- if path and path != "mcp":
98
- path_parts = [p for p in path.split("/") if p and p != "mcp"]
99
- if path_parts:
100
- name = path_parts[0]
101
- name = re.sub(r'[-_]', ' ', name)
102
- return name.title()
103
-
104
- # Fall back to hostname
105
- if hostname:
106
- parts = hostname.replace("mcp.", "").replace(".mcp", "").split(".")
107
- if parts:
108
- name = parts[0]
109
- if name in ["www", "api"]:
110
- name = parts[1] if len(parts) > 1 else name
111
- return name.title()
112
-
113
- return "Unknown Server"
114
- except Exception:
115
- return "Unknown Server"
116
-
117
-
118
- def parse_txt_content(content: str) -> list:
119
- """Parse MCP URLs from text content supporting multiple formats."""
120
- servers = []
121
- lines = content.strip().split("\n")
122
-
123
- # Check if content is JSON
124
- stripped = content.strip()
125
- if stripped.startswith("[") or stripped.startswith("{"):
126
- try:
127
- data = json.loads(stripped)
128
- if isinstance(data, list):
129
- for item in data:
130
- if isinstance(item, str):
131
- servers.append({"name": extract_name_from_url(item), "url": item})
132
- elif isinstance(item, dict) and "url" in item:
133
- servers.append({
134
- "name": item.get("name", extract_name_from_url(item["url"])),
135
- "url": item["url"],
136
- "headers": item.get("headers", {})
137
- })
138
- return servers
139
- elif isinstance(data, dict) and "servers" in data:
140
- return parse_txt_content(json.dumps(data["servers"]))
141
- except json.JSONDecodeError:
142
- pass
143
-
144
- # Parse line by line
145
- for line in lines:
146
- line = line.strip()
147
-
148
- # Skip empty lines and comments
149
- if not line or line.startswith("#") or line.startswith("//"):
150
- continue
151
-
152
- # Format: Name: URL
153
- if ": " in line and not line.startswith("http"):
154
- parts = line.split(": ", 1)
155
- if len(parts) == 2 and parts[1].startswith("http"):
156
- servers.append({"name": parts[0].strip(), "url": parts[1].strip()})
157
- continue
158
-
159
- # Format: Name|URL
160
- if "|" in line:
161
- parts = line.split("|", 1)
162
- if len(parts) == 2:
163
- if parts[0].startswith("http"):
164
- servers.append({"name": parts[1].strip(), "url": parts[0].strip()})
165
- else:
166
- servers.append({"name": parts[0].strip(), "url": parts[1].strip()})
167
- continue
168
-
169
- # Plain URL
170
- if line.startswith("http://") or line.startswith("https://"):
171
- servers.append({"name": extract_name_from_url(line), "url": line})
172
-
173
- return servers
174
-
175
-
176
- async def validate_single_endpoint(url: str, timeout: float = 10.0) -> dict:
177
- """Validate a single MCP endpoint."""
178
- result = {
179
- "url": url,
180
- "valid": False,
181
- "status_code": None,
182
- "response_time_ms": None,
183
- "has_tools": False,
184
- "tool_count": 0,
185
- "error": None
186
- }
187
-
188
  try:
189
- start_time = datetime.now()
190
-
191
- async with httpx.AsyncClient(timeout=timeout, verify=True) as client:
192
- # Try MCP discovery endpoint
193
- response = await client.get(url)
194
-
195
- result["status_code"] = response.status_code
196
- result["response_time_ms"] = int((datetime.now() - start_time).total_seconds() * 1000)
197
-
198
- if response.status_code == 200:
199
- result["valid"] = True
200
-
201
- # Try to detect tools
202
- try:
203
- data = response.json()
204
- if isinstance(data, dict):
205
- if "tools" in data:
206
- result["has_tools"] = True
207
- result["tool_count"] = len(data["tools"])
208
- elif "capabilities" in data:
209
- result["has_tools"] = True
210
- except Exception:
211
- pass
212
- else:
213
- result["error"] = f"HTTP {response.status_code}"
214
-
215
- except httpx.TimeoutException:
216
- result["error"] = "Connection timeout"
217
- except httpx.ConnectError:
218
- result["error"] = "Connection failed"
219
- except ssl.SSLError as e:
220
- result["error"] = f"SSL error: {str(e)[:50]}"
221
  except Exception as e:
222
- result["error"] = str(e)[:100]
223
-
224
- return result
225
-
226
 
227
- def run_async(coro):
228
- """Run async coroutine in sync context."""
 
229
  try:
230
- loop = asyncio.get_event_loop()
231
- except RuntimeError:
232
- loop = asyncio.new_event_loop()
233
- asyncio.set_event_loop(loop)
234
- return loop.run_until_complete(coro)
235
-
236
-
237
- # ============================================================================
238
- # TOOL FUNCTIONS (These become MCP tools)
239
- # ============================================================================
240
-
241
- def parse_mcp_urls(
242
- file_content: str,
243
- output_format: str = "markdown"
244
- ) -> str:
245
- """
246
- Parse MCP server URLs from text content.
247
-
248
- Supports multiple formats:
249
- - Plain URLs (one per line)
250
- - Name: URL format
251
- - Name|URL format
252
- - JSON arrays
253
- - Comments with # or //
254
-
255
- Args:
256
- file_content: Text content containing MCP server URLs
257
- output_format: Output format - 'markdown' or 'json'
258
-
259
- Returns:
260
- Parsed servers in the requested format
261
- """
262
- if not file_content or not file_content.strip():
263
- return "❌ Error: No content provided. Please paste your MCP server URLs."
264
-
265
- servers = parse_txt_content(file_content)
266
-
267
- if not servers:
268
- return "❌ No valid MCP server URLs found in the content."
269
-
270
- if output_format == "json":
271
- return json.dumps(servers, indent=2)
272
-
273
- # Markdown output
274
- lines = [
275
- f"# 📋 Parsed MCP Servers",
276
- f"",
277
- f"**Found {len(servers)} server(s)**",
278
- f"",
279
- "| # | Name | URL |",
280
- "|---|------|-----|"
281
- ]
282
-
283
- for i, server in enumerate(servers, 1):
284
- name = server.get("name", "Unknown")
285
- url = server.get("url", "")
286
- lines.append(f"| {i} | {name} | `{url}` |")
287
-
288
- lines.extend([
289
- "",
290
- "---",
291
- "*Use 'Validate Servers' to check connectivity*"
292
- ])
293
-
294
- return "\n".join(lines)
295
-
296
-
297
- def validate_mcp_servers(
298
- urls_text: str,
299
- timeout_seconds: float = 10.0
300
- ) -> str:
301
- """
302
- Validate MCP server endpoints for connectivity and availability.
303
-
304
- Checks each URL for:
305
- - HTTP connectivity
306
- - Response time
307
- - Tool availability
308
- - SSL certificate validity
309
-
310
- Args:
311
- urls_text: Newline-separated list of MCP server URLs or parsed content
312
- timeout_seconds: Request timeout in seconds (default: 10)
313
-
314
- Returns:
315
- Validation results in markdown format
316
- """
317
- if not urls_text or not urls_text.strip():
318
- return "❌ Error: No URLs provided for validation."
319
-
320
- # Parse URLs
321
- servers = parse_txt_content(urls_text)
322
- if not servers:
323
- # Try treating as simple URL list
324
- urls = [u.strip() for u in urls_text.strip().split("\n") if u.strip().startswith("http")]
325
- servers = [{"name": extract_name_from_url(u), "url": u} for u in urls]
326
-
327
- if not servers:
328
- return "❌ No valid URLs found to validate."
329
-
330
- # Validate each server
331
- async def validate_all():
332
- tasks = [validate_single_endpoint(s["url"], timeout_seconds) for s in servers]
333
- return await asyncio.gather(*tasks)
334
-
335
- results = run_async(validate_all())
336
-
337
- # Build report
338
- valid_count = sum(1 for r in results if r["valid"])
339
-
340
- lines = [
341
- "# ✅ MCP Server Validation Results",
342
- "",
343
- f"**Validated: {valid_count}/{len(results)} servers online**",
344
- "",
345
- "| Status | Name | URL | Response Time | Tools |",
346
- "|--------|------|-----|---------------|-------|"
347
- ]
348
-
349
- for server, result in zip(servers, results):
350
- status = "✅" if result["valid"] else "❌"
351
- name = server.get("name", "Unknown")
352
- url = server.get("url", "")[:50] + "..." if len(server.get("url", "")) > 50 else server.get("url", "")
353
-
354
- if result["response_time_ms"]:
355
- response_time = f"{result['response_time_ms']}ms"
356
- else:
357
- response_time = result.get("error", "N/A")[:30]
358
-
359
- tools = f"{result['tool_count']}" if result["has_tools"] else "-"
360
-
361
- lines.append(f"| {status} | {name} | `{url}` | {response_time} | {tools} |")
362
-
363
- # Summary
364
- lines.extend([
365
- "",
366
- "## Summary",
367
- f"- ✅ Online: {valid_count}",
368
- f"- ❌ Offline: {len(results) - valid_count}",
369
- f"- ⏱️ Avg Response: {sum(r['response_time_ms'] or 0 for r in results) // max(valid_count, 1)}ms"
370
- ])
371
-
372
- return "\n".join(lines)
373
-
374
-
375
- def generate_huggingchat_config(
376
- urls_text: str,
377
- include_headers: bool = False
378
- ) -> str:
379
- """
380
- Generate HuggingChat-compatible MCP configuration JSON.
381
-
382
- Creates a JSON configuration that can be used with HuggingChat's
383
- MCP server settings or the MCP_SERVERS environment variable.
384
-
385
- Args:
386
- urls_text: Text content containing MCP server URLs
387
- include_headers: Include empty headers object for each server
388
-
389
- Returns:
390
- JSON configuration for HuggingChat
391
- """
392
- if not urls_text or not urls_text.strip():
393
- return "❌ Error: No URLs provided."
394
-
395
- servers = parse_txt_content(urls_text)
396
-
397
- if not servers:
398
- return "❌ No valid MCP server URLs found."
399
-
400
- # Build HuggingChat config
401
- config = {"mcpServers": {}}
402
-
403
- for server in servers:
404
- name = server.get("name", "unknown").lower().replace(" ", "-")
405
- # Ensure unique names
406
- base_name = name
407
- counter = 1
408
- while name in config["mcpServers"]:
409
- name = f"{base_name}-{counter}"
410
- counter += 1
411
-
412
- server_config = {"url": server.get("url", "")}
413
-
414
- if include_headers:
415
- server_config["headers"] = server.get("headers", {})
416
-
417
- config["mcpServers"][name] = server_config
418
-
419
- output = [
420
- "# 🔧 HuggingChat MCP Configuration",
421
- "",
422
- "## JSON Configuration",
423
- "```json",
424
- json.dumps(config, indent=2),
425
- "```",
426
- "",
427
- "## Environment Variable Format",
428
- "```bash",
429
- f'export MCP_SERVERS=\'{json.dumps(config["mcpServers"])}\'',
430
- "```",
431
- "",
432
- "## Instructions",
433
- "1. Go to https://huggingface.co/settings/mcp",
434
- "2. Add each server using the URLs above",
435
- "3. Or use the environment variable for self-hosted instances",
436
- "",
437
- f"**Total Servers: {len(servers)}**"
438
- ]
439
-
440
- return "\n".join(output)
441
-
442
-
443
- def health_check_servers(
444
- urls_text: str,
445
- check_ssl: bool = True,
446
- detailed: bool = False
447
- ) -> str:
448
- """
449
- Perform comprehensive health checks on MCP servers.
450
-
451
- Checks include:
452
- - HTTP/HTTPS connectivity
453
- - SSL certificate validation
454
- - Response time measurement
455
- - Endpoint availability
456
-
457
- Args:
458
- urls_text: Text content containing MCP server URLs
459
- check_ssl: Verify SSL certificates (default: True)
460
- detailed: Include detailed error information
461
-
462
- Returns:
463
- Health check report in markdown format
464
- """
465
- if not urls_text or not urls_text.strip():
466
- return "❌ Error: No URLs provided for health check."
467
-
468
- servers = parse_txt_content(urls_text)
469
- if not servers:
470
- urls = [u.strip() for u in urls_text.strip().split("\n") if u.strip().startswith("http")]
471
- servers = [{"name": extract_name_from_url(u), "url": u} for u in urls]
472
-
473
- if not servers:
474
- return "❌ No valid URLs found."
475
-
476
- async def check_health(url: str) -> dict:
477
- result = {
478
- "url": url,
479
- "status": "unknown",
480
- "http_ok": False,
481
- "ssl_ok": None,
482
- "response_time_ms": None,
483
- "error": None
484
- }
485
-
486
- try:
487
- start = datetime.now()
488
- async with httpx.AsyncClient(timeout=15.0, verify=check_ssl) as client:
489
- response = await client.get(url)
490
- result["response_time_ms"] = int((datetime.now() - start).total_seconds() * 1000)
491
- result["http_ok"] = response.status_code < 400
492
- result["ssl_ok"] = url.startswith("https://")
493
- result["status"] = "healthy" if result["http_ok"] else "unhealthy"
494
- except ssl.SSLError as e:
495
- result["status"] = "ssl_error"
496
- result["ssl_ok"] = False
497
- result["error"] = str(e)[:100] if detailed else "SSL certificate error"
498
- except Exception as e:
499
- result["status"] = "error"
500
- result["error"] = str(e)[:100] if detailed else "Connection failed"
501
-
502
- return result
503
-
504
- async def check_all():
505
- tasks = [check_health(s["url"]) for s in servers]
506
- return await asyncio.gather(*tasks)
507
-
508
- results = run_async(check_all())
509
-
510
- healthy = sum(1 for r in results if r["status"] == "healthy")
511
-
512
- lines = [
513
- "# 🏥 MCP Server Health Report",
514
- "",
515
- f"**Health Status: {healthy}/{len(results)} servers healthy**",
516
- f"*Checked at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')}*",
517
- "",
518
- "| Health | Server | SSL | Response | Details |",
519
- "|--------|--------|-----|----------|---------|"
520
- ]
521
-
522
- status_icons = {
523
- "healthy": "🟢",
524
- "unhealthy": "🟡",
525
- "ssl_error": "🔴",
526
- "error": "🔴",
527
- "unknown": "⚪"
528
- }
529
-
530
- for server, result in zip(servers, results):
531
- icon = status_icons.get(result["status"], "⚪")
532
- name = server.get("name", "Unknown")[:20]
533
- ssl_status = "✅" if result["ssl_ok"] else ("❌" if result["ssl_ok"] is False else "-")
534
- response = f"{result['response_time_ms']}ms" if result["response_time_ms"] else "-"
535
- details = result.get("error", "OK")[:30] if result.get("error") else "OK"
536
-
537
- lines.append(f"| {icon} | {name} | {ssl_status} | {response} | {details} |")
538
-
539
- lines.extend([
540
- "",
541
- "## Legend",
542
- "- 🟢 Healthy - Server responding normally",
543
- "- 🟡 Unhealthy - Server returned error status",
544
- "- 🔴 Error - Connection or SSL failure",
545
- ])
546
-
547
- return "\n".join(lines)
548
-
549
-
550
- def list_popular_servers(
551
- category: str = "All",
552
- output_format: str = "markdown"
553
- ) -> str:
554
- """
555
- List popular and recommended MCP servers.
556
-
557
- Provides a curated list of well-known MCP servers organized
558
- by category for easy discovery.
559
-
560
- Args:
561
- category: Filter by category (All, Official, Search, Development, etc.)
562
- output_format: Output format - 'markdown' or 'json'
563
-
564
- Returns:
565
- List of popular MCP servers
566
- """
567
- if category == "All":
568
- all_servers = []
569
- for cat, servers in POPULAR_MCP_SERVERS.items():
570
- for s in servers:
571
- all_servers.append({**s, "category": cat})
572
- servers = all_servers
573
- else:
574
- servers = POPULAR_MCP_SERVERS.get(category, [])
575
- servers = [{**s, "category": category} for s in servers]
576
-
577
- if not servers:
578
- available = ", ".join(POPULAR_MCP_SERVERS.keys())
579
- return f"❌ Category not found. Available: All, {available}"
580
-
581
- if output_format == "json":
582
- return json.dumps(servers, indent=2)
583
-
584
- lines = [
585
- "# 🌟 Popular MCP Servers",
586
- "",
587
- f"**Showing: {category}** ({len(servers)} servers)",
588
- ""
589
- ]
590
-
591
- if category == "All":
592
- for cat in POPULAR_MCP_SERVERS.keys():
593
- lines.append(f"## {cat}")
594
- lines.append("")
595
- lines.append("| Name | URL | Description |")
596
- lines.append("|------|-----|-------------|")
597
- for s in POPULAR_MCP_SERVERS[cat]:
598
- lines.append(f"| {s['name']} | `{s['url']}` | {s['description']} |")
599
- lines.append("")
600
- else:
601
- lines.append("| Name | URL | Description |")
602
- lines.append("|------|-----|-------------|")
603
- for s in servers:
604
- lines.append(f"| {s['name']} | `{s['url']}` | {s['description']} |")
605
-
606
- lines.extend([
607
- "",
608
- "---",
609
- "*Copy URLs and use 'Generate Config' to create HuggingChat configuration*"
610
- ])
611
-
612
- return "\n".join(lines)
613
-
614
-
615
- def create_sample_file() -> str:
616
- """
617
- Create a sample MCP URLs file with various formats.
618
-
619
- Generates example content showing all supported URL formats
620
- that can be used as a template.
621
-
622
- Returns:
623
- Sample file content with examples
624
- """
625
- sample = """# Sample MCP Server URLs File
626
- # ============================
627
- # This file demonstrates all supported formats
628
-
629
- # Format 1: Plain URLs (one per line)
630
- https://huggingface.co/mcp
631
- https://mcp.exa.ai/mcp
632
-
633
- # Format 2: Name: URL
634
- Tavily Search: https://mcp.tavily.com/mcp
635
- Brave Search: https://mcp.brave.com/mcp
636
-
637
- # Format 3: Name|URL (pipe-separated)
638
- GitHub|https://mcp.github.com/mcp
639
- Linear|https://mcp.linear.app/mcp
640
-
641
- # Format 4: JSON array (paste this separately)
642
- # [
643
- # {"name": "Custom Server", "url": "https://custom.example.com/mcp"},
644
- # {"name": "API Server", "url": "https://api.example.com/mcp", "headers": {"Authorization": "Bearer token"}}
645
- # ]
646
-
647
- // Double-slash comments also work
648
- // Add your own servers below:
649
-
650
- """
651
-
652
- output = [
653
- "# 📄 Sample MCP URLs File",
654
- "",
655
- "Copy the content below and modify for your needs:",
656
- "",
657
- "```txt",
658
- sample,
659
- "```",
660
- "",
661
- "## Supported Formats",
662
- "1. **Plain URL**: `https://example.com/mcp`",
663
- "2. **Named**: `Server Name: https://example.com/mcp`",
664
- "3. **Pipe-separated**: `Server Name|https://example.com/mcp`",
665
- "4. **JSON**: `[{\"name\": \"...\", \"url\": \"...\"}]`",
666
- "",
667
- "## Tips",
668
- "- Use `#` or `//` for comments",
669
- "- Names are auto-extracted if not provided",
670
- "- JSON format supports custom headers"
671
- ]
672
-
673
- return "\n".join(output)
674
-
675
-
676
- def convert_to_claude_desktop_config(
677
- urls_text: str
678
- ) -> str:
679
- """
680
- Convert MCP URLs to Claude Desktop configuration format.
681
-
682
- Generates JSON configuration compatible with Claude Desktop's
683
- MCP server settings.
684
-
685
- Args:
686
- urls_text: Text content containing MCP server URLs
687
-
688
- Returns:
689
- Claude Desktop compatible JSON configuration
690
- """
691
- if not urls_text or not urls_text.strip():
692
- return "❌ Error: No URLs provided."
693
-
694
- servers = parse_txt_content(urls_text)
695
-
696
- if not servers:
697
- return "❌ No valid MCP server URLs found."
698
-
699
- # Claude Desktop uses streamable HTTP format
700
- config = {"mcpServers": {}}
701
-
702
- for server in servers:
703
- name = server.get("name", "unknown").lower().replace(" ", "-")
704
- name = re.sub(r'[^a-z0-9-]', '', name)
705
-
706
- base_name = name
707
- counter = 1
708
- while name in config["mcpServers"]:
709
- name = f"{base_name}-{counter}"
710
- counter += 1
711
-
712
- # Claude Desktop format for remote MCP servers
713
- config["mcpServers"][name] = {
714
- "command": "npx",
715
- "args": ["mcp-remote", server.get("url", "")]
716
- }
717
-
718
- output = [
719
- "# 🖥️ Claude Desktop MCP Configuration",
720
- "",
721
- "Add this to your Claude Desktop config file:",
722
- "- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`",
723
- "- **Windows**: `%APPDATA%\\Claude\\claude_desktop_config.json`",
724
- "",
725
- "```json",
726
- json.dumps(config, indent=2),
727
- "```",
728
- "",
729
- "## Requirements",
730
- "- Node.js must be installed",
731
- "- `npx` must be available in PATH",
732
- "",
733
- f"**Total Servers: {len(servers)}**"
734
- ]
735
-
736
- return "\n".join(output)
737
-
738
-
739
- def detect_duplicate_servers(
740
- imported_config: str,
741
- new_servers_txt: str,
742
- match_mode: str = "url"
743
- ) -> str:
744
- """
745
- Detect duplicate MCP servers by comparing imported servers with a txt file.
746
-
747
- Compares your currently imported HuggingChat MCP servers against servers
748
- in a txt file to identify duplicates, new servers, and existing-only servers.
749
-
750
- Args:
751
- imported_config: Your current HuggingChat MCP config (JSON) or list of URLs
752
- new_servers_txt: Text file content with MCP servers to compare
753
- match_mode: How to match servers - 'url' (exact URL), 'domain' (same domain), or 'name' (by name)
754
-
755
- Returns:
756
- Comparison report showing duplicates, new servers, and recommendations
757
- """
758
- if not imported_config or not imported_config.strip():
759
- return "❌ Error: Please paste your current HuggingChat MCP configuration or list of imported server URLs."
760
-
761
- if not new_servers_txt or not new_servers_txt.strip():
762
- return "❌ Error: Please provide the txt file content with servers to compare."
763
-
764
- # Parse the imported config
765
- imported_servers = []
766
- stripped_config = imported_config.strip()
767
-
768
- # Try parsing as JSON config first
769
- if stripped_config.startswith("{") or stripped_config.startswith("["):
770
- try:
771
- data = json.loads(stripped_config)
772
-
773
- # Handle HuggingChat mcpServers format
774
- if isinstance(data, dict):
775
- if "mcpServers" in data:
776
- for name, config in data["mcpServers"].items():
777
- url = config.get("url", "") if isinstance(config, dict) else ""
778
- imported_servers.append({"name": name, "url": url})
779
- elif all(isinstance(v, dict) for v in data.values()):
780
- # Direct server dict format
781
- for name, config in data.items():
782
- url = config.get("url", "") if isinstance(config, dict) else ""
783
- imported_servers.append({"name": name, "url": url})
784
- elif isinstance(data, list):
785
- for item in data:
786
- if isinstance(item, str):
787
- imported_servers.append({"name": extract_name_from_url(item), "url": item})
788
- elif isinstance(item, dict) and "url" in item:
789
- imported_servers.append({
790
- "name": item.get("name", extract_name_from_url(item["url"])),
791
- "url": item["url"]
792
- })
793
- except json.JSONDecodeError:
794
- pass
795
-
796
- # If JSON parsing didn't work, try line-by-line parsing
797
- if not imported_servers:
798
- imported_servers = parse_txt_content(imported_config)
799
-
800
- if not imported_servers:
801
- return "❌ Could not parse your imported servers. Please paste either:\n- JSON config from HuggingChat\n- List of URLs (one per line)\n- Name: URL format"
802
-
803
- # Parse the new servers from txt file
804
- new_servers = parse_txt_content(new_servers_txt)
805
-
806
- if not new_servers:
807
- return "❌ Could not parse any servers from the txt file content."
808
-
809
- # Normalize URLs for comparison
810
- def normalize_url(url: str) -> str:
811
- """Normalize URL for comparison."""
812
- url = url.lower().strip()
813
- url = re.sub(r'^https?://', '', url)
814
- url = re.sub(r'/$', '', url)
815
- return url
816
-
817
- def get_domain(url: str) -> str:
818
- """Extract domain from URL."""
819
- try:
820
- parsed = urlparse(url)
821
- return (parsed.hostname or "").lower()
822
- except:
823
- return ""
824
-
825
- # Build lookup sets based on match mode
826
- imported_urls = {normalize_url(s["url"]) for s in imported_servers if s.get("url")}
827
- imported_domains = {get_domain(s["url"]) for s in imported_servers if s.get("url")}
828
- imported_names = {s["name"].lower().strip() for s in imported_servers if s.get("name")}
829
-
830
- # Categorize new servers
831
- duplicates = []
832
- new_to_import = []
833
-
834
- for server in new_servers:
835
- url = server.get("url", "")
836
- name = server.get("name", "")
837
- normalized = normalize_url(url)
838
- domain = get_domain(url)
839
 
840
- is_duplicate = False
841
- match_reason = ""
842
 
843
- if match_mode == "url":
844
- if normalized in imported_urls:
845
- is_duplicate = True
846
- match_reason = "exact URL match"
847
- elif match_mode == "domain":
848
- if domain and domain in imported_domains:
849
- is_duplicate = True
850
- match_reason = f"same domain ({domain})"
851
- elif match_mode == "name":
852
- if name.lower().strip() in imported_names:
853
- is_duplicate = True
854
- match_reason = "same name"
855
 
856
- if is_duplicate:
857
- duplicates.append({**server, "match_reason": match_reason})
858
- else:
859
- new_to_import.append(server)
860
-
861
- # Find servers in imported but not in new list
862
- new_urls = {normalize_url(s["url"]) for s in new_servers if s.get("url")}
863
- only_in_imported = [s for s in imported_servers if normalize_url(s.get("url", "")) not in new_urls]
864
-
865
- # Build report
866
- lines = [
867
- "# 🔍 Duplicate Detection Report",
868
- "",
869
- f"**Match Mode:** {match_mode.title()}",
870
- f"**Imported Servers:** {len(imported_servers)}",
871
- f"**Servers in TXT File:** {len(new_servers)}",
872
- "",
873
- "---",
874
- ""
875
- ]
876
-
877
- # Duplicates section
878
- if duplicates:
879
- lines.extend([
880
- f"## ⚠️ Already Imported ({len(duplicates)})",
881
- "",
882
- "These servers are already in your HuggingChat and **don't need to be imported again**:",
883
- "",
884
- "| Server | URL | Match Reason |",
885
- "|--------|-----|--------------|"
886
- ])
887
- for s in duplicates:
888
- name = s.get("name", "Unknown")[:25]
889
- url = s.get("url", "")[:40] + ("..." if len(s.get("url", "")) > 40 else "")
890
- reason = s.get("match_reason", "")
891
- lines.append(f"| {name} | `{url}` | {reason} |")
892
- lines.append("")
893
- else:
894
- lines.extend([
895
- "## ✅ No Duplicates Found",
896
- "",
897
- "None of the servers in the txt file are already imported.",
898
- ""
899
- ])
900
-
901
- # New servers section
902
- if new_to_import:
903
- lines.extend([
904
- f"## 🆕 New Servers to Import ({len(new_to_import)})",
905
- "",
906
- "These servers are **not yet imported** and can be added:",
907
- "",
908
- "| Server | URL |",
909
- "|--------|-----|"
910
- ])
911
- for s in new_to_import:
912
- name = s.get("name", "Unknown")[:25]
913
- url = s.get("url", "")[:50] + ("..." if len(s.get("url", "")) > 50 else "")
914
- lines.append(f"| {name} | `{url}` |")
915
- lines.append("")
916
-
917
- # Generate import config for new servers only
918
- lines.extend([
919
- "### Quick Import Config (New Servers Only)",
920
- "",
921
- "```json",
922
- json.dumps({
923
- "mcpServers": {
924
- s.get("name", "unknown").lower().replace(" ", "-"): {"url": s.get("url", "")}
925
- for s in new_to_import
926
- }
927
- }, indent=2),
928
- "```",
929
- ""
930
- ])
931
- else:
932
- lines.extend([
933
- "## ℹ️ No New Servers",
934
- "",
935
- "All servers in the txt file are already imported.",
936
- ""
937
- ])
938
-
939
- # Only in imported section
940
- if only_in_imported:
941
- lines.extend([
942
- f"## 📋 Only in HuggingChat ({len(only_in_imported)})",
943
- "",
944
- "These servers are imported but **not in your txt file** (you may want to add them):",
945
- "",
946
- "| Server | URL |",
947
- "|--------|-----|"
948
- ])
949
- for s in only_in_imported[:10]: # Limit to 10
950
- name = s.get("name", "Unknown")[:25]
951
- url = s.get("url", "")[:50] + ("..." if len(s.get("url", "")) > 50 else "")
952
- lines.append(f"| {name} | `{url}` |")
953
- if len(only_in_imported) > 10:
954
- lines.append(f"| ... | *and {len(only_in_imported) - 10} more* |")
955
- lines.append("")
956
-
957
- # Summary
958
- lines.extend([
959
- "---",
960
- "## 📊 Summary",
961
- "",
962
- f"- ⚠️ **Duplicates (skip):** {len(duplicates)}",
963
- f"- 🆕 **New (import these):** {len(new_to_import)}",
964
- f"- 📋 **Only in HuggingChat:** {len(only_in_imported)}",
965
- "",
966
- "*Use the 'Generate Config' tab with the new servers list to create an import configuration.*"
967
- ])
968
-
969
- return "\n".join(lines)
970
-
971
-
972
- # ============================================================================
973
- # GRADIO INTERFACE DEFINITIONS
974
- # ============================================================================
975
-
976
- # Create individual interfaces for each tool
977
- parse_interface = gr.Interface(
978
- fn=parse_mcp_urls,
979
- inputs=[
980
- gr.Textbox(
981
- label="MCP URLs Content",
982
- placeholder="Paste your MCP server URLs here...\n\nSupported formats:\n- https://example.com/mcp\n- Server Name: https://example.com/mcp\n- Server Name|https://example.com/mcp",
983
- lines=10
984
- ),
985
- gr.Radio(
986
- choices=["markdown", "json"],
987
- value="markdown",
988
- label="Output Format"
989
- )
990
- ],
991
- outputs=gr.Markdown(label="Parsed Results"),
992
- api_name="parse_mcp_urls",
993
- title="📋 Parse MCP URLs",
994
- description="Parse MCP server URLs from various text formats"
995
- )
996
-
997
- validate_interface = gr.Interface(
998
- fn=validate_mcp_servers,
999
- inputs=[
1000
- gr.Textbox(
1001
- label="MCP URLs to Validate",
1002
- placeholder="Paste URLs to validate (one per line or parsed format)",
1003
- lines=8
1004
- ),
1005
- gr.Slider(
1006
- minimum=5,
1007
- maximum=30,
1008
- value=10,
1009
- step=1,
1010
- label="Timeout (seconds)"
1011
- )
1012
- ],
1013
- outputs=gr.Markdown(label="Validation Results"),
1014
- api_name="validate_mcp_servers",
1015
- title="✅ Validate MCP Servers",
1016
- description="Check connectivity and availability of MCP endpoints"
1017
- )
1018
-
1019
- generate_config_interface = gr.Interface(
1020
- fn=generate_huggingchat_config,
1021
- inputs=[
1022
- gr.Textbox(
1023
- label="MCP URLs",
1024
- placeholder="Paste MCP server URLs to generate config",
1025
- lines=8
1026
- ),
1027
- gr.Checkbox(
1028
- label="Include Headers Template",
1029
- value=False
1030
- )
1031
- ],
1032
- outputs=gr.Markdown(label="HuggingChat Configuration"),
1033
- api_name="generate_huggingchat_config",
1034
- title="🔧 Generate HuggingChat Config",
1035
- description="Create HuggingChat-compatible MCP configuration"
1036
- )
1037
-
1038
- health_check_interface = gr.Interface(
1039
- fn=health_check_servers,
1040
- inputs=[
1041
- gr.Textbox(
1042
- label="MCP URLs",
1043
- placeholder="Paste URLs for health check",
1044
- lines=8
1045
- ),
1046
- gr.Checkbox(
1047
- label="Verify SSL Certificates",
1048
- value=True
1049
- ),
1050
- gr.Checkbox(
1051
- label="Show Detailed Errors",
1052
- value=False
1053
- )
1054
- ],
1055
- outputs=gr.Markdown(label="Health Report"),
1056
- api_name="health_check_mcp_servers",
1057
- title="🏥 Health Check",
1058
- description="Comprehensive health monitoring for MCP servers"
1059
- )
1060
-
1061
- popular_servers_interface = gr.Interface(
1062
- fn=list_popular_servers,
1063
- inputs=[
1064
- gr.Dropdown(
1065
- choices=["All", "Official", "Search & Research", "Development", "Productivity", "AI & ML", "Data & Analytics"],
1066
- value="All",
1067
- label="Category"
1068
- ),
1069
- gr.Radio(
1070
- choices=["markdown", "json"],
1071
- value="markdown",
1072
- label="Output Format"
1073
- )
1074
- ],
1075
- outputs=gr.Markdown(label="Popular Servers"),
1076
- api_name="list_popular_mcp_servers",
1077
- title="🌟 Popular MCP Servers",
1078
- description="Discover curated MCP servers by category"
1079
- )
1080
-
1081
- sample_file_interface = gr.Interface(
1082
- fn=create_sample_file,
1083
- inputs=[],
1084
- outputs=gr.Markdown(label="Sample File"),
1085
- api_name="create_sample_urls_file",
1086
- title="📄 Sample URLs File",
1087
- description="Generate a template file with example formats"
1088
- )
1089
-
1090
- claude_config_interface = gr.Interface(
1091
- fn=convert_to_claude_desktop_config,
1092
- inputs=[
1093
- gr.Textbox(
1094
- label="MCP URLs",
1095
- placeholder="Paste MCP server URLs",
1096
- lines=8
1097
- )
1098
- ],
1099
- outputs=gr.Markdown(label="Claude Desktop Config"),
1100
- api_name="convert_to_claude_desktop",
1101
- title="🖥️ Claude Desktop Config",
1102
- description="Generate Claude Desktop MCP configuration"
1103
- )
1104
-
1105
- duplicate_detection_interface = gr.Interface(
1106
- fn=detect_duplicate_servers,
1107
- inputs=[
1108
- gr.Textbox(
1109
- label="Your Imported Servers (from HuggingChat)",
1110
- placeholder='Paste your current HuggingChat MCP config here...\n\nAccepted formats:\n1. JSON config: {"mcpServers": {"server-name": {"url": "..."}}}\n2. URL list (one per line)\n3. Name: URL format\n\nYou can export this from HuggingChat settings or paste your server URLs.',
1111
- lines=10
1112
- ),
1113
- gr.Textbox(
1114
- label="Servers from TXT File (to compare)",
1115
- placeholder="Paste the content of your servers.txt file here...\n\nThese are the servers you want to check for duplicates.",
1116
- lines=10
1117
- ),
1118
- gr.Radio(
1119
- choices=["url", "domain", "name"],
1120
- value="url",
1121
- label="Match Mode",
1122
- info="URL: exact match | Domain: same domain | Name: same server name"
1123
- )
1124
- ],
1125
- outputs=gr.Markdown(label="Duplicate Detection Results"),
1126
- api_name="detect_duplicate_servers",
1127
- title="🔍 Detect Duplicates",
1128
- description="Compare your imported HuggingChat servers with a txt file to find duplicates"
1129
- )
1130
-
1131
- # ============================================================================
1132
- # MAIN APPLICATION
1133
- # ============================================================================
1134
-
1135
- # Create tabbed interface combining all tools
1136
- demo = gr.TabbedInterface(
1137
- interface_list=[
1138
- parse_interface,
1139
- validate_interface,
1140
- generate_config_interface,
1141
- duplicate_detection_interface,
1142
- health_check_interface,
1143
- popular_servers_interface,
1144
- sample_file_interface,
1145
- claude_config_interface
1146
- ],
1147
- tab_names=[
1148
- "📋 Parse",
1149
- "✅ Validate",
1150
- "🔧 HuggingChat Config",
1151
- "🔍 Detect Duplicates",
1152
- "🏥 Health Check",
1153
- "🌟 Popular Servers",
1154
- "📄 Sample File",
1155
- "🖥️ Claude Desktop"
1156
- ],
1157
- title=APP_TITLE
1158
- )
1159
-
1160
- # Add custom CSS and header
1161
- with gr.Blocks(title=APP_TITLE) as app:
1162
- gr.Markdown(APP_DESCRIPTION)
1163
-
1164
- # Embed the tabbed interface
1165
- demo.render()
1166
-
1167
- gr.Markdown("""
1168
- ---
1169
- ### 🔗 MCP Server Information
1170
-
1171
- **MCP Endpoint:** `/gradio_api/mcp/`
1172
-
1173
- **For Claude Desktop / Cursor / Windsurf:**
1174
- ```json
1175
- {
1176
- "mcpServers": {
1177
- "huggingchat-importer": {
1178
- "url": "http://localhost:7860/gradio_api/mcp/"
1179
- }
1180
- }
1181
- }
1182
- ```
1183
-
1184
- **For clients without Streamable HTTP support:**
1185
- ```json
1186
- {
1187
- "mcpServers": {
1188
- "huggingchat-importer": {
1189
- "command": "npx",
1190
- "args": ["mcp-remote", "http://localhost:7860/gradio_api/mcp/"]
1191
- }
1192
- }
1193
- }
1194
- ```
1195
-
1196
- ---
1197
- *Built with Gradio 6.0.2 | MCP Transport: Streamable HTTP*
1198
- """)
1199
-
1200
- # ============================================================================
1201
- # LAUNCH
1202
- # ============================================================================
1203
 
1204
  if __name__ == "__main__":
1205
- app.launch(
1206
- mcp_server=True, # Enable MCP server
1207
- server_name="0.0.0.0",
1208
- server_port=7860,
1209
- share=False,
1210
- show_error=True,
1211
- theme=gr.themes.Soft()
1212
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ import torch
3
+ import uuid
4
+ import spaces
5
+ from supermariogpt.dataset import MarioDataset
6
+ from supermariogpt.prompter import Prompter
7
+ from supermariogpt.lm import MarioLM
8
+ from supermariogpt.utils import view_level, convert_level_to_png
9
+
10
+ from fastapi import FastAPI, HTTPException
11
+ from fastapi.staticfiles import StaticFiles
12
+
13
+ import os
14
+ import uvicorn
15
+ from pathlib import Path
16
+ import logging
17
+
18
+ # Setup logging
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Initialize model
23
+ try:
24
+ mario_lm = MarioLM()
25
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
26
+ mario_lm = mario_lm.to(device)
27
+ logger.info(f"Model loaded successfully on {device}")
28
+ except Exception as e:
29
+ logger.error(f"Failed to load model: {e}")
30
+ raise
31
+
32
+ TILE_DIR = "data/tiles"
33
+
34
+ # Ensure static directory exists
35
+ Path("static").mkdir(exist_ok=True)
36
+
37
+ gr.set_static_paths(paths=[Path("static").absolute()])
38
+
39
+ app = FastAPI()
40
+
41
+ def make_html_file(generated_level):
42
+ """Generate HTML file for level visualization"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  try:
44
+ level_text = f"""{'''
45
+ '''.join(view_level(generated_level, mario_lm.tokenizer))}"""
46
+ unique_id = uuid.uuid4() # Changed from uuid1 to uuid4 for better randomness
47
+ html_filename = f"demo-{unique_id}.html"
48
+
49
+ html_content = f'''<!DOCTYPE html>
50
+ <html lang="en">
51
+
52
+ <head>
53
+ <meta charset="utf-8">
54
+ <title>supermariogpt</title>
55
+ <script src="https://cjrtnc.leaningtech.com/20230216/loader.js"></script>
56
+ </head>
57
+
58
+ <body>
59
+ </body>
60
+ <script>
61
+ cheerpjInit().then(function () {{
62
+ cheerpjAddStringFile("/str/mylevel.txt", `{level_text}`);
63
+ }});
64
+ cheerpjCreateDisplay(512, 500);
65
+ cheerpjRunJar("/app/gradio_api/file=static/mario.jar");
66
+ </script>
67
+ </html>'''
68
+
69
+ with open(Path("static") / html_filename, 'w', encoding='utf-8') as f:
70
+ f.write(html_content)
71
+
72
+ return html_filename
 
 
 
73
  except Exception as e:
74
+ logger.error(f"Error creating HTML file: {e}")
75
+ raise
 
 
76
 
77
+ @spaces.GPU
78
+ def generate(pipes, enemies, blocks, elevation, temperature=2.0, level_size=1399, prompt="", progress=gr.Progress(track_tqdm=True)):
79
+ """Generate Mario level based on parameters"""
80
  try:
81
+ # Validate inputs
82
+ temperature = max(0.1, min(2.0, float(temperature)))
83
+ level_size = max(100, min(2799, int(level_size)))
84
+
85
+ if prompt == "":
86
+ prompt = f"{pipes} pipes, {enemies} enemies, {blocks} blocks, {elevation} elevation"
87
+
88
+ logger.info(f"Using prompt: {prompt}")
89
+ logger.info(f"Using temperature: {temperature}")
90
+ logger.info(f"Using level size: {level_size}")
91
+
92
+ prompts = [prompt]
93
+ generated_level = mario_lm.sample(
94
+ prompts=prompts,
95
+ num_steps=level_size,
96
+ temperature=float(temperature),
97
+ use_tqdm=True
98
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
+ filename = make_html_file(generated_level)
101
+ img = convert_level_to_png(generated_level.squeeze(), TILE_DIR, mario_lm.tokenizer)[0]
102
 
103
+ gradio_html = f'''<div>
104
+ <iframe width=512 height=512 style="margin: 0 auto" src="/gradio_api/file=static/{filename}"></iframe>
105
+ <p style="text-align:center">Press the arrow keys to move. Press <code>a</code> to run, <code>s</code> to jump and <code>d</code> to shoot fireflowers</p>
106
+ </div>'''
 
 
 
 
 
 
 
 
107
 
108
+ return [img, gradio_html]
109
+ except Exception as e:
110
+ logger.error(f"Error generating level: {e}")
111
+ raise gr.Error(f"Failed to generate level: {str(e)}")
112
+
113
+ with gr.Blocks().queue() as demo:
114
+ gr.Markdown('''# MarioGPT
115
+ ### Playable demo for MarioGPT: Open-Ended Text2Level Generation through Large Language Models
116
+ [[Github](https://github.com/shyamsn97/mario-gpt)], [[Paper](https://arxiv.org/abs/2302.05981)]
117
+ ''')
118
+ with gr.Tabs():
119
+ with gr.TabItem("Compose prompt"):
120
+ with gr.Row():
121
+ pipes = gr.Radio(["no", "little", "some", "many"], value="some", label="How many pipes?")
122
+ enemies = gr.Radio(["no", "little", "some", "many"], value="some", label="How many enemies?")
123
+ with gr.Row():
124
+ blocks = gr.Radio(["little", "some", "many"], value="some", label="How many blocks?")
125
+ elevation = gr.Radio(["low", "high"], value="low", label="Elevation?")
126
+ with gr.TabItem("Type prompt"):
127
+ text_prompt = gr.Textbox(value="", label="Enter your MarioGPT prompt. ex: 'many pipes, many enemies, some blocks, low elevation'")
128
+
129
+ with gr.Accordion(label="Advanced settings", open=False):
130
+ temperature = gr.Slider(value=2.0, minimum=0.1, maximum=2.0, step=0.1, label="temperature: Increase these for more diverse, but lower quality, generations")
131
+ level_size = gr.Slider(value=1399, minimum=100, maximum=2799, step=1, label="level_size")
132
+
133
+ btn = gr.Button("Generate level")
134
+ with gr.Row():
135
+ with gr.Group():
136
+ level_play = gr.HTML()
137
+ level_image = gr.Image()
138
+ btn.click(fn=generate, inputs=[pipes, enemies, blocks, elevation, temperature, level_size, text_prompt], outputs=[level_image, level_play])
139
+ gr.Examples(
140
+ examples=[
141
+ ["many", "many", "some", "high"],
142
+ ["no", "some", "many", "high"],
143
+ ["many", "many", "little", "low"],
144
+ ["no", "no", "many", "high"],
145
+ ],
146
+ inputs=[pipes, enemies, blocks, elevation],
147
+ outputs=[level_image, level_play],
148
+ fn=generate,
149
+ cache_examples=True,
150
+ )
151
+
152
+ # Mount static files and Gradio app
153
+ app.mount("/static", StaticFiles(directory="static", html=True), name="static")
154
+ app = gr.mount_gradio_app(app, demo, "/")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
  if __name__ == "__main__":
157
+ uvicorn.run(app, host="0.0.0.0", port=7860)