mic3333 commited on
Commit
a791ab7
Β·
verified Β·
1 Parent(s): 944bc86

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +223 -420
app.py CHANGED
@@ -1,7 +1,6 @@
1
  #!/usr/bin/env python3
2
  """
3
- LibreChat Pyodide Code Interpreter - Client-Side Python Execution
4
- Enhanced version with better error handling and debugging
5
  """
6
 
7
  import gradio as gr
@@ -9,7 +8,7 @@ import gradio as gr
9
  def create_pyodide_interface():
10
  """Create a Gradio interface that uses Pyodide for client-side Python execution"""
11
 
12
- # Enhanced HTML/JS with comprehensive error handling and debugging
13
  pyodide_html = """
14
  <div id="pyodide-container" style="border: 1px solid #ddd; padding: 15px; border-radius: 5px; margin: 10px 0;">
15
  <div id="pyodide-status" style="font-weight: bold; padding: 10px; background: #f0f0f0; border-radius: 3px;">
@@ -26,43 +25,21 @@ def create_pyodide_interface():
26
  </div>
27
  </div>
28
 
29
- <script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"
30
- onerror="handleCDNError()"
31
- onload="console.log('Pyodide CDN loaded successfully')"></script>
32
-
33
  <script>
 
34
  let pyodide = null;
35
  let pyodideReady = false;
36
  let initializationStarted = false;
37
  let debugMode = true;
38
 
39
- function handleCDNError() {
40
- updateStatus('❌ Failed to load Pyodide from CDN', 'red');
41
- debugLog('CDN load failed - check network or try different browser');
42
- tryFallbackCDN();
43
- }
44
-
45
- function tryFallbackCDN() {
46
- debugLog('Attempting fallback CDN...');
47
- const fallbackScript = document.createElement('script');
48
- fallbackScript.src = 'https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js';
49
- fallbackScript.onload = () => {
50
- debugLog('Fallback CDN loaded successfully');
51
- safeInitPyodide();
52
- };
53
- fallbackScript.onerror = () => {
54
- updateStatus('❌ All CDN sources failed - check internet connection', 'red');
55
- debugLog('All CDN attempts failed');
56
- };
57
- document.head.appendChild(fallbackScript);
58
- }
59
-
60
- function updateStatus(message, color = 'black') {
61
  const statusDiv = document.getElementById('pyodide-status');
62
- statusDiv.innerHTML = message;
63
- statusDiv.style.color = color;
 
 
64
  console.log('Pyodide Status:', message);
65
- debugLog(`Status: ${message}`);
66
  }
67
 
68
  function debugLog(message) {
@@ -70,460 +47,301 @@ def create_pyodide_interface():
70
  console.log('DEBUG:', message);
71
  const debugDiv = document.getElementById('debug-info');
72
  const debugText = document.getElementById('debug-text');
73
- debugDiv.style.display = 'block';
74
- debugText.innerHTML += '<br>' + new Date().toLocaleTimeString() + ': ' + message;
75
- }
76
-
77
- async function testBrowserCompatibility() {
78
- debugLog('Testing browser compatibility...');
79
-
80
- // Test WebAssembly support
81
- if (typeof WebAssembly === 'undefined') {
82
- throw new Error('WebAssembly not supported in this browser');
83
- }
84
- debugLog('βœ“ WebAssembly supported');
85
-
86
- // Test fetch API
87
- if (typeof fetch === 'undefined') {
88
- throw new Error('Fetch API not supported');
89
- }
90
- debugLog('βœ“ Fetch API supported');
91
-
92
- // Test Promise support
93
- if (typeof Promise === 'undefined') {
94
- throw new Error('Promises not supported');
95
  }
96
- debugLog('βœ“ Promises supported');
97
-
98
- debugLog('Browser compatibility check passed');
99
  }
100
 
101
  async function initPyodide() {
102
  if (initializationStarted) {
103
- debugLog('Initialization already started, skipping');
104
  return;
105
  }
106
  initializationStarted = true;
107
 
108
  try {
109
- // Browser compatibility check
110
- await testBrowserCompatibility();
111
-
112
  // Check if loadPyodide is available
113
  if (typeof loadPyodide === 'undefined') {
114
- throw new Error('loadPyodide function not available - CDN loading failed');
115
  }
116
- debugLog('loadPyodide function is available');
117
 
118
- updateStatus('πŸ”„ Initializing Pyodide core...', 'blue');
 
119
 
120
  pyodide = await loadPyodide({
121
- indexURL: "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/",
122
- fullStdLib: false // Faster loading
123
  });
124
 
125
- debugLog('Pyodide core initialized successfully');
126
- updateStatus('πŸ“¦ Loading Python packages...', 'blue');
127
 
128
- // Load packages with individual error handling
129
- const packages = ['numpy', 'matplotlib', 'pandas'];
130
- for (const pkg of packages) {
 
 
 
 
131
  try {
132
- debugLog(`Loading ${pkg}...`);
133
- await pyodide.loadPackage(pkg);
134
- debugLog(`βœ“ ${pkg} loaded successfully`);
135
- } catch (error) {
136
- debugLog(`⚠ Failed to load ${pkg}: ${error.message}`);
137
- // Continue with other packages
138
  }
139
  }
140
 
141
- updateStatus('πŸ”§ Setting up Python environment...', 'blue');
142
-
143
- // Test basic Python execution first
144
- debugLog('Testing basic Python execution...');
145
- const testResult = pyodide.runPython(`
146
- import sys
147
- print(f"Python {sys.version}")
148
- "Basic Python test successful"
149
- `);
150
- debugLog(`Basic test result: ${testResult}`);
151
 
152
- // Set up matplotlib and plotting infrastructure
153
  pyodide.runPython(`
154
- # Global variables for plot data
155
- _current_plot_data = None
156
- _current_bokeh_html = None
157
-
158
- # Check what's available
159
- available_modules = []
160
-
161
- try:
162
- import matplotlib
163
- matplotlib.use('Agg') # Use Agg backend
164
- import matplotlib.pyplot as plt
165
- available_modules.append('matplotlib')
166
- print("βœ“ Matplotlib configured")
167
- except ImportError as e:
168
- print(f"⚠ Matplotlib not available: {e}")
169
-
170
- try:
171
- import numpy as np
172
- available_modules.append('numpy')
173
- print("βœ“ NumPy available")
174
- except ImportError as e:
175
- print(f"⚠ NumPy not available: {e}")
176
-
177
- try:
178
- import pandas as pd
179
- available_modules.append('pandas')
180
- print("βœ“ Pandas available")
181
- except ImportError as e:
182
- print(f"⚠ Pandas not available: {e}")
183
-
184
- import io
185
- import base64
186
-
187
- def capture_matplotlib():
188
- global _current_plot_data
189
- try:
190
- if 'matplotlib' not in available_modules:
191
- return None
192
- if len(plt.get_fignums()) > 0:
193
- buffer = io.BytesIO()
194
- plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100)
195
- buffer.seek(0)
196
- plot_data = buffer.getvalue()
197
- buffer.close()
198
- _current_plot_data = base64.b64encode(plot_data).decode()
199
- plt.close('all')
200
- return _current_plot_data
201
- return None
202
- except Exception as e:
203
- print(f"Matplotlib capture error: {e}")
204
- return None
205
-
206
- # Override plt.show if matplotlib is available
207
- if 'matplotlib' in available_modules:
208
- original_show = plt.show
209
- def custom_show(*args, **kwargs):
210
- return capture_matplotlib()
211
- plt.show = custom_show
212
-
213
- def get_matplotlib_data():
214
- return _current_plot_data
215
-
216
- def clear_plot_data():
217
- global _current_plot_data
218
- _current_plot_data = None
219
-
220
- def get_available_modules():
221
- return available_modules
222
-
223
- print(f"Environment ready! Available modules: {available_modules}")
224
  `);
225
 
226
  pyodideReady = true;
227
- const availableModules = pyodide.runPython('get_available_modules()');
228
- updateStatus(`βœ… Pyodide ready! Available: ${availableModules}`, 'green');
229
- debugLog(`Initialization complete. Available modules: ${availableModules}`);
230
 
231
- document.getElementById('pyodide-output').style.display = 'block';
232
- document.getElementById('output-text').textContent = `Pyodide ready!\nAvailable modules: ${availableModules}\n\nTry some Python code!`;
 
 
 
 
 
 
 
233
 
234
  } catch (error) {
235
- console.error('Detailed Pyodide error:', error);
236
- debugLog(`Initialization error: ${error.message}`);
237
- debugLog(`Error stack: ${error.stack}`);
238
- updateStatus(`❌ Failed: ${error.message}`, 'red');
239
  pyodideReady = false;
240
  }
241
  }
242
 
243
  async function executePyodideCode(code) {
244
- debugLog(`Execution requested. Ready status: ${pyodideReady}`);
245
 
246
  if (!pyodideReady) {
247
- const message = 'Pyodide is not ready yet. Please wait for initialization to complete.';
248
- updateStatus('⏳ Not ready - please wait...', 'orange');
249
- return message;
250
  }
251
 
252
  if (!pyodide) {
253
- return "Error: Pyodide not available.";
254
  }
255
 
256
  if (!code || code.trim() === '') {
257
- return "Error: No code provided.";
258
  }
259
 
260
  try {
261
- updateStatus('▢️ Executing Python code...', 'blue');
262
- debugLog(`Executing code: ${code.substring(0, 100)}...`);
263
 
264
- // Clear any previous plot data
265
  pyodide.runPython('clear_plot_data()');
266
 
267
  // Capture stdout
268
  pyodide.runPython(`
269
- import sys
270
- from io import StringIO
271
- old_stdout = sys.stdout
272
- sys.stdout = captured_output = StringIO()
273
  `);
274
 
275
  // Execute user code
276
  let result = pyodide.runPython(code);
277
- debugLog(`Code execution completed. Result type: ${typeof result}`);
278
 
279
- // Restore stdout and get captured output
280
  let stdout = pyodide.runPython(`
281
- sys.stdout = old_stdout
282
- captured_output.getvalue()
283
  `);
284
 
285
- debugLog(`Stdout captured. Length: ${stdout ? stdout.length : 0}`);
286
-
287
  // Get plot data
288
- let matplotlibData = pyodide.runPython('get_matplotlib_data()');
289
- debugLog(`Matplotlib data: ${matplotlibData ? 'Available' : 'None'}`);
290
 
291
- // Display results
292
- let outputDiv = document.getElementById('pyodide-output');
293
- let outputText = document.getElementById('output-text');
294
- let plotContainer = document.getElementById('plot-container');
295
 
296
- outputDiv.style.display = 'block';
297
-
298
- // Prepare text output
299
- let textOutput = '';
300
- if (stdout && stdout.trim()) {
301
- textOutput += stdout;
302
- }
303
- if (result !== undefined && result !== null && result !== '') {
304
- if (textOutput) textOutput += '\n';
305
- textOutput += `Return value: ${result}`;
306
- }
307
-
308
- outputText.textContent = textOutput || 'Code executed successfully (no output)';
309
 
310
- // Display plots
311
- let plotHtml = '';
312
- if (matplotlibData && matplotlibData.length > 100) {
313
- plotHtml += `
314
- <div style="margin: 10px 0;">
315
- <h5>πŸ“Š Matplotlib Plot:</h5>
316
- <img src="data:image/png;base64,${matplotlibData}"
317
- style="max-width: 100%; height: auto; border: 1px solid #ddd;"
318
- alt="Matplotlib Plot">
319
- </div>
320
- `;
321
- debugLog('Matplotlib plot displayed');
322
  }
323
 
324
- plotContainer.innerHTML = plotHtml;
325
-
326
- if (plotHtml) {
327
- updateStatus('βœ… Code executed with plot generated!', 'green');
 
328
  } else {
329
- updateStatus('βœ… Code executed successfully!', 'green');
 
330
  }
331
 
332
- debugLog('Execution completed successfully');
333
- return textOutput || 'Code executed successfully';
334
 
335
  } catch (error) {
336
- console.error('Python execution error:', error);
337
- debugLog(`Execution error: ${error.message}`);
338
 
339
- let errorMessage = error.toString();
340
- // Make Python errors more readable
341
- if (errorMessage.includes('PythonError')) {
342
- errorMessage = errorMessage.replace('PythonError: ', '');
343
  }
344
-
345
- document.getElementById('output-text').textContent = 'Error: ' + errorMessage;
346
- updateStatus('❌ Execution error', 'red');
347
- return 'Error: ' + errorMessage;
348
  }
349
  }
350
 
351
- // Auto-retry initialization with exponential backoff
352
- async function safeInitPyodide(retryCount = 0) {
353
- try {
354
- await initPyodide();
355
- } catch (error) {
356
- console.error(`Init attempt ${retryCount + 1} failed:`, error);
357
- debugLog(`Initialization attempt ${retryCount + 1} failed: ${error.message}`);
358
-
359
- if (retryCount < 3) {
360
- const delay = Math.pow(2, retryCount) * 2000; // 2s, 4s, 8s delays
361
- updateStatus(`πŸ”„ Retrying in ${delay/1000} seconds... (attempt ${retryCount + 1}/3)`, 'orange');
362
- setTimeout(() => {
363
- initializationStarted = false; // Reset flag for retry
364
- safeInitPyodide(retryCount + 1);
365
- }, delay);
366
- } else {
367
- updateStatus('❌ Initialization failed after 3 attempts', 'red');
368
- debugLog('All initialization attempts exhausted');
369
- }
370
- }
371
- }
372
-
373
- // Check CDN accessibility first
374
- async function checkCDNAccess() {
375
- try {
376
- debugLog('Testing CDN accessibility...');
377
- const response = await fetch('https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js', {method: 'HEAD'});
378
- if (response.ok) {
379
- debugLog('βœ“ Primary CDN accessible');
380
- return true;
381
  } else {
382
- debugLog(`⚠ Primary CDN returned status: ${response.status}`);
383
- return false;
 
384
  }
385
- } catch (error) {
386
- debugLog(`⚠ CDN test failed: ${error.message}`);
387
- return false;
388
- }
389
  }
390
 
391
- // Enhanced initialization with pre-checks
392
- async function enhancedInit() {
393
- debugLog('Starting enhanced initialization...');
394
-
395
- // Pre-flight checks
396
- if (!await checkCDNAccess()) {
397
- updateStatus('⚠️ CDN issues detected, but proceeding...', 'orange');
398
- }
399
-
400
- // Wait a bit for CDN script to load if needed
401
- let attempts = 0;
402
- while (typeof loadPyodide === 'undefined' && attempts < 10) {
403
- debugLog(`Waiting for loadPyodide... attempt ${attempts + 1}`);
404
- await new Promise(resolve => setTimeout(resolve, 1000));
405
- attempts++;
406
- }
407
-
408
- if (typeof loadPyodide === 'undefined') {
409
- updateStatus('❌ loadPyodide never became available', 'red');
410
- return;
411
- }
412
-
413
- await safeInitPyodide();
414
- }
415
-
416
- // Utility functions for external access
417
- function checkPyodideStatus() {
418
- return {
419
- ready: pyodideReady,
420
- started: initializationStarted,
421
- available: typeof loadPyodide !== 'undefined'
422
- };
423
- }
424
-
425
- function toggleDebugMode() {
426
- debugMode = !debugMode;
427
- const debugDiv = document.getElementById('debug-info');
428
- debugDiv.style.display = debugMode ? 'block' : 'none';
429
- return debugMode;
430
- }
431
-
432
- // Initialize when DOM is ready
433
  if (document.readyState === 'loading') {
434
- document.addEventListener('DOMContentLoaded', enhancedInit);
435
  } else {
436
- enhancedInit();
437
  }
438
 
439
- // Make functions globally available
440
  window.executePyodideCode = executePyodideCode;
441
- window.checkPyodideStatus = checkPyodideStatus;
442
- window.toggleDebugMode = toggleDebugMode;
443
- window.retryInit = () => {
444
- initializationStarted = false;
445
- pyodideReady = false;
446
- enhancedInit();
447
  };
448
-
449
- // Global error handler
450
- window.addEventListener('error', (event) => {
451
- if (event.filename && event.filename.includes('pyodide')) {
452
- debugLog(`Global error in Pyodide: ${event.message}`);
453
- updateStatus('❌ Pyodide error detected', 'red');
454
  }
455
- });
 
456
 
457
  </script>
 
458
  """
