mic3333 commited on
Commit
81b031b
·
verified ·
1 Parent(s): 0e64819

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +215 -615
app.py CHANGED
@@ -1,649 +1,249 @@
1
  #!/usr/bin/env python3
2
  """
3
- LibreChat Custom Code Interpreter - HuggingFace Space
4
- A secure code execution server using Gradio + MCP with SSE support
5
  """
6
 
7
- import json
8
- import subprocess
9
- import tempfile
10
- import os
11
- import sys
12
- import uuid
13
- import time
14
- import base64
15
- import io
16
- import re
17
- from pathlib import Path
18
- from typing import Dict, Any, Optional, List
19
  import gradio as gr
20
- import matplotlib
21
- matplotlib.use('Agg') # Use non-interactive backend
22
- import matplotlib.pyplot as plt
23
- import numpy as np
24
- import pandas as pd
25
- import base64
26
- from PIL import Image
27
- import io
28
- import gradio as gr
29
- from pyodide import loadPackage
30
- import js
31
- import asyncio
32
-
33
- class SecureCodeExecutor:
34
- def __init__(self):
35
- self.sessions = {}
36
- self.max_execution_time = 30
37
- self.max_output_length = 10000
38
- self.allowed_languages = ["python", "javascript", "bash"]
39
-
40
- # Security: List of blocked commands/imports
41
- self.blocked_imports = [
42
- 'subprocess', 'os', 'sys', 'shutil', 'glob', 'pickle',
43
- 'marshal', 'imp', 'importlib', '__import__'
44
- ]
45
- self.blocked_bash_commands = [
46
- 'rm', 'sudo', 'chmod', 'chown', 'dd', 'mkfs', 'fdisk',
47
- 'curl', 'wget', 'ssh', 'scp', 'nc', 'netcat'
48
- ]
49
-
50
- def create_session(self) -> str:
51
- """Create a new execution session"""
52
- session_id = str(uuid.uuid4())[:8] # Shorter ID for HF
53
- self.sessions[session_id] = {
54
- 'created_at': time.time(),
55
- 'variables': {},
56
- 'history': [],
57
- 'files': {}
58
- }
59
- return session_id
60
-
61
- def cleanup_old_sessions(self):
62
- """Remove sessions older than 1 hour"""
63
- current_time = time.time()
64
- old_sessions = [
65
- sid for sid, session in self.sessions.items()
66
- if current_time - session['created_at'] > 3600
67
- ]
68
- for sid in old_sessions:
69
- del self.sessions[sid]
70
-
71
- def is_code_safe(self, code: str, language: str) -> tuple[bool, str]:
72
- """Check if code is safe to execute using whole-word matching."""
73
- if language == "python":
74
- # Use regex to find whole-word matches for blocked imports
75
- for blocked in self.blocked_imports:
76
- # \b matches a word boundary. This prevents 'imp' from matching 'import'
77
- pattern = r'\b' + re.escape(blocked) + r'\b'
78
- if re.search(pattern, code):
79
- return False, f"Blocked import/function: {blocked}"
80
-
81
- # Check for dangerous patterns that are not whole words
82
- dangerous_patterns = ['exec(', 'eval(', 'open(', 'file(', '__']
83
- for pattern in dangerous_patterns:
84
- if pattern in code:
85
- return False, f"Dangerous pattern detected: {pattern}"
86
-
87
- elif language == "bash":
88
- # Use regex to find whole-word matches for blocked commands
89
- for blocked in self.blocked_bash_commands:
90
- pattern = r'\b' + re.escape(blocked) + r'\b'
91
- if re.search(pattern, code.lower()):
92
- return False, f"Blocked command: {blocked}"
93
 
