Speedofmastery commited on
Commit
66ed707
Β·
1 Parent(s): 864646f

Switch to subprocess execution (Docker-in-Docker not supported on HF)

Browse files
Files changed (1) hide show
  1. app.py +918 -275
app.py CHANGED
@@ -1,22 +1,18 @@
1
  """
2
- Docker-Based Sandbox Executor for Hugging Face Spaces
3
- Secure code execution using Docker containers
4
  """
5
- from fastapi import FastAPI, HTTPException
 
 
6
  from fastapi.middleware.cors import CORSMiddleware
7
- from pydantic import BaseModel
8
- import subprocess
9
- import tempfile
10
  import os
11
- import time
12
- import base64
13
- from typing import Optional
14
- import docker
15
- from pathlib import Path
16
 
17
- app = FastAPI(title="Docker Sandbox Executor")
18
 
19
- # Enable CORS
20
  app.add_middleware(
21
  CORSMiddleware,
22
  allow_origins=["*"],
@@ -25,305 +21,952 @@ app.add_middleware(
25
  allow_headers=["*"],
26
  )
27
 
28
- # Initialize Docker client
29
- try:
30
- docker_client = docker.from_env()
31
- DOCKER_AVAILABLE = True
32
- print("βœ“ Docker client initialized successfully")
33
- except Exception as e:
34
- DOCKER_AVAILABLE = False
35
- print(f"⚠️ Docker not available: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
- class CodeExecutionRequest(BaseModel):
38
- language: str
39
- code: str
40
- timeout: Optional[int] = 30
41
 
42
- class SandboxDockerExecutor:
43
- """Execute code in isolated Docker containers"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- def __init__(self):
46
- self.execution_count = 0
47
-
48
- def execute_in_docker(self, language: str, code: str, timeout: int = 30):
49
- """Execute code in Docker container"""
50
- self.execution_count += 1
51
- start_time = time.time()
52
-
53
- try:
54
- if language.lower() == "python":
55
- return self._execute_python_docker(code, timeout)
56
- elif language.lower() == "react":
57
- return self._execute_react_docker(code, timeout)
58
- elif language.lower() == "html":
59
- return self._execute_html_docker(code, timeout)
60
- elif language.lower() == "javascript":
61
- return self._execute_javascript_docker(code, timeout)
62
- else:
63
- return False, "", f"❌ Unsupported language: {language}"
64
-
65
- except Exception as e:
66
- return False, "", f"❌ Docker execution error: {str(e)}"
67
 
68
- def _execute_python_docker(self, code: str, timeout: int):
69
- """Execute Python in Docker"""
70
- container = None
71
- try:
72
- # Create container with resource limits
73
- container = docker_client.containers.run(
74
- "python:3.11-alpine",
75
- f"python -c '{code}'",
76
- detach=True,
77
- mem_limit="256m",
78
- cpu_period=100000,
79
- cpu_quota=50000, # 50% CPU
80
- network_mode="none", # No network access
81
- remove=True
82
- )
83
-
84
- # Wait for completion with timeout
85
- result = container.wait(timeout=timeout)
86
- logs = container.logs().decode('utf-8')
87
-
88
- success = result['StatusCode'] == 0
89
- return success, logs, "" if success else logs
90
-
91
- except docker.errors.ContainerError as e:
92
- return False, "", str(e)
93
- except Exception as e:
94
- return False, "", f"Container error: {str(e)}"
95
- finally:
96
- if container:
97
- try:
98
- container.remove(force=True)
99
- except:
100
- pass
101
 
102
- def _execute_react_docker(self, code: str, timeout: int):
103
- """Execute React (generate HTML for client-side rendering)"""
104
- html_content = f"""<!DOCTYPE html>
105
- <html lang="en">
106
- <head>
107
- <meta charset="UTF-8">
108
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
109
- <title>React Sandbox #{self.execution_count}</title>
110
- <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
111
- <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
112
- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
113
- <style>
114
- body {{
115
- margin: 0;
116
- padding: 0;
117
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
118
- }}
119
- #root {{
120
- width: 100%;
121
- min-height: 100vh;
122
- }}
123
- </style>
124
- </head>
125
- <body>
126
- <div id="root"></div>
127
- <script type="text/babel">
128
- {code}
129
- </script>
130
- </body>
131
- </html>"""
132
- return True, html_content, ""
133
 
134
- def _execute_html_docker(self, code: str, timeout: int):
135
- """Execute HTML"""
136
- if not code.strip().startswith('<!DOCTYPE') and not code.strip().startswith('<html'):
137
- code = f"<!DOCTYPE html>\n{code}"
138
- return True, code, ""
 
 
 
139
 
140
- def _execute_javascript_docker(self, code: str, timeout: int):
141
- """Execute JavaScript in Docker"""
142
- container = None
143
- try:
144
- container = docker_client.containers.run(
145
- "node:18-alpine",
146
- f"node -e '{code}'",
147
- detach=True,
148
- mem_limit="256m",
149
- cpu_period=100000,
150
- cpu_quota=50000,
151
- network_mode="none",
152
- remove=True
153
  )
154
 
155
- result = container.wait(timeout=timeout)
156
- logs = container.logs().decode('utf-8')
 
 
 
 
157
 
158
- success = result['StatusCode'] == 0
159
- return success, logs, "" if success else logs
160
 
161
- except Exception as e:
162
- return False, "", f"Container error: {str(e)}"
163
- finally:
164
- if container:
165
- try:
166
- container.remove(force=True)
167
- except:
168
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
- # Global executor
171
- executor = SandboxDockerExecutor()
172
 
173
- def format_terminal_html(language: str, success: bool, stdout: str, stderr: str,
174
- execution_time: float, exec_id: int) -> str:
175
- """Format terminal output as HTML"""
176
- import html as html_module
 
 
177
 
178
- status_color = "#00ff00" if success else "#ff4444"
179
- status_text = "βœ“ SUCCESS" if success else "βœ— FAILED"
180
 
181
- stdout_escaped = html_module.escape(stdout) if stdout else ""
182
- stderr_escaped = html_module.escape(stderr) if stderr else ""
 
 
 
183
 
184
- return f"""<!DOCTYPE html>
185
- <html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  <head>
187
  <meta charset="UTF-8">