459
 
460
  return pyodide_html
461
 
462
  # Create the Gradio interface
463
- with gr.Blocks(title="LibreChat Pyodide Code Interpreter", theme=gr.themes.Soft()) as demo:
464
  gr.Markdown("# 🐍 LibreChat Pyodide Code Interpreter")
465
- gr.Markdown("**Client-side Python execution** using Pyodide - Python runs directly in your browser!")
466
- gr.Markdown("⏳ **Please wait for the green 'ready' status before executing code.**")
467
 
468
  # Pyodide interface
469
  pyodide_interface = gr.HTML(create_pyodide_interface())
470
 
471
  with gr.Row():
472
- with gr.Column(scale=2):
473
  code_input = gr.Textbox(
474
- value="""# Test 1: Basic Python
475
- print("Hello from browser Python!")
476
- print("Python is working:", 2 + 2 == 4)
 
 
 
 
 
 
 
 
 
 
 
477
 
478
- # Test 2: Try this after the first test works
479
- # import numpy as np
480
- # import matplotlib.pyplot as plt
481
- #
482
- # x = np.linspace(0, 10, 100)
483
- # y = np.sin(x)
484
- #
485
- # plt.figure(figsize=(10, 6))
486
- # plt.plot(x, y, 'b-', linewidth=2, label='sin(x)')
487
- # plt.title('Sine Wave Demo')
488
- # plt.xlabel('x')
489
- # plt.ylabel('sin(x)')
490
- # plt.legend()
491
- # plt.grid(True, alpha=0.3)
492
- # plt.show()""",
493
- lines=20,
494
- label="Python Code",
495
- placeholder="Enter Python code here..."
496
  )