94
- return True, ""
95
-
96
- def execute_python_code(self, code: str, session_id: Optional[str] = None) -> Dict[str, Any]:
97
- """Execute Python code with visualization support"""
98
- # Security check
99
- is_safe, reason = self.is_code_safe(code, "python")
100
- if not is_safe:
101
- return {
102
- "success": False,
103
- "stdout": "",
104
- "stderr": f"Security violation: {reason}",
105
- "execution_time": time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  }
107
-
108
- # Prepare execution environment
109
- setup_code = '''
110
- import matplotlib
111
- matplotlib.use('Agg')
112
- import matplotlib.pyplot as plt
113
- import numpy as np
114
- import pandas as pd
115
- import json
116
- import math
117
- import random
118
- import base64
119
- import io
120
- from datetime import datetime, timedelta
121
-
122
- # Custom print function to capture output
123
- _output_buffer = []
124
- _original_print = print
125
- def print(*args, **kwargs):
126
- _output_buffer.append(' '.join(str(arg) for arg in args))
127
-
128
- # Function to save plots as base64
129
- def save_current_plot():
130
- if plt.get_fignums(): # Check if there are any figures
131
- buffer = io.BytesIO()
132
- plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100)
133
- buffer.seek(0)
134
- plot_data = buffer.getvalue()
135
- buffer.close()
136
- return base64.b64encode(plot_data).decode()
137
- return None
138
- '''
139
-
140
- # Combine setup and user code
141
- full_code = setup_code + "\n" + code
142
 
143
- # Finalization code to capture plot and print the buffer to real stdout
144
- finalization_code = '''
145
- # Capture the plot if one exists and print it directly to stdout for the parent process
146
- _plot_data = save_current_plot()
147
- if _plot_data:
148
- _original_print('PLOT_DATA:' + _plot_data)
149
-
150
- # Print everything from the custom print buffer to stdout
151
- if _output_buffer:
152
- _original_print('\\n'.join(_output_buffer))
153
- '''
154
- full_code += "\n" + finalization_code
155
-
156
- try:
157
- # Create temporary file
158
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
159
- f.write(full_code)
160
- temp_file = f.name
161
-
162
- # Execute with timeout
163
- result = subprocess.run(
164
- [sys.executable, temp_file],
165
- capture_output=True,
166
- text=True,
167
- timeout=self.max_execution_time,
168
- cwd=tempfile.gettempdir()
169
- )
170
-
171
- # Process output
172
- stdout = result.stdout
173
- stderr = result.stderr
174
- plot_data = None
175
-
176
- # Extract plot data if present
177
- if 'PLOT_DATA:' in stdout:
178
- lines = stdout.split('\n')
179
- clean_lines = []
180
- for line in lines:
181
- if line.startswith('PLOT_DATA:'):
182
- plot_data = line.replace('PLOT_DATA:', '')
183
- else:
184
- clean_lines.append(line)
185
- stdout = '\n'.join(clean_lines)
186
-
187
- # Limit output length
188
- if len(stdout) > self.max_output_length:
189
- stdout = stdout[:self.max_output_length] + "\n... (output truncated)"
190
-
191
- execution_result = {
192
- "success": result.returncode == 0,
193
- "stdout": stdout.strip(),
194
- "stderr": stderr.strip() if stderr else "",
195
- "execution_time": time.time(),
196
- "return_code": result.returncode
197
- }
198
-
199
- if plot_data:
200
- execution_result["plot"] = plot_data
201
-
202
- return execution_result
203
-
204
- except subprocess.TimeoutExpired:
205
- return {
206
- "success": False,
207
- "stdout": "",
208
- "stderr": "Execution timed out (30s limit)",
209
- "execution_time": time.time()
210
- }
211
- except Exception as e:
212
- return {
213
- "success": False,
214
- "stdout": "",
215
- "stderr": str(e),
216
- "execution_time": time.time()
217
- }
218
- finally:
219
- if 'temp_file' in locals():
220
- try:
221
- os.unlink(temp_file)
222
- except:
223
- pass
224
-
225
- def execute_javascript_code(self, code: str, session_id: Optional[str] = None) -> Dict[str, Any]:
226
- """Execute JavaScript code using Node.js"""
227
- # Security check
228
- is_safe, reason = self.is_code_safe(code, "javascript")
229
- if not is_safe:
230
- return {
231
- "success": False,
232
- "stdout": "",
233
- "stderr": f"Security violation: {reason}",
234
- "execution_time": time.time()
235
- }
236
-
237
- try:
238
- with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
239
- f.write(code)
240
- temp_file = f.name
241
-
242
- result = subprocess.run(
243
- ['node', temp_file],
244
- capture_output=True,
245
- text=True,
246
- timeout=self.max_execution_time
247
- )
248
-
249
- stdout = result.stdout
250
- if len(stdout) > self.max_output_length:
251
- stdout = stdout[:self.max_output_length] + "\n... (output truncated)"
252
-
253
- return {
254
- "success": result.returncode == 0,
255
- "stdout": stdout.strip(),
256
- "stderr": result.stderr.strip() if result.stderr else "",
257
- "execution_time": time.time(),
258
- "return_code": result.returncode
259
- }
260
-
261
- except subprocess.TimeoutExpired:
262
- return {
263
- "success": False,
264
- "stdout": "",
265
- "stderr": "Execution timed out (30s limit)",
266
- "execution_time": time.time()
267
- }
268
- except Exception as e:
269
- return {
270
- "success": False,
271
- "stdout": "",
272
- "stderr": str(e),
273
- "execution_time": time.time()
274
- }
275
- finally:
276
- if 'temp_file' in locals():
277
- try:
278
- os.unlink(temp_file)
279
- except:
280
- pass
281
-
282
- def execute_bash_command(self, command: str, session_id: Optional[str] = None) -> Dict[str, Any]:
283
- """Execute bash commands with security restrictions"""
284
- # Security check
285
- is_safe, reason = self.is_code_safe(command, "bash")
286
- if not is_safe:
287
- return {
288
- "success": False,
289
- "stdout": "",
290
- "stderr": f"Security violation: {reason}",
291
- "execution_time": time.time()
292
- }
293
-
294
- try:
295
- result = subprocess.run(
296
- command,
297
- shell=True,
298
- capture_output=True,
299
- text=True,
300
- timeout=self.max_execution_time,
301
- cwd=tempfile.gettempdir()
302
- )
303
-
304
- stdout = result.stdout
305
- if len(stdout) > self.max_output_length:
306
- stdout = stdout[:self.max_output_length] + "\n... (output truncated)"
307
-
308
- return {
309
- "success": result.returncode == 0,
310
- "stdout": stdout.strip(),
311
- "stderr": result.stderr.strip() if result.stderr else "",
312
- "execution_time": time.time(),
313
- "return_code": result.returncode
314
- }
315
-
316
- except subprocess.TimeoutExpired:
317
- return {
318
- "success": False,
319
- "stdout": "",
320
- "stderr": "Command timed out (30s limit)",
321
- "execution_time": time.time()
322
- }
323
- except Exception as e:
324
- return {
325
- "success": False,
326
- "stdout": "",
327
- "stderr": str(e),
328
- "execution_time": time.time()
329
  }
