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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +390 -121
app.py CHANGED
@@ -1,6 +1,7 @@
1
  #!/usr/bin/env python3
2
  """
3
  LibreChat Pyodide Code Interpreter - Client-Side Python Execution
 
4
  """
5
 
6
  import gradio as gr
@@ -8,68 +9,186 @@ import gradio as gr
8
  def create_pyodide_interface():
9
  """Create a Gradio interface that uses Pyodide for client-side Python execution"""
10
 
11
- # Enhanced HTML/JS with better error handling and status updates
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;">
15
  πŸ”„ Loading Pyodide... This may take 10-30 seconds on first load.
16
  </div>
 
 
 
 
17
  <div id="pyodide-output" style="display:none; margin-top: 10px;">
18
  <h4>Execution Results:</h4>
19
- <pre id="output-text" style="background: #f8f8f8; padding: 10px; border-radius: 3px; max-height: 300px; overflow-y: auto;"></pre>
20
  <div id="plot-container" style="text-align: center; margin-top: 10px;"></div>
21
  </div>
22
  </div>
23
 
24
- <script src="https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js"></script>
 
 
 
25
  <script>
26
  let pyodide = null;
27
  let pyodideReady = false;
28
  let initializationStarted = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  function updateStatus(message, color = 'black') {
31
  const statusDiv = document.getElementById('pyodide-status');
32
  statusDiv.innerHTML = message;
33
  statusDiv.style.color = color;
34
  console.log('Pyodide Status:', message);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
 
37
  async function initPyodide() {
38
- if (initializationStarted) return;
 
 
 
39
  initializationStarted = true;
40
 
41
  try {
42
- updateStatus('πŸ”„ Loading Pyodide core...', 'blue');
 
 
 
 
 
 
 
 
 
43
 
44
  pyodide = await loadPyodide({
45
- indexURL: "https://cdn.jsdelivr.net/pyodide/v0.24.1/full/"
 
46
  });
47
 
48
- updateStatus('πŸ“¦ Loading Python packages (numpy, matplotlib, pandas, bokeh)...', 'blue');
 
49
 
50
- await pyodide.loadPackage(["numpy", "matplotlib", "pandas", "bokeh"]);
51
- updateStatus('πŸ“¦ All packages loaded, setting up backends...', 'blue');
 
 
 
 
 
 
 
 
 
 
52
 
53
- // Update the Python setup:
 
 
 
 
 
 
 
 
 
 
 
54
  pyodide.runPython(`
55
- import matplotlib
56
- matplotlib.use('AGG')
57
- import matplotlib.pyplot as plt
58
- import numpy as np
59
- import pandas as pd
60
- from bokeh.plotting import figure, show, output_file
61
- from bokeh.models import HoverTool
62
- from bokeh.io import export_png
63
- import io
64
- import base64
65
-
66
  # Global variables for plot data
67
  _current_plot_data = None
68
  _current_bokeh_html = None
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  def capture_matplotlib():
71
  global _current_plot_data
72
  try:
 
 
73
  if len(plt.get_fignums()) > 0:
74
  buffer = io.BytesIO()
75
  plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100)
@@ -84,68 +203,63 @@ def create_pyodide_interface():
84
  print(f"Matplotlib capture error: {e}")
85
  return None
86
 
87
- def capture_bokeh(bokeh_fig):
88
- global _current_bokeh_html
89
- try:
90
- from bokeh.embed import file_html
91
- from bokeh.resources import CDN
92
- _current_bokeh_html = file_html(bokeh_fig, CDN, "Bokeh Plot")
93
- return _current_bokeh_html
94
- except Exception as e:
95
- print(f"Bokeh capture error: {e}")
96
- return None
97
-
98
- # Override functions
99
- original_show = plt.show
100
- def custom_show(*args, **kwargs):
101
- return capture_matplotlib()
102
- plt.show = custom_show
103
-
104
- # Custom bokeh show function
105
- def show_bokeh(fig):
106
- return capture_bokeh(fig)
107
 
108
  def get_matplotlib_data():
109
  return _current_plot_data
110
 
111
- def get_bokeh_data():
112
- return _current_bokeh_html
113
-
114
  def clear_plot_data():
115
- global _current_plot_data, _current_bokeh_html
116
  _current_plot_data = None
117
- _current_bokeh_html = None
118
 
119
- print("Pyodide ready with matplotlib AND bokeh support!")
 
 
 
120
  `);
121
 
122
  pyodideReady = true;
123
- updateStatus('βœ… Pyodide ready! You can now execute Python code.', 'green');
 
 
124
 
125
  document.getElementById('pyodide-output').style.display = 'block';
126
- document.getElementById('output-text').textContent = 'Pyodide initialization complete! Ready to execute Python code.';
127
 
128
  } catch (error) {
129
- console.error('Pyodide initialization error:', error);
130
- updateStatus('❌ Failed to initialize Pyodide: ' + error.toString(), 'red');
 
 
131
  pyodideReady = false;
132
  }
133
  }
134
 
135
  async function executePyodideCode(code) {
136
- console.log('Execute called, pyodideReady:', pyodideReady);
137
 
138
  if (!pyodideReady) {
139
- updateStatus('⏳ Pyodide is still initializing. Please wait...', 'orange');
140
- return "Pyodide is still loading. Please wait for the green 'ready' status above.";
 
141
  }
142
 
143
  if (!pyodide) {
144
  return "Error: Pyodide not available.";
145
  }
146
 
 
 
 
 
147
  try {
148
  updateStatus('▢️ Executing Python code...', 'blue');
 
149
 
150
  // Clear any previous plot data
151
  pyodide.runPython('clear_plot_data()');
@@ -160,22 +274,19 @@ def create_pyodide_interface():
160
 
161
  // Execute user code
162
  let result = pyodide.runPython(code);
 
163
 
164
- // Get captured output (this should NOT contain base64 data)
165
  let stdout = pyodide.runPython(`
166
  sys.stdout = old_stdout
167
  captured_output.getvalue()
168
  `);
169
 
170
- // Get both types of plot data
171
- let matplotlibData = pyodide.runPython('get_matplotlib_data()');
172
- let bokehData = pyodide.runPython('get_bokeh_data()');
173
 
174
- console.log('Stdout length:', stdout ? stdout.length : 0);
175
- console.log('Matplotlib data length:', matplotlibData ? matplotlibData.length : 0);
176
- console.log('Bokeh data length:', bokehData ? bokehData.length : 0);
177
- console.log('Matplotlib data preview:', matplotlibData ? matplotlibData.substring(0, 50) + '...' : 'None');
178
- console.log('Bokeh data preview:', bokehData ? bokehData.substring(0, 50) + '...' : 'None');
179
 
180
  // Display results
181
  let outputDiv = document.getElementById('pyodide-output');
@@ -184,12 +295,20 @@ def create_pyodide_interface():
184
 
185
  outputDiv.style.display = 'block';
186
 
187
- let textOutput = stdout || (result !== undefined ? String(result) : '');
 
 
 
 
 
 
 
 
 
188
  outputText.textContent = textOutput || 'Code executed successfully (no output)';
189
 
190
  // Display plots
191
  let plotHtml = '';
192
-
193
  if (matplotlibData && matplotlibData.length > 100) {
194
  plotHtml += `
195
  <div style="margin: 10px 0;">
@@ -199,123 +318,228 @@ def create_pyodide_interface():
199
  alt="Matplotlib Plot">
200
  </div>
201
  `;
202
- }
203
-
204
- if (bokehData) {
205
- plotHtml += `
206
- <div style="margin: 10px 0;">
207
- <h5>πŸ“Š Interactive Bokeh Plot:</h5>
208
- <div style="border: 1px solid #ddd; border-radius: 3px;">
209
- ${bokehData}
210
- </div>
211
- </div>
212
- `;
213
  }
214
 
215
  plotContainer.innerHTML = plotHtml;
216
 
217
  if (plotHtml) {
218
- if (textOutput.trim()) {
219
- outputText.textContent += '\\n\\nπŸ“Š Plot(s) generated and displayed below!';
220
- } else {
221
- outputText.textContent = 'πŸ“Š Plot(s) generated and displayed below!';
222
- }
223
- updateStatus('βœ… Code executed with plot(s) generated!', 'green');
224
  } else {
225
  updateStatus('βœ… Code executed successfully!', 'green');
226
  }
227
 
 
228
  return textOutput || 'Code executed successfully';
229
 
230
  } catch (error) {
231
- console.error('Execution error:', error);
232
- document.getElementById('output-text').textContent = 'Error: ' + error.toString();
233
- updateStatus('❌ Execution error: ' + error.toString(), 'red');
234
- return 'Error: ' + error.toString();
 
 
 
 
 
 
 
 
235
  }
236
  }
237
 
238
- // Auto-retry initialization if it fails
239
- async function safeInitPyodide() {
240
  try {
241
  await initPyodide();
242
  } catch (error) {
243
- console.error('Init failed, retrying in 3 seconds:', error);
244
- updateStatus('πŸ”„ Initialization failed, retrying in 3 seconds...', 'orange');
245
- setTimeout(safeInitPyodide, 3000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  }
 
 
 
 
 
 
 
247
  }
248
 
249
- // Initialize when page loads
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  if (document.readyState === 'loading') {
251
- document.addEventListener('DOMContentLoaded', safeInitPyodide);
252
  } else {
253
- safeInitPyodide();
254
  }
255
 
256
  // Make functions globally available
257
  window.executePyodideCode = executePyodideCode;
258
- window.checkPyodideStatus = () => pyodideReady;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  </script>
260
  """
261
 
262
  return pyodide_html
263
 
264
  # Create the Gradio interface
265
- with gr.Blocks(title="LibreChat Pyodide Code Interpreter") as demo:
266
- gr.Markdown("# LibreChat Pyodide Code Interpreter")
267
  gr.Markdown("**Client-side Python execution** using Pyodide - Python runs directly in your browser!")
268
- gr.Markdown("⏳ **Please wait for Pyodide to fully initialize before executing code (watch the status above).**")
269
 
270
  # Pyodide interface
271
  pyodide_interface = gr.HTML(create_pyodide_interface())
272
 
273
  with gr.Row():
274
- with gr.Column():
275
  code_input = gr.Textbox(
276
- value="""# Simple test - try this first
277
- print("Hello from browser-based Python!")
278
- print("2 + 2 =", 2 + 2)
279
 
280
- # Then try this matplotlib example:
281
- # import matplotlib.pyplot as plt
282
  # import numpy as np
283
- # x = np.linspace(0, 10, 50)
284
- # plt.plot(x, np.sin(x))
285
- # plt.title('Sine Wave')
 
 
 
 
 
 
 
 
 
286
  # plt.show()""",
287
- lines=15,
288
- label="Python Code (executes in your browser)"
 
289
  )
290
 
291
- execute_btn = gr.Button("Execute with Pyodide", variant="primary")
292
- check_status_btn = gr.Button("Check Pyodide Status", variant="secondary")
 
 
293
 
294
- with gr.Column():
295
- gr.Markdown("### Execution Status")
 
296
  status_display = gr.Textbox(
297
- label="Last Execution Result",
298
  interactive=False,
299
- lines=3
 
300
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
- # JavaScript execution
303
  execute_btn.click(
304
  fn=None,
305
  inputs=[code_input],
306
  outputs=[status_display],
307
  js="""
308
  function(code) {
 
309
  if (window.executePyodideCode) {
310
  return window.executePyodideCode(code);
311
  } else {
312
- return "Pyodide functions not available. Check browser console for errors.";
313
  }
314
  }
315
  """
316
  )
317
 
318
- # Status check button
319
  check_status_btn.click(
320
  fn=None,
321
  inputs=[],
@@ -323,17 +547,62 @@ print("2 + 2 =", 2 + 2)
323
  js="""
324
  function() {
325
  if (window.checkPyodideStatus) {
326
- return window.checkPyodideStatus() ? "βœ… Pyodide is ready!" : "⏳ Pyodide is still loading...";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  } else {
328
- return "❌ Pyodide not available.";
329
  }
330
  }
331
  """
332
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
  if __name__ == "__main__":
 
 
 
 
335
  demo.launch(
336
  server_name="0.0.0.0",
337
  server_port=7860,
338
- share=False
 
 
339
  )
 
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
  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;">
16
  πŸ”„ Loading Pyodide... This may take 10-30 seconds on first load.
17
  </div>
18
+ <div id="debug-info" style="display:none; margin-top: 10px; padding: 10px; background: #fff3cd; border-radius: 3px; font-size: 12px;">
19
+ <strong>Debug Info:</strong>
20
+ <div id="debug-text"></div>
21
+ </div>
22
  <div id="pyodide-output" style="display:none; margin-top: 10px;">
23
  <h4>Execution Results:</h4>
24
+ <pre id="output-text" style="background: #f8f8f8; padding: 10px; border-radius: 3px; max-height: 300px; overflow-y: auto; white-space: pre-wrap;"></pre>
25
  <div id="plot-container" style="text-align: center; margin-top: 10px;"></div>
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) {
69
+ if (!debugMode) return;
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)
 
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()');
 
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');
 
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;">
 
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],
530
  outputs=[status_display],
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=[],
 
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
  """
573
  )
574
+
575
+ debug_btn.click(
576
+ fn=None,
577
+ inputs=[],
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
  )