497
 
498
- with gr.Row():
499
- execute_btn = gr.Button("πŸš€ Execute Python", variant="primary", size="lg")
500
- check_status_btn = gr.Button("πŸ“Š Check Status", variant="secondary")
501
- retry_btn = gr.Button("πŸ”„ Retry Init", variant="secondary")
502
-
503
- with gr.Column(scale=1):
504
- gr.Markdown("### πŸ”§ Control Panel")
505
 
 
506
  status_display = gr.Textbox(
507
- label="Execution Status",
508
  interactive=False,
509
- lines=4,
510
- placeholder="Status will appear here..."
511
  )
512
 
513
- with gr.Row():
514
- debug_btn = gr.Button("πŸ› Toggle Debug", size="sm")
515
- clear_btn = gr.Button("πŸ—‘οΈ Clear Output", size="sm")
516
-
517
- gr.Markdown("""
518
- ### πŸ“‹ Usage Tips:
519
- - **Wait for green status** before executing code
520
- - **Start simple** - test basic Python first, then try imports
521
- - **Check debug info** if something goes wrong
522
- - **Matplotlib plots** will display automatically when you call `plt.show()`
523
- - **Large datasets** work well with pandas and numpy
524
- """)
525
 
526
- # JavaScript execution handlers
527
  execute_btn.click(
528
  fn=None,
529
  inputs=[code_input],
@@ -531,42 +349,35 @@ print("Python is working:", 2 + 2 == 4)
531
  js="""
532
  function(code) {
533
  console.log('Execute button clicked');
534
- if (window.executePyodideCode) {
535
- return window.executePyodideCode(code);
536
- } else {
537
- return "❌ Pyodide functions not available. Check debug info.";
538
- }
539
- }
540
- """
541
- )
542
-
543
- check_status_btn.click(
544
- fn=None,
545
- inputs=[],
546
- outputs=[status_display],
547
- js="""
548
- function() {
549
- if (window.checkPyodideStatus) {
550
- const status = window.checkPyodideStatus();
551
- return `Ready: ${status.ready}\\nStarted: ${status.started}\\nAvailable: ${status.available}`;
552
- } else {
553
- return "❌ Status check not available.";
554
  }
555
  }
556
  """
557
  )