330
-
331
- def execute_code(self, code: str, language: str = "python", session_id: Optional[str] = None) -> str:
332
- """Main execution function - returns JSON for MCP compatibility"""
333
- # Cleanup old sessions periodically
334
- if len(self.sessions) > 10:
335
- self.cleanup_old_sessions()
336
-
337
- if language not in self.allowed_languages:
338
- return json.dumps({
339
- "success": False,
340
- "error": f"Language '{language}' not supported. Allowed: {', '.join(self.allowed_languages)}"
341
- })
342
-
343
- # Create session if needed
344
- if session_id and session_id not in self.sessions:
345
- session_id = self.create_session()
346
- elif not session_id:
347
- session_id = self.create_session()
348
-
349
- # Execute based on language
350
- if language == "python":
351
- result = self.execute_python_code(code, session_id)
352
- elif language == "javascript":
353
- result = self.execute_javascript_code(code, session_id)
354
- elif language == "bash":
355
- result = self.execute_bash_command(code, session_id)
356
- else:
357
- result = {
358
- "success": False,
359
- "error": f"Execution handler for {language} not implemented"
360
- }
361
-
362
- # Store in session history
363
- if session_id in self.sessions:
364
- self.sessions[session_id]['history'].append({
365
- 'code': code,
366
- 'language': language,
367
- 'result': result,
368
- 'timestamp': time.time()
369
- })
370
-
371
- result['session_id'] = session_id
372
- return json.dumps(result, indent=2)
373
-
374
- # Global executor instance
375
- executor = SecureCodeExecutor()
376
-
377
- # MCP Functions
378
- # Replace the execute_python_code function with this enhanced version
379
- def execute_python_code(code: str, session_id: str = None) -> str:
380
- """
381
- Execute Python code safely with visualization support.
382
-
383
- Args:
384
- code (str): Python code to execute
385
- session_id (str, optional): Session ID for persistent context
386
-
387
- Returns:
388
- str: JSON string with execution results, including image data for LibreChat
389
- """
390
- result_json = executor.execute_code(code, "python", session_id)
391
- result = json.loads(result_json)
392
-
393
- # If there's a plot, we need to format it specially for LibreChat
394
- if result.get("plot"):
395
- plot_base64 = result["plot"]
396
-
397
- # Create a response that LibreChat can understand
398
- # Include both the text output and indicate there's an image
399
- response = {
400
- "success": result["success"],
401
- "stdout": result.get("stdout", ""),
402
- "stderr": result.get("stderr", ""),
403
- "session_id": result.get("session_id"),
404
- "execution_time": result.get("execution_time"),
405
- # Add image information
406
- "images": [
407
- {
408
- "type": "image/png",
409
- "data": plot_base64,
410
- "description": "Generated matplotlib plot"
411
  }
412
- ],
413
- # Also include a message about the image
414
- "message": f"{result.get('stdout', '')}\n\n📊 Generated visualization (matplotlib plot)"
 
 
 
 
415
  }