188
- <title>🐳 Docker Sandbox Terminal #{exec_id}</title>
 
 
 
 
 
 
 
 
189
  <style>
190
- * {{ margin: 0; padding: 0; box-sizing: border-box; }}
191
- body {{
192
- background: #000;
193
- color: #00ff00;
194
- font-family: 'Courier New', monospace;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  padding: 20px;
196
- line-height: 1.6;
197
- }}
198
- .header {{
199
- border-bottom: 2px solid #00ff00;
200
- padding-bottom: 15px;
201
- margin-bottom: 20px;
202
- }}
203
- h1 {{ color: #00ff00; font-size: 24px; margin-bottom: 10px; }}
204
- .info {{
205
- display: grid;
206
- grid-template-columns: 150px 1fr;
207
  gap: 10px;
208
- margin-bottom: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  font-size: 14px;
210
- }}
211
- .info-label {{ color: #888; }}
212
- .info-value {{ color: #00ff00; }}
213
- .status {{ color: {status_color}; font-weight: bold; font-size: 18px; }}
214
- .output-section {{ margin: 20px 0; }}
215
- .output-title {{
216
- color: #00aaff;
217
- font-size: 16px;
218
- margin-bottom: 10px;
219
- border-bottom: 1px solid #00aaff;
220
- padding-bottom: 5px;
221
- }}
222
- .output-content {{
223
- background: #111;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  padding: 15px;
225
- border-left: 3px solid #00ff00;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  white-space: pre-wrap;
227
  word-wrap: break-word;
228
- max-height: 400px;
229
- overflow-y: auto;
230
- }}
231
- .stderr {{ color: #ff6666; border-left-color: #ff4444; }}
232
- .empty {{ color: #666; font-style: italic; }}
233
- ::-webkit-scrollbar {{ width: 10px; }}
234
- ::-webkit-scrollbar-track {{ background: #111; }}
235
- ::-webkit-scrollbar-thumb {{ background: #00ff00; border-radius: 5px; }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  </style>
237
  </head>
238
  <body>
239
- <div class="header">
240
- <h1>🐳 DOCKER SANDBOX TERMINAL</h1>
241
- </div>
242
- <div class="info">
243
- <div class="info-label">Execution ID:</div>
244
- <div class="info-value">#{exec_id}</div>
245
- <div class="info-label">Language:</div>
246
- <div class="info-value">{language.upper()}</div>
247
- <div class="info-label">Execution Time:</div>
248
- <div class="info-value">{execution_time:.3f}s</div>
249
- <div class="info-label">Status:</div>
250
- <div class="status">{status_text}</div>
251
- </div>
252
- <div class="output-section">
253
- <div class="output-title">πŸ“€ STDOUT</div>
254
- <div class="output-content {'empty' if not stdout_escaped else ''}">
255
- {stdout_escaped if stdout_escaped else '(no output)'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  </div>
257
  </div>
258
- {f'<div class="output-section"><div class="output-title">⚠️ STDERR</div><div class="output-content stderr">{stderr_escaped}</div></div>' if stderr_escaped else ''}
259
- </body>
260
- </html>"""
261
 
262
- @app.post("/execute")
263
- async def execute_code(request: CodeExecutionRequest):
264
- """Execute code in Docker sandbox"""
265
-
266
- if not DOCKER_AVAILABLE:
267
- raise HTTPException(status_code=503, detail="Docker not available in this environment")
268
-
269
- start_time = time.time()
270
-
271
- # Execute in Docker
272
- success, stdout, stderr = executor.execute_in_docker(
273
- request.language,
274
- request.code,
275
- request.timeout
276
- )
277
-
278
- execution_time = time.time() - start_time
279
-
280
- # Generate terminal view
281
- terminal_html = format_terminal_html(
282
- request.language,
283
- success,
284
- stdout,
285
- stderr,
286
- execution_time,
287
- executor.execution_count
288
- )
289
-
290
- # Encode as data URLs
291
- terminal_b64 = base64.b64encode(terminal_html.encode('utf-8')).decode('utf-8')
292
- terminal_url = f"data:text/html;base64,{terminal_b64}"
293
-
294
- # For HTML/React, use stdout as preview
295
- preview_url = None
296
- if request.language.lower() in ["html", "react"]:
297
- preview_b64 = base64.b64encode(stdout.encode('utf-8')).decode('utf-8')
298
- preview_url = f"data:text/html;base64,{preview_b64}"
299
-
300
- return {
301
- "status": "success" if success else "error",
302
- "preview_url": preview_url,
303
- "terminal_view_url": terminal_url,
304
- "execution_time": execution_time,
305
- "docker_enabled": True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
 
308
- @app.get("/health")
309
- async def health_check():
310
- """Health check endpoint"""
311
- return {
312
- "status": "healthy",
313
- "docker_available": DOCKER_AVAILABLE
 
 
 
 
 
 
314
  }
 
 
 
 
 
315
 
316
- @app.get("/")
317
- async def root():
318
- """Root endpoint"""
319
- return {
320
- "message": "🐳 Docker Sandbox Executor API",
321
- "docker_enabled": DOCKER_AVAILABLE,
322
- "endpoints": {
323
- "/execute": "POST - Execute code in Docker sandbox",
324
- "/health": "GET - Health check"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
  if __name__ == "__main__":
329
  import uvicorn
 
1
  """
2
+ FastAPI Universal Code Execution Sandbox for Hugging Face Spaces
3
+ Supports: HTML, React, Python, Node.js, Java, Ruby, PHP, Go, Rust, C++, C#, Swift, Kotlin, and more!
4
  """
5
+
6
+ from fastapi import FastAPI, Request
7
+ from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
8
  from fastapi.middleware.cors import CORSMiddleware
 
 
 
9
  import os
10
+ import asyncio
11
+ import shutil
 
 
 
12
 
13
+ app = FastAPI()
14
 
15
+ # Enable CORS for iframe embedding
16
  app.add_middleware(
17
  CORSMiddleware,
18
  allow_origins=["*"],
 
21
  allow_headers=["*"],
22
  )
23
 
24
+ # Language configurations
25
+ LANGUAGE_CONFIG = {
26
+ "python": {
27
+ "extension": ".py",
28
+ "command": lambda file: ["python3", file],
29
+ "runtime_check": "python3"
30
+ },
31
+ "node": {
32
+ "extension": ".mjs",
33
+ "command": lambda file: ["node", file],
34
+ "runtime_check": "node"
35
+ },
36
+ "java": {
37
+ "extension": ".java",
38
+ "compile": lambda file: ["javac", file],
39
+ "command": lambda: ["java", "-cp", "/tmp", "Main"],
40
+ "runtime_check": "javac",
41
+ "needs_compile": True
42
+ },
43
+ "ruby": {
44
+ "extension": ".rb",
45
+ "command": lambda file: ["ruby", file],
46
+ "runtime_check": "ruby"
47
+ },
48
+ "php": {
49
+ "extension": ".php",
50
+ "command": lambda file: ["php", file],
51
+ "runtime_check": "php"
52
+ },
53
+ "go": {
54
+ "extension": ".go",
55
+ "command": lambda file: ["go", "run", file],
56
+ "runtime_check": "go"
57
+ },
58
+ "rust": {
59
+ "extension": ".rs",
60
+ "compile": lambda file: ["rustc", file, "-o", "/tmp/rust_output"],
61
+ "command": lambda: ["/tmp/rust_output"],
62
+ "runtime_check": "rustc",
63
+ "needs_compile": True
64
+ },
65
+ "cpp": {
66
+ "extension": ".cpp",
67
+ "compile": lambda file: ["g++", file, "-o", "/tmp/cpp_output"],
68
+ "command": lambda: ["/tmp/cpp_output"],
69
+ "runtime_check": "g++",
70
+ "needs_compile": True
71
+ },
72
+ "c": {
73
+ "extension": ".c",
74
+ "compile": lambda file: ["gcc", file, "-o", "/tmp/c_output"],
75
+ "command": lambda: ["/tmp/c_output"],
76
+ "runtime_check": "gcc",
77
+ "needs_compile": True
78
+ },
79
+ "csharp": {
80
+ "extension": ".cs",
81
+ "compile": lambda file: ["mcs", file, "-out:/tmp/csharp_output.exe"],
82
+ "command": lambda: ["mono", "/tmp/csharp_output.exe"],
83
+ "runtime_check": "mcs",
84
+ "needs_compile": True
85
+ },
86
+ "swift": {
87
+ "extension": ".swift",
88
+ "command": lambda file: ["swift", file],
89
+ "runtime_check": "swift"
90
+ },
91
+ "kotlin": {
92
+ "extension": ".kt",
93
+ "compile": lambda file: ["kotlinc", file, "-include-runtime", "-d", "/tmp/kotlin_output.jar"],
94
+ "command": lambda: ["java", "-jar", "/tmp/kotlin_output.jar"],
95
+ "runtime_check": "kotlinc",
96
+ "needs_compile": True
97
+ },
98
+ "perl": {
99
+ "extension": ".pl",
100
+ "command": lambda file: ["perl", file],
101
+ "runtime_check": "perl"
102
+ },
103
+ "lua": {
104
+ "extension": ".lua",
105
+ "command": lambda file: ["lua", file],
106
+ "runtime_check": "lua"
107
+ },
108
+ "bash": {
109
+ "extension": ".sh",
110
+ "command": lambda file: ["bash", file],
111
+ "runtime_check": "bash"
112
+ },
113
+ "r": {
114
+ "extension": ".r",
115
+ "command": lambda file: ["Rscript", file],
116
+ "runtime_check": "Rscript"
117
+ },
118
+ "scala": {
119
+ "extension": ".scala",
120
+ "command": lambda file: ["scala", file],
121
+ "runtime_check": "scala"
122
+ }
123
+ }
124
+
125
+
126
+ @app.get("/health")
127
+ async def health():
128
+ """Health check for pre-warming the Space"""
129
+ return {"status": "ok"}
130
+
131
+
132
+ @app.get("/capabilities")
133
+ async def capabilities():
134
+ """Check which language runtimes are available"""
135
+ caps = {}
136
+ for lang, config in LANGUAGE_CONFIG.items():
137
+ runtime = config.get("runtime_check")
138
+ caps[lang] = shutil.which(runtime) is not None
139
+ return caps
140
 
 
 
 
 
141
 
142
+ @app.get("/languages")
143
+ async def list_languages():
144
+ """List all supported languages with availability"""
145
+ result = []
146
+ for lang, config in LANGUAGE_CONFIG.items():
147
+ runtime = config.get("runtime_check")
148
+ available = shutil.which(runtime) is not None
149
+ result.append({
150
+ "language": lang,
151
+ "available": available,
152
+ "extension": config["extension"],
153
+ "needs_compile": config.get("needs_compile", False)
154
+ })
155
+ return {"languages": result}
156
+
157
+
158
+ async def stream_execution(language: str, code: str):
159
+ """Execute code for any supported language and stream output"""
160
 
161
+ config = LANGUAGE_CONFIG.get(language)
162
+ if not config:
163
+ yield f"Error: Unsupported language '{language}'\n"
164
+ yield "done\n"
165
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
+ # Check runtime availability
168
+ runtime = config.get("runtime_check")
169
+ if not shutil.which(runtime):
170
+ yield f"Error: {language} runtime not available\n"
171
+ yield "done\n"
172
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
+ # Determine filename
175
+ if language == "java":
176
+ code_path = "/tmp/Main.java"
177
+ else:
178
+ code_path = f"/tmp/code{config['extension']}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
+ # Write code to file
181
+ try:
182
+ with open(code_path, "w") as f:
183
+ f.write(code)
184
+ except Exception as e:
185
+ yield f"Error writing code: {e}\n"
186
+ yield "done\n"
187
+ return
188
 
189
+ try:
190
+ # Compile if needed
191
+ if config.get("needs_compile"):
192
+ compile_cmd = config["compile"](code_path)
193
+
194
+ compile_process = await asyncio.create_subprocess_exec(
195
+ *compile_cmd,
196
+ stdout=asyncio.subprocess.PIPE,
197
+ stderr=asyncio.subprocess.STDOUT
 
 
 
 
198
  )
199
 
200
+ # Stream compilation output
201
+ while True:
202
+ line = await compile_process.stdout.readline()
203
+ if not line:
204
+ break
205
+ yield line.decode('utf-8', errors='replace')
206
 
207
+ exit_code = await compile_process.wait()
 
208
 
209
+ if exit_code != 0:
210
+ yield f"Compilation failed with exit code {exit_code}\n"
211
+ yield "done\n"
212
+ return
213
+
214
+ # Execute
215
+ if config.get("needs_compile") and callable(config["command"]):
216
+ run_cmd = config["command"]()
217
+ else:
218
+ run_cmd = config["command"](code_path)
219
+
220
+ run_process = await asyncio.create_subprocess_exec(
221
+ *run_cmd,
222
+ stdout=asyncio.subprocess.PIPE,
223
+ stderr=asyncio.subprocess.STDOUT
224
+ )
225
+
226
+ # Stream execution output
227
+ while True:
228
+ line = await run_process.stdout.readline()
229
+ if not line:
230
+ break
231
+ yield line.decode('utf-8', errors='replace')
232
+
233
+ await run_process.wait()
234
+ yield "done\n"
235
+
236
+ except Exception as e:
237
+ yield f"Execution error: {e}\n"
238
+ yield "done\n"
239
+
240
+ finally:
241
+ # Clean up
242
+ try:
243
+ os.remove(code_path)
244
+ # Clean up compiled outputs
245
+ if language == "java":
246
+ os.remove("/tmp/Main.class")
247
+ elif language == "rust":
248
+ os.remove("/tmp/rust_output")
249
+ elif language == "cpp":
250
+ os.remove("/tmp/cpp_output")
251
+ elif language == "c":
252
+ os.remove("/tmp/c_output")
253
+ elif language == "csharp":
254
+ os.remove("/tmp/csharp_output.exe")
255
+ elif language == "kotlin":
256
+ os.remove("/tmp/kotlin_output.jar")
257
+ except Exception:
258
+ pass
259
+
260
+ # Schedule exit
261
+ asyncio.create_task(delayed_exit())
262
+
263
+
264
+ async def delayed_exit():
265
+ """Exit the process after a short delay"""
266
+ await asyncio.sleep(0.5)
267
+ os._exit(0)
268
 
 
 
269
 
270
+ @app.post("/run")
271
+ async def run_code(request: Request):
272
+ """Execute code in any supported language and stream output"""
273
+ body = await request.json()
274
+ language = body.get("language", "").lower()
275
+ code = body.get("code", "")
276
 
277
+ if not code:
278
+ return JSONResponse({"error": "NO_CODE"}, status_code=400)
279
 
280
+ if language not in LANGUAGE_CONFIG:
281
+ return JSONResponse(
282
+ {"error": f"UNSUPPORTED_LANGUAGE: {language}"},
283
+ status_code=400
284
+ )
285
 
286
+ # Check if runtime is available
287
+ config = LANGUAGE_CONFIG[language]
288
+ runtime = config.get("runtime_check")
289
+ if not shutil.which(runtime):
290
+ return JSONResponse(
291
+ {"error": f"{language.upper()}_NOT_AVAILABLE"},
292
+ status_code=503
293
+ )
294
+
295
+ return StreamingResponse(
296
+ stream_execution(language, code),
297
+ media_type="text/plain"
298
+ )
299
+
300
+
301
+ @app.get("/", response_class=HTMLResponse)
302
+ async def root():
303
+ """Serve the HTML UI with multi-language support"""
304
+ html_content = """
305
+ <!DOCTYPE html>
306
+ <html lang="en">
307
  <head>
308
  <meta charset="UTF-8">
309
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
310
+ <title>Universal Code Sandbox</title>
311
+
312
+ <!-- CDN Scripts -->
313
+ <script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js"></script>
314
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
315
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
316
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
317
+
318
  <style>
319
+ * {
320
+ margin: 0;
321
+ padding: 0;
322
+ box-sizing: border-box;
323
+ }
324
+
325
+ body {
326
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
327
+ background: #1e1e1e;
328
+ color: #d4d4d4;
329
+ height: 100vh;
330
+ overflow: hidden;
331
+ display: flex;
332
+ flex-direction: column;
333
+ }
334
+
335
+ .app-container {
336
+ display: flex;
337
+ flex-direction: column;
338
+ height: 100vh;
339
  padding: 20px;
340
+ gap: 15px;
341
+ }
342
+
343
+ h1 {
344
+ color: #4ec9b0;
345
+ font-size: 28px;
346
+ margin: 0;
347
+ }
348
+
349
+ .controls {
350
+ display: flex;
351
  gap: 10px;
352
+ align-items: center;
353
+ flex-wrap: wrap;
354
+ flex-shrink: 0;
355
+ }
356
+
357
+ label {
358
+ color: #9cdcfe;
359
+ font-weight: bold;
360
+ }
361
+
362
+ select, button {
363
+ background: #252526;
364
+ color: #d4d4d4;
365
+ border: 1px solid #3c3c3c;
366
+ padding: 10px 15px;
367
+ border-radius: 4px;
368
+ font-family: inherit;
369
  font-size: 14px;
370
+ cursor: pointer;
371
+ transition: all 0.2s;
372
+ }
373
+
374
+ select:hover {
375
+ border-color: #007acc;
376
+ }
377
+
378
+ select:focus, button:focus {
379
+ outline: none;
380
+ border-color: #007acc;
381
+ }
382
+
383
+ button {
384
+ background: #007acc;
385
+ color: white;
386
+ border: none;
387
+ font-weight: bold;
388
+ padding: 10px 20px;
389
+ }
390
+
391
+ button:hover:not(:disabled) {
392
+ background: #005a9e;
393
+ transform: translateY(-1px);
394
+ }
395
+
396
+ button:active:not(:disabled) {
397
+ transform: translateY(0);
398
+ }
399
+
400
+ button:disabled {
401
+ background: #3c3c3c;
402
+ cursor: not-allowed;
403
+ opacity: 0.6;
404
+ }
405
+
406
+ .workspace {
407
+ flex: 1;
408
+ display: grid;
409
+ grid-template-rows: minmax(250px, 1fr) minmax(250px, 1fr);
410
+ gap: 15px;
411
+ min-height: 0;
412
+ overflow: hidden;
413
+ }
414
+
415
+ .editor-panel {
416
+ display: flex;
417
+ flex-direction: column;
418
+ min-height: 0;
419
+ }
420
+
421
+ .panel-label {
422
+ color: #9cdcfe;
423
+ font-weight: bold;
424
+ margin-bottom: 8px;
425
+ font-size: 13px;
426
+ text-transform: uppercase;
427
+ letter-spacing: 1px;
428
+ }
429
+
430
+ textarea {
431
+ flex: 1;
432
+ background: #252526;
433
+ color: #d4d4d4;
434
+ border: 1px solid #3c3c3c;
435
+ border-radius: 4px;
436
  padding: 15px;
437
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
438
+ font-size: 14px;
439
+ line-height: 1.6;
440
+ resize: none;
441
+ outline: none;
442
+ min-height: 0;
443
+ }
444
+
445
+ textarea:focus {
446
+ border-color: #007acc;
447
+ }
448
+
449
+ .output-panel {
450
+ display: flex;
451
+ flex-direction: column;
452
+ min-height: 0;
453
+ position: relative;
454
+ }
455
+
456
+ #output {
457
+ flex: 1;
458
+ background: #0c0c0c;
459
+ color: #00ff00;
460
+ border: 1px solid #00ff00;
461
+ border-radius: 4px;
462
+ padding: 15px;
463
+ overflow-y: auto;
464
+ font-size: 13px;
465
+ line-height: 1.6;
466
  white-space: pre-wrap;
467
  word-wrap: break-word;
468
+ font-family: 'Courier New', monospace;
469
+ min-height: 0;
470
+ }
471
+
472
+ #html-preview, #react-root {
473
+ flex: 1;
474
+ background: white;
475
+ border: 1px solid #3c3c3c;
476
+ border-radius: 4px;
477
+ overflow: auto;
478
+ min-height: 0;
479
+ }
480
+
481
+ #html-preview {
482
+ border: 1px solid #3c3c3c;
483
+ }
484
+
485
+ .error { color: #f48771; }
486
+ .success { color: #4ec9b0; }
487
+ .info { color: #9cdcfe; }
488
+
489
+ .badge {
490
+ display: inline-block;
491
+ padding: 3px 8px;
492
+ border-radius: 3px;
493
+ font-size: 11px;
494
+ margin-left: 5px;
495
+ font-weight: bold;
496
+ }
497
+
498
+ .badge-available { background: #4ec9b0; color: #000; }
499
+ .badge-unavailable { background: #f48771; color: #000; }
500
+
501
+ /* Scrollbar styling */
502
+ ::-webkit-scrollbar {
503
+ width: 10px;
504
+ height: 10px;
505
+ }
506
+
507
+ ::-webkit-scrollbar-track {
508
+ background: #1e1e1e;
509
+ }
510
+
511
+ ::-webkit-scrollbar-thumb {
512
+ background: #3c3c3c;
513
+ border-radius: 5px;
514
+ }
515
+
516
+ ::-webkit-scrollbar-thumb:hover {
517
+ background: #007acc;
518
+ }
519
+
520
+ @media (max-width: 768px) {
521
+ .workspace {
522
+ grid-template-rows: 300px 1fr;
523
+ }
524
+
525
+ h1 {
526
+ font-size: 20px;
527
+ }
528
+ }
529
  </style>
530
  </head>
531
  <body>
532
+ <div class="app-container">
533
+ <h1>&#128640; Universal Code Sandbox</h1>
534
+
535
+ <div class="controls">
536
+ <label for="language">Language:</label>
537
+ <select id="language" onchange="switchLanguage()">
538
+ <optgroup label="Client-Side">
539
+ <option value="html">HTML</option>
540
+ <option value="react">React/JSX</option>
541
+ </optgroup>
542
+ <optgroup label="Scripting Languages">
543
+ <option value="python">Python</option>
544
+ <option value="node">Node.js/JavaScript</option>
545
+ <option value="ruby">Ruby</option>
546
+ <option value="php">PHP</option>
547
+ <option value="perl">Perl</option>
548
+ <option value="lua">Lua</option>
549
+ <option value="bash">Bash</option>
550
+ <option value="r">R</option>
551
+ </optgroup>
552
+ <optgroup label="Compiled Languages">
553
+ <option value="java">Java</option>
554
+ <option value="cpp">C++</option>
555
+ <option value="c">C</option>
556
+ <option value="go">Go</option>
557
+ <option value="rust">Rust</option>
558
+ <option value="csharp">C#</option>
559
+ <option value="swift">Swift</option>
560
+ <option value="kotlin">Kotlin</option>
561
+ <option value="scala">Scala</option>
562
+ </optgroup>
563
+ </select>
564
+
565
+ <button id="runBtn" onclick="runCode()">&#9654; Run</button>
566
+ <button onclick="checkAvailability()">&#128269; Check Available</button>
567
+ <span id="status" class="info"></span>
568
+ </div>
569
+
570
+ <div class="workspace">
571
+ <div class="editor-panel">
572
+ <div class="panel-label">&#9670; Code Editor</div>
573
+ <textarea id="editor" placeholder="Enter your code here..." spellcheck="false"></textarea>
574
+ </div>
575
+
576
+ <div class="output-panel">
577
+ <div class="panel-label">&#9670; Output / Preview</div>
578
+ <pre id="output" style="display: none;"></pre>
579
+ <iframe id="html-preview" style="display: none;" sandbox="allow-scripts"></iframe>
580
+ <div id="react-root" style="display: none;"></div>
581
+ </div>
582
  </div>
583
  </div>
 
 
 
584
 
585
+ <script>
586
+ const editor = document.getElementById('editor');
587
+ const output = document.getElementById('output');
588
+ const htmlPreview = document.getElementById('html-preview');
589
+ const reactRoot = document.getElementById('react-root');
590
+ const languageSelect = document.getElementById('language');
591
+ const runBtn = document.getElementById('runBtn');
592
+ const status = document.getElementById('status');
593
+
594
+ let capabilities = {};
595
+
596
+ // Fetch capabilities on load
597
+ fetch('/capabilities')
598
+ .then(res => res.json())
599
+ .then(data => {
600
+ capabilities = data;
601
+ updateLanguageSelector();
602
+ });
603
+
604
+ function updateLanguageSelector() {
605
+ const options = languageSelect.querySelectorAll('option');
606
+ options.forEach(opt => {
607
+ const lang = opt.value;
608
+ if (lang === 'html' || lang === 'react') {
609
+ return; // Client-side always available
610
+ }
611
+ if (capabilities[lang] !== undefined) {
612
+ const badge = capabilities[lang] ?
613
+ '<span class="badge badge-available">βœ“</span>' :
614
+ '<span class="badge badge-unavailable">βœ—</span>';
615
+ opt.innerHTML = opt.innerHTML.split('<')[0] + ' ' + (capabilities[lang] ? 'βœ“' : 'βœ—');
616
+ }
617
+ });
618
+ }
619
+
620
+ async function checkAvailability() {
621
+ status.textContent = 'πŸ” Checking...';
622
+ status.className = 'info';
623
+
624
+ const response = await fetch('/languages');
625
+ const data = await response.json();
626
+
627
+ output.style.display = 'block';
628
+ htmlPreview.style.display = 'none';
629
+ reactRoot.style.display = 'none';
630
+
631
+ output.textContent = 'Available Languages:\\n\\n';
632
+ data.languages.forEach(lang => {
633
+ const badge = lang.available ? 'βœ…' : '❌';
634
+ output.textContent += `${badge} ${lang.language.toUpperCase()} (${lang.extension})\\n`;
635
+ });
636
+
637
+ status.textContent = 'βœ“ Check complete';
638
+ status.className = 'success';
639
+ }
640
+
641
+ const examples = {
642
+ python: `# Python example
643
+ print("Hello from Python!")
644
+ for i in range(5):
645
+ print(f"Number: {i + 1}")
646
+ print("Done!")`,
647
+
648
+ node: `// Node.js example
649
+ console.log('Hello from Node.js!');
650
+ for (let i = 0; i < 5; i++) {
651
+ console.log('Step ' + (i + 1));
652
+ await new Promise(r => setTimeout(r, 200));
653
+ }
654
+ console.log('Done!');`,
655
+
656
+ java: `public class Main {
657
+ public static void main(String[] args) {
658
+ System.out.println("Hello from Java!");
659
+ for (int i = 0; i < 5; i++) {
660
+ System.out.println("Step " + (i + 1));
661
+ }
662
+ System.out.println("Done!");
663
  }
664
+ }`,
665
+
666
+ ruby: `# Ruby example
667
+ puts "Hello from Ruby!"
668
+ 5.times do |i|
669
+ puts "Step #{i + 1}"
670
+ end
671
+ puts "Done!"`,
672
+
673
+ php: `<?php
674
+ echo "Hello from PHP!\\n";
675
+ for ($i = 1; $i <= 5; $i++) {
676
+ echo "Step $i\\n";
677
+ }
678
+ echo "Done!\\n";
679
+ ?>`,
680
+
681
+ go: `package main
682
+ import "fmt"
683
 
684
+ func main() {
685
+ fmt.Println("Hello from Go!")
686
+ for i := 1; i <= 5; i++ {
687
+ fmt.Printf("Step %d\\n", i)
688
+ }
689
+ fmt.Println("Done!")
690
+ }`,
691
+
692
+ rust: `fn main() {
693
+ println!("Hello from Rust!");
694
+ for i in 1..=5 {
695
+ println!("Step {}", i);
696
  }
697
+ println!("Done!");
698
+ }`,
699
+
700
+ cpp: `#include <iostream>
701
+ using namespace std;
702
 
703
+ int main() {
704
+ cout << "Hello from C++!" << endl;
705
+ for (int i = 1; i <= 5; i++) {
706
+ cout << "Step " << i << endl;
707
+ }
708
+ cout << "Done!" << endl;
709
+ return 0;
710
+ }`,
711
+
712
+ c: `#include <stdio.h>
713
+
714
+ int main() {
715
+ printf("Hello from C!\\n");
716
+ for (int i = 1; i <= 5; i++) {
717
+ printf("Step %d\\n", i);
718
+ }
719
+ printf("Done!\\n");
720
+ return 0;
721
+ }`,
722
+
723
+ csharp: `using System;
724
+
725
+ class Program {
726
+ static void Main() {
727
+ Console.WriteLine("Hello from C#!");
728
+ for (int i = 1; i <= 5; i++) {
729
+ Console.WriteLine($"Step {i}");
730
  }
731
+ Console.WriteLine("Done!");
732
+ }
733
+ }`,
734
+
735
+ swift: `print("Hello from Swift!")
736
+ for i in 1...5 {
737
+ print("Step \\(i)")
738
+ }
739
+ print("Done!")`,
740
+
741
+ kotlin: `fun main() {
742
+ println("Hello from Kotlin!")
743
+ for (i in 1..5) {
744
+ println("Step $i")
745
+ }
746
+ println("Done!")
747
+ }`,
748
+
749
+ perl: `# Perl example
750
+ print "Hello from Perl!\\n";
751
+ for my $i (1..5) {
752
+ print "Step $i\\n";
753
+ }
754
+ print "Done!\\n";`,
755
+
756
+ lua: `-- Lua example
757
+ print("Hello from Lua!")
758
+ for i = 1, 5 do
759
+ print("Step " .. i)
760
+ end
761
+ print("Done!")`,
762
+
763
+ bash: `#!/bin/bash
764
+ echo "Hello from Bash!"
765
+ for i in {1..5}; do
766
+ echo "Step $i"
767
+ done
768
+ echo "Done!"`,
769
+
770
+ r: `# R example
771
+ print("Hello from R!")
772
+ for (i in 1:5) {
773
+ print(paste("Step", i))
774
+ }
775
+ print("Done!")`,
776
+
777
+ scala: `object Main extends App {
778
+ println("Hello from Scala!")
779
+ for (i <- 1 to 5) {
780
+ println(s"Step $i")
781
  }
782
+ println("Done!")
783
+ }`,
784
+
785
+ html: `<!DOCTYPE html>
786
+ <html>
787
+ <head>
788
+ <style>
789
+ body { font-family: Arial; padding: 20px; }
790
+ h1 { color: #007acc; }
791
+ </style>
792
+ </head>
793
+ <body>
794
+ <h1>Hello from HTML!</h1>
795
+ <p>This is rendered client-side.</p>
796
+ <button onclick="alert('Clicked!')">Click Me</button>
797
+ </body>
798
+ </html>`,
799
+
800
+ react: `function App() {
801
+ const [count, setCount] = React.useState(0);
802
+
803
+ return (
804
+ <div style={{padding: '20px', fontFamily: 'Arial'}}>
805
+ <h1>Hello from React!</h1>
806
+ <p>Count: {count}</p>
807
+ <button onClick={() => setCount(count + 1)}>
808
+ Increment
809
+ </button>
810
+ </div>
811
+ );
812
+ }
813
+
814
+ ReactDOM.createRoot(document.getElementById('react-root')).render(<App />);`
815
+ };
816
+
817
+ function switchLanguage() {
818
+ const lang = languageSelect.value;
819
+ editor.value = examples[lang] || '';
820
+
821
+ // Hide all output areas
822
+ output.style.display = 'none';
823
+ htmlPreview.style.display = 'none';
824
+ reactRoot.style.display = 'none';
825
+
826
+ // Show appropriate output area
827
+ if (lang === 'html') {
828
+ htmlPreview.style.display = 'block';
829
+ } else if (lang === 'react') {
830
+ reactRoot.style.display = 'block';
831
+ } else {
832
+ output.style.display = 'block';
833
+ }
834
+
835
+ output.textContent = '';
836
+ status.textContent = '';
837
+ }
838
+
839
+ async function runCode() {
840
+ const lang = languageSelect.value;
841
+ const code = editor.value.trim();
842
+
843
+ if (!code) {
844
+ status.textContent = '⚠️ No code to run';
845
+ status.className = 'error';
846
+ return;
847
+ }
848
+
849
+ output.textContent = '';
850
+ status.textContent = 'πŸš€ Running...';
851
+ status.className = 'info';
852
+
853
+ if (lang === 'html') {
854
+ runHTML(code);
855
+ } else if (lang === 'react') {
856
+ runReact(code);
857
+ } else {
858
+ await runServerSide(lang, code);
859
+ }
860
+ }
861
+
862
+ function runHTML(code) {
863
+ try {
864
+ const clean = DOMPurify.sanitize(code);
865
+ htmlPreview.srcdoc = clean;
866
+ status.textContent = 'βœ“ Rendered';
867
+ status.className = 'success';
868
+ } catch (error) {
869
+ output.style.display = 'block';
870
+ htmlPreview.style.display = 'none';
871
+ output.textContent = 'Error: ' + error.message;
872
+ status.textContent = '❌ Error';
873
+ status.className = 'error';
874
+ }
875
+ }
876
+
877
+ function runReact(code) {
878
+ try {
879
+ reactRoot.innerHTML = '';
880
+ const transformed = Babel.transform(code, {
881
+ presets: ['react']
882
+ }).code;
883
+ eval(transformed);
884
+ status.textContent = 'βœ“ Rendered';
885
+ status.className = 'success';
886
+ } catch (error) {
887
+ reactRoot.innerHTML = '<div style="padding: 20px; color: red; font-family: monospace;">' +
888
+ '<strong>Error:</strong><br>' + error.message + '</div>';
889
+ status.textContent = '❌ Error';
890
+ status.className = 'error';
891
+ }
892
+ }
893
+
894
+ async function runServerSide(lang, code) {
895
+ runBtn.disabled = true;
896
+ editor.disabled = true;
897
+ output.textContent = '';
898
+
899
+ try {
900
+ const response = await fetch('/run', {
901
+ method: 'POST',
902
+ headers: { 'Content-Type': 'application/json' },
903
+ body: JSON.stringify({ language: lang, code: code })
904
+ });
905
+
906
+ if (!response.ok) {
907
+ const error = await response.json();
908
+ output.textContent = 'Error: ' + (error.error || 'Unknown error');
909
+ status.textContent = '❌ Error';
910
+ status.className = 'error';
911
+ runBtn.disabled = false;
912
+ editor.disabled = false;
913
+ return;
914
+ }
915
+
916
+ const reader = response.body.getReader();
917
+ const decoder = new TextDecoder();
918
+
919
+ while (true) {
920
+ const { value, done } = await reader.read();
921
+ if (done) break;
922
+
923
+ const chunk = decoder.decode(value, { stream: true });
924
+ output.textContent += chunk;
925
+ output.scrollTop = output.scrollHeight;
926
+
927
+ if (chunk.includes('done')) {
928
+ status.textContent = 'βœ“ Complete (Space restarting...)';
929
+ status.className = 'success';
930
+ }
931
+ }
932
+
933
+ } catch (error) {
934
+ output.textContent += '\\n\\nConnection error: ' + error.message;
935
+ status.textContent = '❌ Error';
936
+ status.className = 'error';
937
+ } finally {
938
+ runBtn.disabled = false;
939
+ editor.disabled = false;
940
+ }
941
+ }
942
+
943
+ // Tab key support
944
+ editor.addEventListener('keydown', (e) => {
945
+ if (e.key === 'Tab') {
946
+ e.preventDefault();
947
+ const start = editor.selectionStart;
948
+ const end = editor.selectionEnd;
949
+ editor.value = editor.value.substring(0, start) + ' ' + editor.value.substring(end);
950
+ editor.selectionStart = editor.selectionEnd = start + 4;
951
+ }
952
+ });
953
+
954
+ // Ctrl+Enter to run
955
+ editor.addEventListener('keydown', (e) => {
956
+ if (e.ctrlKey && e.key === 'Enter') {
957
+ e.preventDefault();
958
+ runCode();
959
+ }
960
+ });
961
+
962
+ // Initialize
963
+ switchLanguage();
964
+ </script>
965
+ </body>
966
+ </html>
967
+ """
968
+ return HTMLResponse(content=html_content)
969
+
970
 
971
  if __name__ == "__main__":
972
  import uvicorn