558
 
559
- retry_btn.click(
560
  fn=None,
561
  inputs=[],
562
  outputs=[status_display],
563
  js="""
564
  function() {
565
- if (window.retryInit) {
566
- window.retryInit();
567
- return "πŸ”„ Retrying initialization...";
568
- } else {
569
- return "❌ Retry function not available.";
 
 
 
 
570
  }
571
  }
572
  """
@@ -578,31 +389,23 @@ print("Python is working:", 2 + 2 == 4)
578
  outputs=[status_display],
579
  js="""
580
  function() {
581
- if (window.toggleDebugMode) {
582
- const debugOn = window.toggleDebugMode();
583
- return debugOn ? "πŸ› Debug mode ON" : "πŸ› Debug mode OFF";
584
- } else {
585
- return "❌ Debug toggle not available.";
 
 
 
 
586
  }
587
  }
588
  """
589
  )
590
-
591
- clear_btn.click(
592
- fn=lambda: ("", ""),
593
- inputs=[],
594
- outputs=[status_display, code_input]
595
- )
596
 
597
  if __name__ == "__main__":
598
- print("πŸš€ Starting LibreChat Pyodide Code Interpreter...")
599
- print("πŸ“ Server will be available at: http://localhost:7860")
600
- print("πŸ”§ Debug mode enabled by default")
601
-
602
  demo.launch(
603
  server_name="0.0.0.0",
604
  server_port=7860,
605
- share=False,
606
- debug=True,
607
- show_error=True
608
  )
 