416
 
417
- return json.dumps(response, indent=2)
418
-
419
- # If no plot, return the original result
420
- return result_json
421
-
422
- def execute_javascript_code(code: str, session_id: str = None) -> str:
423
- """
424
- Execute JavaScript code using Node.js.
425
-
426
- Args:
427
- code (str): JavaScript code to execute
428
- session_id (str, optional): Session ID for persistent context
429
-
430
- Returns:
431
- str: JSON string with execution results
432
- """
433
- return executor.execute_code(code, "javascript", session_id)
434
-
435
- def execute_bash_command(command: str, session_id: str = None) -> str:
436
- """
437
- Execute bash commands with security restrictions.
438
-
439
- Args:
440
- command (str): Bash command to execute
441
- session_id (str, optional): Session ID for persistent context
442
-
443
- Returns:
444
- str: JSON string with execution results
445
- """
446
- return executor.execute_code(command, "bash", session_id)
447
-
448
- def create_execution_session() -> str:
449
- """
450
- Create a new execution session for maintaining state.
451
-
452
- Returns:
453
- str: JSON string containing new session ID
454
- """
455
- session_id = executor.create_session()
456
- return json.dumps({"session_id": session_id, "created_at": time.time()})
457
-
458
- def list_execution_sessions() -> str:
459
- """
460
- List all active execution sessions.
461
-
462
- Returns:
463
- str: JSON string containing session information
464
- """
465
- return json.dumps({
466
- "sessions": list(executor.sessions.keys()),
467
- "count": len(executor.sessions),
468
- "timestamp": time.time()
469
- })
470
-
471
- def get_execution_history(session_id: str) -> str:
472
- """
473
- Get execution history for a specific session.
474
-
475
- Args:
476
- session_id (str): Session ID to get history for
477
-
478
- Returns:
479
- str: JSON string containing execution history
480
- """
481
- if session_id not in executor.sessions:
482
- return json.dumps({"error": "Session not found"})
483
-
484
- return json.dumps({
485
- "session_id": session_id,
486
- "history": executor.sessions[session_id]['history'],
487
- "created_at": executor.sessions[session_id]['created_at']
488
- })
489
-
490
- def get_system_info() -> str:
491
- """
492
- Get system information and available packages.
493
-
494
- Returns:
495
- str: JSON string containing system information
496
- """
497
- return json.dumps({
498
- "python_version": sys.version,
499
- "available_packages": [
500
- "numpy", "pandas", "matplotlib", "json", "math",
501
- "random", "datetime", "base64", "io"
502
- ],
503
- "execution_limits": {
504
- "max_time": executor.max_execution_time,
505
- "max_output": executor.max_output_length
506
- },
507
- "supported_languages": executor.allowed_languages
508
- })
509
-
510
- # Replace the gradio_execute_code function with this improved version
511
- def gradio_execute_code(code: str, language: str, session_id: str = ""):
512
- """Gradio interface for code execution with proper image handling"""
513
- if not session_id:
514
- session_id = None
515
-
516
- result_json = executor.execute_code(code, language.lower(), session_id)
517
- result = json.loads(result_json)
518
-
519
- output = ""
520
- plot_image = None
521
-
522
- if result.get("success"):
523
- if result.get("stdout"):
524
- output += f"Output:\n{result['stdout']}\n\n"
525
- if result.get("stderr"):
526
- output += f"Warnings:\n{result['stderr']}\n\n"
527
 