1
  #!/usr/bin/env python3
2
  """
3
+ LibreChat Pyodide Code Interpreter - Fixed JavaScript Syntax
 
4
  """
5
 
6
  import gradio as gr
 
8
  def create_pyodide_interface():
9
  """Create a Gradio interface that uses Pyodide for client-side Python execution"""
10
 
11
+ # Fixed HTML/JS with proper string escaping
12
  pyodide_html = """
13
  <div id="pyodide-container" style="border: 1px solid #ddd; padding: 15px; border-radius: 5px; margin: 10px 0;">
14
  <div id="pyodide-status" style="font-weight: bold; padding: 10px; background: #f0f0f0; border-radius: 3px;">
 
25
  </div>
26
  </div>
27
 
 
 
 
 
28
  <script>
29
+ // Global variables
30
  let pyodide = null;
31
  let pyodideReady = false;
32
  let initializationStarted = false;
33
  let debugMode = true;
34
 
35
+ function updateStatus(message, color) {
36
+ color = color || 'black';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  const statusDiv = document.getElementById('pyodide-status');
38
+ if (statusDiv) {
39
+ statusDiv.innerHTML = message;
40
+ statusDiv.style.color = color;
41
+ }
42
  console.log('Pyodide Status:', message);
 
43
  }
44
 
45
  function debugLog(message) {
 
47
  console.log('DEBUG:', message);
48
  const debugDiv = document.getElementById('debug-info');
49
  const debugText = document.getElementById('debug-text');
50
+ if (debugDiv && debugText) {
51
+ debugDiv.style.display = 'block';
52
+ debugText.innerHTML += '<br>' + new Date().toLocaleTimeString() + ': ' + message;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
 
 
 
54
  }
55
 
56
  async function initPyodide() {
57
  if (initializationStarted) {
58
+ debugLog('Initialization already started');
59
  return;
60
  }
61
  initializationStarted = true;
62
 
63
  try {
 
 
 
64
  // Check if loadPyodide is available
65
  if (typeof loadPyodide === 'undefined') {
66
+ throw new Error('loadPyodide function not available');
67
  }
 
68
 
69
+ updateStatus('πŸ”„ Loading Pyodide core...', 'blue');
70
+ debugLog('Starting Pyodide initialization...');
71
 
72
  pyodide = await loadPyodide({
73
+ indexURL: "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/"
 
74
  });
75
 
76
+ debugLog('Pyodide core loaded');
77
+ updateStatus('πŸ“¦ Loading packages...', 'blue');
78
 
79
+ // Load packages with error handling
80
+ try {
81
+ await pyodide.loadPackage(['numpy', 'matplotlib', 'pandas']);
82
+ debugLog('All packages loaded successfully');
83
+ } catch (pkgError) {
84
+ debugLog('Package loading error: ' + pkgError.message);
85
+ // Try minimal packages
86
  try {
87
+ await pyodide.loadPackage(['numpy']);
88
+ debugLog('Loaded numpy only');
89
+ } catch (minError) {
90
+ debugLog('Even numpy failed: ' + minError.message);
 
 
91
  }
92
  }
93
 
94
+ updateStatus('πŸ”§ Setting up environment...', 'blue');
 
 
 
 
 
 
 
 
 
95
 
96
+ // Setup Python environment with proper escaping
97
  pyodide.runPython(`
98
+ import sys
99
+ print("Python " + sys.version)
100
+
101
+ # Global plot data storage
102
+ _current_plot_data = None
103
+
104
+ def capture_matplotlib():
105
+ global _current_plot_data
106
+ try:
107
+ import matplotlib.pyplot as plt
108
+ import io
109
+ import base64
110
+
111
+ if len(plt.get_fignums()) > 0:
112
+ buffer = io.BytesIO()
113
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100)
114
+ buffer.seek(0)
115
+ plot_data = buffer.getvalue()
116
+ buffer.close()
117
+ _current_plot_data = base64.b64encode(plot_data).decode()
118
+ plt.close('all')
119
+ return _current_plot_data
120
+ return None
121
+ except Exception as e:
122
+ print("Plot capture error: " + str(e))
123
+ return None
124
+
125
+ def get_plot_data():
126
+ return _current_plot_data
127
+
128
+ def clear_plot_data():
129
+ global _current_plot_data
130
+ _current_plot_data = None
131
+
132
+ # Try to set up matplotlib
133
+ try:
134
+ import matplotlib
135
+ matplotlib.use('Agg')
136
+ import matplotlib.pyplot as plt
137
+
138
+ # Override show function
139
+ original_show = plt.show
140
+ def custom_show(*args, **kwargs):
141
+ return capture_matplotlib()
142
+ plt.show = custom_show
143
+
144
+ print("Matplotlib configured successfully")
145
+ except ImportError:
146
+ print("Matplotlib not available")
147
+
148
+ print("Python environment ready!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  `);
150
 
151
  pyodideReady = true;
152
+ updateStatus('βœ… Pyodide ready!', 'green');
153
+ debugLog('Initialization complete');
 
154
 
155
+ // Show output area
156
+ const outputDiv = document.getElementById('pyodide-output');
157
+ if (outputDiv) {
158
+ outputDiv.style.display = 'block';
159
+ }
160
+ const outputText = document.getElementById('output-text');
161
+ if (outputText) {
162
+ outputText.textContent = 'Pyodide ready! You can now execute Python code.';
163
+ }
164
 
165
  } catch (error) {
166
+ console.error('Pyodide initialization error:', error);
167
+ debugLog('Init error: ' + error.message);
168
+ updateStatus('❌ Failed: ' + error.message, 'red');
 
169
  pyodideReady = false;
170
  }
171
  }
172
 
173
  async function executePyodideCode(code) {
174
+ debugLog('Execute function called');
175
 
176
  if (!pyodideReady) {
177
+ updateStatus('⏳ Not ready yet...', 'orange');
178
+ return 'Pyodide is not ready. Please wait for green status.';
 
179
  }
180
 
181
  if (!pyodide) {
182
+ return 'Error: Pyodide not available.';
183
  }
184
 
185
  if (!code || code.trim() === '') {
186
+ return 'Error: No code provided.';
187
  }
188
 
189
  try {
190
+ updateStatus('▢️ Executing...', 'blue');
191
+ debugLog('Executing code: ' + code.substring(0, 50) + '...');
192
 
193
+ // Clear previous plots
194
  pyodide.runPython('clear_plot_data()');
195
 
196
  // Capture stdout
197
  pyodide.runPython(`
198
+ import sys
199
+ from io import StringIO
200
+ old_stdout = sys.stdout
201
+ sys.stdout = captured_output = StringIO()
202
  `);
203
 
204
  // Execute user code
205
  let result = pyodide.runPython(code);
 
206
 
207
+ // Get captured output
208
  let stdout = pyodide.runPython(`
209
+ sys.stdout = old_stdout
210
+ captured_output.getvalue()
211
  `);
212
 
 
 
213
  // Get plot data
214
+ let plotData = pyodide.runPython('get_plot_data()');
 
215
 
216
+ debugLog('Execution completed');
 
 
 
217
 
218
+ // Display results
219
+ const outputText = document.getElementById('output-text');
220
+ const plotContainer = document.getElementById('plot-container');
 
 
 
 
 
 
 
 
 
 
221
 
222
+ if (outputText) {
223
+ let textOutput = stdout || '';
224
+ if (result !== undefined && result !== null && result !== '') {
225
+ if (textOutput) textOutput += '\\n';
226
+ textOutput += 'Return: ' + result;
227
+ }
228
+ outputText.textContent = textOutput || 'Code executed (no output)';
 
 
 
 
 
229
  }
230
 
231
+ // Handle plots
232
+ if (plotContainer && plotData && plotData.length > 100) {
233
+ plotContainer.innerHTML = '<div style="margin: 10px 0;"><h5>πŸ“Š Plot:</h5><img src="data:image/png;base64,' + plotData + '" style="max-width: 100%; height: auto; border: 1px solid #ddd;" alt="Plot"></div>';
234
+ updateStatus('βœ… Executed with plot!', 'green');
235
+ debugLog('Plot generated and displayed');
236
  } else {
237
+ if (plotContainer) plotContainer.innerHTML = '';
238
+ updateStatus('βœ… Executed successfully!', 'green');
239
  }
240
 
241
+ return stdout || 'Code executed successfully';
 
242
 
243
  } catch (error) {
244
+ console.error('Execution error:', error);
245
+ debugLog('Execution error: ' + error.message);
246
 
247
+ const outputText = document.getElementById('output-text');
248
+ if (outputText) {
249
+ outputText.textContent = 'Error: ' + error.toString();
 
250
  }
251
+ updateStatus('❌ Execution failed', 'red');
252
+ return 'Error: ' + error.toString();
 
 
253
  }
254
  }
255
 
256
+ // Initialize when ready
257
+ function safeInit() {
258
+ setTimeout(function() {
259
+ if (typeof loadPyodide !== 'undefined') {
260
+ debugLog('loadPyodide available, starting init');
261
+ initPyodide();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  } else {
263
+ debugLog('loadPyodide not available yet, retrying...');
264
+ updateStatus('⏳ Waiting for CDN...', 'orange');
265
+ safeInit();
266
  }
267
+ }, 1000);
 
 
 
268
  }
269
 
270
+ // Start initialization
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  if (document.readyState === 'loading') {
272
+ document.addEventListener('DOMContentLoaded', safeInit);
273
  } else {
274
+ safeInit();
275
  }
276
 
277
+ // Global functions
278
  window.executePyodideCode = executePyodideCode;
279
+ window.checkPyodideStatus = function() {
280
+ return pyodideReady;
 
 
 
 
281
  };
282
+ window.toggleDebugMode = function() {
283
+ debugMode = !debugMode;
284
+ const debugDiv = document.getElementById('debug-info');
285
+ if (debugDiv) {
286
+ debugDiv.style.display = debugMode ? 'block' : 'none';
 
287
  }
288
+ return debugMode;
289
+ };
290
 
291
  </script>
292
+ <script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js" async></script>
293
  """