528
- # Handle plot data properly
529
- if result.get("plot"):
530
- import base64
531
- from PIL import Image
532
- import io
533
-
534
- try:
535
- # Decode base64 to image
536
- plot_data = base64.b64decode(result["plot"])
537
- plot_image = Image.open(io.BytesIO(plot_data))
538
- output += f"📊 Plot generated successfully!\n\n"
539
- except Exception as e:
540
- output += f"⚠️ Plot generated but couldn't display: {str(e)}\n\n"
541
- else:
542
- output += f"❌ Error:\n{result.get('stderr', result.get('error', 'Unknown error'))}\n\n"
543
-
544
- output += f"🔑 Session ID: {result.get('session_id', 'N/A')}"
545
 
546
- return output, plot_image
547
 
548
- # Replace the Gradio interface section with this improved version
549
- with gr.Blocks(title="LibreChat Code Interpreter") as demo:
550
- gr.Markdown("# LibreChat Code Interpreter")
551
- gr.Markdown("Execute Python, JavaScript, and Bash code safely through MCP integration.")
 
 
 
 
 
 
 
552
 
553
  with gr.Row():
554
  with gr.Column():
555
  code_input = gr.Textbox(
556
- placeholder="Enter your code here...",
557
- lines=10,
558
- label="Code"
559
- )
560
- language_dropdown = gr.Dropdown(
561
- choices=["Python", "JavaScript", "Bash"],
562
- value="Python",
563
- label="Language"
564
- )
565
- session_input = gr.Textbox(
566
- placeholder="Optional: Session ID for persistent context",
567
- label="Session ID"
568
- )
569
- execute_btn = gr.Button("Execute Code", variant="primary")
570
-
571
- with gr.Column():
572
- output_display = gr.Textbox(
573
- lines=10,
574
- label="Execution Result",
575
- interactive=False
576
- )
577
- # Add image output for plots
578
- plot_display = gr.Image(
579
- label="Generated Plot",
580
- visible=True,
581
- type="pil"
582
- )
583
-
584
- # Examples section remains the same...
585
- gr.Markdown("## Examples")
586
-
587
- example_python = gr.Code("""
588
  import matplotlib.pyplot as plt
589
  import numpy as np
590
-
591
- # Generate sample data
592
  x = np.linspace(0, 10, 100)
593
- y = np.sin(x) * np.exp(-x/10)
594
-
595
- # Create plot
596
  plt.figure(figsize=(10, 6))
597
- plt.plot(x, y, 'b-', linewidth=2, label='Damped Sine Wave')
598
- plt.title('Example Visualization')
599
  plt.xlabel('X values')
600
  plt.ylabel('Y values')
601
- plt.legend()
602
  plt.grid(True, alpha=0.3)
603
  plt.show()
604
-
605
- print("Visualization created successfully!")
606
- """, language="python", label="Python Example with Visualization")
607
-
608
- example_js = gr.Code("""
609
- // Data processing example
610
- const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
611
-
612
- const sum = data.reduce((acc, val) => acc + val, 0);
613
- const mean = sum / data.length;
614
- const variance = data.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / data.length;
615
- const stdDev = Math.sqrt(variance);
616
-
617
- console.log(`Dataset: [${data.join(', ')}]`);
618
- console.log(`Sum: ${sum}`);
619
- console.log(`Mean: ${mean}`);
620
- console.log(`Standard Deviation: ${stdDev.toFixed(3)}`);
621
-
622
- // JSON processing
623
- const result = {
624
- dataset: data,
625
- statistics: {
626
- sum, mean, variance, stdDev
627
- },
628
- timestamp: new Date().toISOString()
629
- };
630
-
631
- console.log('\\nResult:');
632
- console.log(JSON.stringify(result, null, 2));
633
- """, language="javascript", label="JavaScript Example")
634
 
635
- # Updated click handler to return both outputs
636
  execute_btn.click(
637
- fn=gradio_execute_code,
638
- inputs=[code_input, language_dropdown, session_input],
639
- outputs=[output_display, plot_display] # Now returns both text and image
 
 
 
 
 
 
 
 
 
640
  )
641
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
  if __name__ == "__main__":
643
- # Launch with MCP server enabled
644
  demo.launch(
645
- mcp_server=True,
646
- share=False,
647
  server_name="0.0.0.0",
648
- server_port=7860
 
649
  )
 
1
  #!/usr/bin/env python3
2
  """
3
+ LibreChat Pyodide Code Interpreter - Client-Side Python Execution
 
4
  """
5
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  import gradio as gr
7
+
8
+ def create_pyodide_interface():
9
+ """Create a Gradio interface that uses Pyodide for client-side Python execution"""
10
+
11
+ # Custom HTML/JS that loads and uses Pyodide
12
+ pyodide_html = """
13
+ <div id="pyodide-container">
14
+ <div id="pyodide-status">Loading Pyodide...</div>
15
+ <div id="pyodide-output" style="display:none;">
16
+ <pre id="output-text"></pre>
17
+ <div id="plot-container"></div>
18
+ </div>
19
+ </div>
20
+
21
+ <script src="https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js"></script>
22
+ <script>
23
+ let pyodide = null;
24
+ let plotCounter = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ async function initPyodide() {
27
+ try {
28
+ pyodide = await loadPyodide();
29
+ await pyodide.loadPackage(["numpy", "matplotlib", "pandas"]);
30
+
31
+ // Set up matplotlib backend for web
32
+ pyodide.runPython(`
33
+ import matplotlib
34
+ matplotlib.use('AGG')
35
+ import matplotlib.pyplot as plt
36
+ import numpy as np
37
+ import pandas as pd
38
+ import io
39
+ import base64
40
+
41
+ def show_plot():
42
+ buffer = io.BytesIO()
43
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100)
44
+ buffer.seek(0)
45
+ plot_data = buffer.getvalue()
46
+ buffer.close()
47
+ plt.close()
48
+ return base64.b64encode(plot_data).decode()
49
+
50
+ # Override plt.show() to capture plots
51
+ original_show = plt.show
52
+ def custom_show(*args, **kwargs):
53
+ return show_plot()
54
+ plt.show = custom_show
55
+ `);
56
+
57
+ document.getElementById('pyodide-status').textContent = 'Pyodide ready!';
58
+ document.getElementById('pyodide-status').style.color = 'green';
59
+ } catch (error) {
60
+ document.getElementById('pyodide-status').textContent = 'Error loading Pyodide: ' + error;
61
+ document.getElementById('pyodide-status').style.color = 'red';
62
  }
63
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ async function executePyodideCode(code) {
66
+ if (!pyodide) {
67
+ return "Pyodide not ready yet. Please wait...";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
+
70
+ try {
71
+ // Capture stdout
72
+ pyodide.runPython(`
73
+ import sys
74
+ from io import StringIO
75
+ old_stdout = sys.stdout
76
+ sys.stdout = captured_output = StringIO()
77
+ `);
78
+
79
+ // Execute user code
80
+ let result = pyodide.runPython(code);
81
+
82
+ // Get captured output
83
+ let stdout = pyodide.runPython(`
84
+ sys.stdout = old_stdout
85
+ captured_output.getvalue()
86
+ `);
87
+
88
+ // Check for plots
89
+ let plotData = null;
90
+ try {
91
+ plotData = pyodide.runPython(`
92
+ if plt.get_fignums():
93
+ show_plot()
94
+ else:
95
+ None
96
+ `);
97
+ } catch (e) {
98
+ // No plot generated
99
+ }
100
+
101
+ // Display results
102
+ let outputDiv = document.getElementById('pyodide-output');
103
+ let outputText = document.getElementById('output-text');
104
+ let plotContainer = document.getElementById('plot-container');
105
+
106
+ outputDiv.style.display = 'block';
107
+
108
+ let output = stdout || (result !== undefined ? String(result) : '');
109
+ outputText.textContent = output || 'Code executed successfully (no output)';
110
+
111
+ // Display plot if generated
112
+ if (plotData) {
113
+ plotContainer.innerHTML = `<img src="data:image/png;base64,${plotData}" style="max-width: 100%; height: auto;" alt="Generated Plot">`;
114
+ outputText.textContent += '\n\n📊 Plot generated successfully!';
115
+ } else {
116
+ plotContainer.innerHTML = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  }
118
+
119
+ return output + (plotData ? '\n📊 Plot displayed below' : '');
120
+
121
+ } catch (error) {
122
+ document.getElementById('output-text').textContent = 'Error: ' + error.toString();
123
+ return 'Error: ' + error.toString();
124
+ }
125
  }
126
 
127
+ // Initialize Pyodide when page loads
128
+ initPyodide();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
+ // Make function globally available
131
+ window.executePyodideCode = executePyodideCode;
132
+ </script>
133
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
+ return pyodide_html
136
 
137
+ def python_executor_fallback(code):
138
+ """Fallback Python executor for server-side processing"""
139
+ return "Pyodide is client-side only. Use the interface above to execute Python code in your browser."
140
+
141
+ # Create the Gradio interface
142
+ with gr.Blocks(title="LibreChat Pyodide Code Interpreter") as demo:
143
+ gr.Markdown("# LibreChat Pyodide Code Interpreter")
144
+ gr.Markdown("**Client-side Python execution** using Pyodide - Python runs directly in your browser!")
145
+
146
+ # Pyodide interface
147
+ pyodide_interface = gr.HTML(create_pyodide_interface())
148
 
149
  with gr.Row():
150
  with gr.Column():
151
  code_input = gr.Textbox(
152
+ placeholder="""# Try this example:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  import matplotlib.pyplot as plt
154
  import numpy as np