294
 
295
  return pyodide_html
296
 
297
  # Create the Gradio interface
298
+ with gr.Blocks(title="LibreChat Pyodide Code Interpreter") as demo:
299
  gr.Markdown("# 🐍 LibreChat Pyodide Code Interpreter")
300
+ gr.Markdown("**Client-side Python execution** - Python runs in your browser!")
 
301
 
302
  # Pyodide interface
303
  pyodide_interface = gr.HTML(create_pyodide_interface())
304
 
305
  with gr.Row():
306
+ with gr.Column():
307
  code_input = gr.Textbox(
308
+ value="""# Start with this simple test:
309
+ print("Hello from Python!")
310
+ print("2 + 2 =", 2 + 2)
311
+
312
+ # Check what's available:
313
+ import sys
314
+ print("Python version:", sys.version_info[:2])
315
+
316
+ try:
317
+ import numpy as np
318
+ print("βœ… NumPy available")
319
+ print("Random number:", np.random.rand())
320
+ except ImportError:
321
+ print("❌ NumPy not available")
322
 
323
+ try:
324
+ import matplotlib.pyplot as plt
325
+ print("βœ… Matplotlib available")
326
+ except ImportError:
327
+ print("❌ Matplotlib not available")""",
328
+ lines=15,
329
+ label="Python Code"
 
 
 
 
 
 
 
 
 
 
 
330
  )