155
+
 
156
  x = np.linspace(0, 10, 100)
157
+ y = np.sin(x)
158
+
 
159
  plt.figure(figsize=(10, 6))
160
+ plt.plot(x, y, 'b-', linewidth=2)
161
+ plt.title('Sine Wave - Generated with Pyodide!')
162
  plt.xlabel('X values')
163
  plt.ylabel('Y values')
 
164
  plt.grid(True, alpha=0.3)
165
  plt.show()
166
+
167
+ print("Plot created with client-side Python!")""",
168
+ lines=15,
169
+ label="Python Code (executes in your browser)"
170
+ )
171
+
172
+ execute_btn = gr.Button("Execute with Pyodide", variant="primary")
173
+
174
+ with gr.Column():
175
+ gr.Markdown("### Results will appear above in the Pyodide container")
176
+ status_display = gr.Textbox(
177
+ label="Execution Status",
178
+ interactive=False,
179
+ lines=3
180
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
+ # JavaScript execution
183
  execute_btn.click(
184
+ fn=None, # No Python function needed
185
+ inputs=[code_input],
186
+ outputs=[status_display],
187
+ js="""
188
+ function(code) {
189
+ if (window.executePyodideCode) {
190
+ return window.executePyodideCode(code);
191
+ } else {
192
+ return "Pyodide not ready yet. Please wait for initialization.";
193
+ }
194
+ }
195
+ """
196
  )
197
+
198
+ # Examples
199
+ gr.Markdown("## Examples")
200
+
201
+ gr.Code("""
202
+ # Data Analysis Example
203
+ import pandas as pd
204
+ import numpy as np
205
+
206
+ # Create sample data
207
+ data = {
208
+ 'Name': ['Alice', 'Bob', 'Charlie', 'Diana'],
209
+ 'Age': [25, 30, 35, 28],
210
+ 'Score': [85, 92, 78, 96]
211
+ }
212
+
213
+ df = pd.DataFrame(data)
214
+ print("Sample DataFrame:")
215
+ print(df)
216
+ print(f"\\nAverage Score: {df['Score'].mean():.1f}")
217
+ """, language="python", label="Data Analysis with Pandas")
218
+
219
+ gr.Code("""
220
+ # Visualization Example
221
+ import matplotlib.pyplot as plt
222
+ import numpy as np
223
+
224
+ # Generate data
225
+ categories = ['A', 'B', 'C', 'D', 'E']
226
+ values = np.random.randint(10, 50, 5)
227
+
228
+ # Create bar chart
229
+ plt.figure(figsize=(10, 6))
230
+ bars = plt.bar(categories, values, color=['red', 'blue', 'green', 'orange', 'purple'])
231
+ plt.title('Random Bar Chart - Pyodide Demo')
232
+ plt.xlabel('Categories')
233
+ plt.ylabel('Values')
234
+
235
+ # Add value labels on bars
236
+ for bar, value in zip(bars, values):
237
+ plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
238
+ str(value), ha='center', va='bottom')
239
+
240
+ plt.show()
241
+ print(f"Generated chart with values: {values}")
242
+ """, language="python", label="Matplotlib Visualization")
243
+
244
  if __name__ == "__main__":
 
245
  demo.launch(
 
 
246
  server_name="0.0.0.0",
247
+ server_port=7860,
248
+ share=False
249
  )