331
 
332
+ execute_btn = gr.Button("πŸš€ Execute", variant="primary")
 
 
 
 
 
 
333
 
334
+ with gr.Column():
335
  status_display = gr.Textbox(
336
+ label="Status",
337
  interactive=False,
338
+ lines=3
 
339
  )
340
 
341
+ check_btn = gr.Button("πŸ“Š Check Status")
342
+ debug_btn = gr.Button("πŸ› Toggle Debug")
 
 
 
 
 
 
 
 
 
 
343
 
344
+ # Fixed JavaScript handlers with proper escaping
345
  execute_btn.click(
346
  fn=None,
347
  inputs=[code_input],
 
349
  js="""
350
  function(code) {
351
  console.log('Execute button clicked');
352
+ try {
353
+ if (window.executePyodideCode) {
354
+ return window.executePyodideCode(code);
355
+ } else {
356
+ return 'executePyodideCode function not available';
357
+ }
358
+ } catch (error) {
359
+ console.error('JS execution error:', error);
360
+ return 'JavaScript error: ' + error.message;
 
 
 
 
 
 
 
 
 
 
 
361
  }
362
  }
363
  """
364
  )
365
 
366
+ check_btn.click(
367
  fn=None,
368
  inputs=[],
369
  outputs=[status_display],
370
  js="""
371
  function() {
372
+ try {
373
+ if (window.checkPyodideStatus) {
374
+ const ready = window.checkPyodideStatus();
375
+ return ready ? 'βœ… Pyodide is ready!' : '⏳ Pyodide still loading...';
376
+ } else {
377
+ return '❌ Status function not available';
378
+ }
379
+ } catch (error) {
380
+ return 'Status check error: ' + error.message;
381
  }
382
  }
383
  """
 
389
  outputs=[status_display],
390
  js="""
391
  function() {
392
+ try {
393
+ if (window.toggleDebugMode) {
394
+ const debugOn = window.toggleDebugMode();
395
+ return debugOn ? 'πŸ› Debug ON' : 'πŸ› Debug OFF';
396
+ } else {
397
+ return '❌ Debug toggle not available';
398
+ }
399
+ } catch (error) {
400
+ return 'Debug toggle error: ' + error.message;
401
  }
402
  }
403
  """
404
  )
 
 
 
 
 
 
405
 
406
  if __name__ == "__main__":
 
 
 
 
407
  demo.launch(
408
  server_name="0.0.0.0",
409
  server_port=7860,
410
+ share=False
 
 
411
  )