mic3333 commited on
Commit
390dba3
·
verified ·
1 Parent(s): 735010b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +142 -478
app.py CHANGED
@@ -1,69 +1,96 @@
1
  #!/usr/bin/env python3
2
  """
3
- LibreChat Pyodide Code Interpreter - With Plotly Support
4
  """
5
 
6
  import gradio as gr
7
 
8
  def create_pyodide_interface():
9
- """Create a Gradio interface with Pyodide + Plotly support"""
10
 
11
  pyodide_html = """
12
  <div id="pyodide-container" style="border: 1px solid #ddd; padding: 15px; border-radius: 5px; margin: 10px 0;">
13
  <div id="pyodide-status" style="font-weight: bold; padding: 10px; background: #f0f0f0; border-radius: 3px;">
14
- 🔄 Loading Pyodide with Plotly... This may take 15-30 seconds.
15
- </div>
16
- <div id="debug-info" style="display:none; margin-top: 10px; padding: 10px; background: #fff3cd; border-radius: 3px; font-size: 12px;">
17
- <strong>Debug Info:</strong>
18
- <div id="debug-text"></div>
19
  </div>
20
  <div id="pyodide-output" style="display:none; margin-top: 10px;">
21
  <h4>Execution Results:</h4>
22
- <pre id="output-text" style="background: #f8f8f8; padding: 10px; border-radius: 3px; max-height: 300px; overflow-y: auto; white-space: pre-wrap;"></pre>
23
- <div id="plot-container" style="text-align: center; margin-top: 10px;"></div>
24
  </div>
25
  </div>
26
 
27
- <!-- Load Plotly.js first -->
28
  <script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
29
 
30
  <script>
31
- // Global variables
32
  let pyodide = null;
33
  let pyodideReady = false;
34
- let initializationStarted = false;
35
- let debugMode = true;
36
 
37
- function updateStatus(message, color) {
38
- color = color || 'black';
39
  const statusDiv = document.getElementById('pyodide-status');
40
  if (statusDiv) {
41
  statusDiv.innerHTML = message;
42
  statusDiv.style.color = color;
43
  }
44
- console.log('Pyodide Status:', message);
45
  }
46
 
47
- function debugLog(message) {
48
- if (!debugMode) return;
49
- console.log('DEBUG:', message);
50
- const debugDiv = document.getElementById('debug-info');
51
- const debugText = document.getElementById('debug-text');
52
- if (debugDiv && debugText) {
53
- debugDiv.style.display = 'block';
54
- debugText.innerHTML += '<br>' + new Date().toLocaleTimeString() + ': ' + message;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
56
- }
57
 
58
  async function initPyodide() {
59
- if (initializationStarted) {
60
- debugLog('Initialization already started');
61
- return;
62
- }
63
- initializationStarted = true;
64
-
65
  try {
66
- // Check prerequisites
67
  if (typeof loadPyodide === 'undefined') {
68
  throw new Error('Pyodide CDN not loaded');
69
  }
@@ -71,150 +98,99 @@ def create_pyodide_interface():
71
  throw new Error('Plotly CDN not loaded');
72
  }
73
 
74
- updateStatus('🔄 Loading Pyodide core...', 'blue');
75
- debugLog('Starting Pyodide with Plotly support...');
76
 
77
  pyodide = await loadPyodide({
78
  indexURL: "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/"
79
  });
80
 
81
- debugLog('Pyodide core loaded');
82
- updateStatus('📦 Installing Python packages...', 'blue');
83
 
84
- // Install plotly and other packages
85
- const packages = ['numpy', 'pandas', 'matplotlib'];
86
 
87
- for (const pkg of packages) {
88
- try {
89
- debugLog(`Installing ${pkg}...`);
90
- await pyodide.loadPackage(pkg);
91
- debugLog(`✓ ${pkg} installed`);
92
- } catch (error) {
93
- debugLog(`⚠ ${pkg} failed: ${error.message}`);
94
- }
95
- }
96
-
97
- // Install plotly via pip in Pyodide
98
- updateStatus('📦 Installing Plotly via pip...', 'blue');
99
- try {
100
- await pyodide.loadPackage(['micropip']);
101
- await pyodide.runPythonAsync(`
102
- import micropip
103
- await micropip.install('plotly')
104
- `);
105
- debugLog('✓ Plotly installed via micropip');
106
- } catch (error) {
107
- debugLog('⚠ Plotly installation failed: ' + error.message);
108
- }
109
 
110
- updateStatus('🔧 Setting up plotting environment...', 'blue');
111
 
112
- // Setup Python environment with Plotly support
113
  pyodide.runPython(`
114
  import sys
115
- print("Python " + sys.version)
 
116
 
117
- # Global storage for plots
118
- _matplotlib_data = None
119
- _plotly_data = None
120
 
121
- def capture_matplotlib():
122
- global _matplotlib_data
123
- try:
124
- import matplotlib.pyplot as plt
125
- import io
126
- import base64
127
-
128
- if len(plt.get_fignums()) > 0:
129
- buffer = io.BytesIO()
130
- plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100)
131
- buffer.seek(0)
132
- plot_data = buffer.getvalue()
133
- buffer.close()
134
- _matplotlib_data = base64.b64encode(plot_data).decode()
135
- plt.close('all')
136
- return _matplotlib_data
137
- return None
138
- except Exception as e:
139
- print("Matplotlib capture error: " + str(e))
140
- return None
141
 
142
- def capture_plotly(fig):
143
- global _plotly_data
 
144
  try:
145
- # Convert plotly figure to HTML
146
- import plotly.offline as pyo
147
- _plotly_data = pyo.plot(fig, output_type='div', include_plotlyjs=False)
148
- return _plotly_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  except Exception as e:
150
- print("Plotly capture error: " + str(e))
151
- return None
152
 
153
- def get_matplotlib_data():
154
- return _matplotlib_data
155
-
156
- def get_plotly_data():
157
- return _plotly_data
158
-
159
- def clear_plot_data():
160
- global _matplotlib_data, _plotly_data
161
- _matplotlib_data = None
162
- _plotly_data = None
163
-
164
- # Setup matplotlib if available
165
- try:
166
- import matplotlib
167
- matplotlib.use('Agg')
168
- import matplotlib.pyplot as plt
169
-
170
- original_show = plt.show
171
- def custom_show(*args, **kwargs):
172
- return capture_matplotlib()
173
- plt.show = custom_show
174
-
175
- print("✅ Matplotlib configured")
176
- except ImportError:
177
- print("❌ Matplotlib not available")
178
-
179
- # Setup plotly if available
180
  try:
181
  import plotly.graph_objects as go
182
  import plotly.express as px
183
 
184
- # Custom show function for Plotly
185
- def show_plotly(fig):
186
- return capture_plotly(fig)
 
 
187
 
188
- # Monkey patch plotly's show
189
- original_plotly_show = go.Figure.show
190
- def custom_plotly_show(self, *args, **kwargs):
191
- return capture_plotly(self)
192
- go.Figure.show = custom_plotly_show
193
 
194
- print("✅ Plotly configured")
195
- print("Available: plotly.graph_objects as 'go', plotly.express as 'px'")
196
  except ImportError as e:
197
- print("❌ Plotly not available: " + str(e))
198
-
199
- # Test basic functionality
200
- try:
201
- import numpy as np
202
- print("✅ NumPy available")
203
- except ImportError:
204
- print("❌ NumPy not available")
205
 
 
206
  try:
207
- import pandas as pd
208
- print("✅ Pandas available")
 
 
209
  except ImportError:
210
- print("❌ Pandas not available")
211
 
212
- print("Environment setup complete!")
213
  `);
214
 
215
  pyodideReady = true;
216
  updateStatus('✅ Pyodide + Plotly ready!', 'green');
217
- debugLog('Full initialization complete');
218
 
219
  // Show output area
220
  const outputDiv = document.getElementById('pyodide-output');
@@ -222,34 +198,31 @@ print("Environment setup complete!")
222
 
223
  const outputText = document.getElementById('output-text');
224
  if (outputText) {
225
- outputText.textContent = 'Pyodide ready with Plotly support!\\n\\nTry the examples below or write your own code.';
226
  }
227
 
228
  } catch (error) {
229
  console.error('Initialization error:', error);
230
- debugLog('Init error: ' + error.message);
231
  updateStatus('❌ Failed: ' + error.message, 'red');
232
  pyodideReady = false;
233
  }
234
  }
235
 
236
  async function executePyodideCode(code) {
237
- debugLog('Execute function called');
238
-
239
  if (!pyodideReady) {
240
- return 'Pyodide is not ready. Please wait for green status.';
241
  }
242
 
243
  if (!code || code.trim() === '') {
244
- return 'Error: No code provided.';
245
  }
246
 
247
  try {
248
- updateStatus('▶️ Executing Python...', 'blue');
249
- debugLog('Executing: ' + code.substring(0, 50) + '...');
250
 
251
  // Clear previous plots
252
- pyodide.runPython('clear_plot_data()');
 
253
 
254
  // Capture stdout
255
  pyodide.runPython(`
@@ -268,362 +241,53 @@ sys.stdout = old_stdout
268
  captured_output.getvalue()
269
  `);
270
 
271
- // Get plot data
272
- let matplotlibData = pyodide.runPython('get_matplotlib_data()');
273
- let plotlyData = pyodide.runPython('get_plotly_data()');
274
-
275
- debugLog('Execution completed');
276
-
277
- // Display results
278
  const outputText = document.getElementById('output-text');
279
- const plotContainer = document.getElementById('plot-container');
280
-
281
- // Handle text output
282
  if (outputText) {
283
  let textOutput = stdout || '';
284
  if (result !== undefined && result !== null && result !== '') {
285
  if (textOutput) textOutput += '\\n';
286
  textOutput += 'Return: ' + result;
287
  }
288
- outputText.textContent = textOutput || 'Code executed successfully (no text output)';
289
- }
290
-
291
- // Handle plots
292
- let plotHTML = '';
293
-
294
- if (matplotlibData && matplotlibData.length > 100) {
295
- plotHTML += `
296
- <div style="margin: 10px 0;">
297
- <h5>📊 Matplotlib Plot:</h5>
298
- <img src="data:image/png;base64,${matplotlibData}"
299
- style="max-width: 100%; height: auto; border: 1px solid #ddd;"
300
- alt="Matplotlib Plot">
301
- </div>
302
- `;
303
- }
304
-
305
- if (plotlyData && plotlyData.length > 100) {
306
- plotHTML += `
307
- <div style="margin: 10px 0;">
308
- <h5>📈 Interactive Plotly Chart:</h5>
309
- <div style="border: 1px solid #ddd; border-radius: 5px; padding: 10px;">
310
- ${plotlyData}
311
- </div>
312
- </div>
313
- `;
314
- }
315
-
316
- if (plotContainer) {
317
- plotContainer.innerHTML = plotHTML;
318
- }
319
-
320
- if (plotHTML) {
321
- updateStatus('✅ Executed with plot(s)!', 'green');
322
- } else {
323
- updateStatus('✅ Executed successfully!', 'green');
324
  }
325
 
 
326
  return stdout || 'Code executed successfully';
327
 
328
  } catch (error) {
329
  console.error('Execution error:', error);
330
- debugLog('Error: ' + error.message);
331
 
332
  const outputText = document.getElementById('output-text');
333
  if (outputText) {
334
  outputText.textContent = 'Error: ' + error.toString();
335
  }
336
- updateStatus('❌ Execution failed', 'red');
337
  return 'Error: ' + error.toString();
338
  }
339
  }
340
 
341
- // Safe initialization with retry
342
- async function safeInit() {
343
- let retries = 0;
344
- const maxRetries = 3;
345
-
346
- while (retries < maxRetries) {
347
- try {
348
- if (typeof loadPyodide !== 'undefined' && typeof Plotly !== 'undefined') {
349
- await initPyodide();
350
- return;
351
- }
352
- } catch (error) {
353
- debugLog(`Init attempt ${retries + 1} failed: ${error.message}`);
354
- }
355
-
356
- retries++;
357
- if (retries < maxRetries) {
358
- debugLog(`Retrying in ${retries * 2} seconds...`);
359
- await new Promise(resolve => setTimeout(resolve, retries * 2000));
360
- }
361
  }
362
-
363
- updateStatus('❌ Initialization failed after ' + maxRetries + ' attempts', 'red');
364
- }
365
-
366
- // Wait for both CDNs to load
367
- function waitForCDNs() {
368
- const checkInterval = setInterval(() => {
369
- if (typeof loadPyodide !== 'undefined' && typeof Plotly !== 'undefined') {
370
- clearInterval(checkInterval);
371
- debugLog('Both CDNs loaded, starting init');
372
- safeInit();
373
- } else {
374
- debugLog('Waiting for CDNs... Pyodide: ' + (typeof loadPyodide !== 'undefined') + ', Plotly: ' + (typeof Plotly !== 'undefined'));
375
- }
376
- }, 1000);
377
-
378
- // Timeout after 30 seconds
379
- setTimeout(() => {
380
- clearInterval(checkInterval);
381
- if (!pyodideReady) {
382
- updateStatus('❌ CDN loading timeout', 'red');
383
- }
384
- }, 30000);
385
  }
386
 
387
- // Start when DOM is ready
388
  if (document.readyState === 'loading') {
389
- document.addEventListener('DOMContentLoaded', waitForCDNs);
390
  } else {
391
- waitForCDNs();
392
  }
393
 
394
  // Global functions
395
  window.executePyodideCode = executePyodideCode;
396
  window.checkPyodideStatus = () => pyodideReady;
397
- window.toggleDebugMode = function() {
398
- debugMode = !debugMode;
399
- const debugDiv = document.getElementById('debug-info');
400
- if (debugDiv) debugDiv.style.display = debugMode ? 'block' : 'none';
401
- return debugMode;
402
- };
403
 
404
  </script>
405
- <script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js" async></script>
406
- """
407
-
408
- return pyodide_html
409
-
410
- # Create the Gradio interface
411
- with gr.Blocks(title="Pyodide + Plotly Code Interpreter") as demo:
412
- gr.Markdown("# 🐍📈 Pyodide + Plotly Code Interpreter")
413
- gr.Markdown("**Interactive Python with Plotly charts** - runs entirely in your browser!")
414
-
415
- # Pyodide interface
416
- pyodide_interface = gr.HTML(create_pyodide_interface())
417
-
418
- with gr.Row():
419
- with gr.Column(scale=2):
420
- code_input = gr.Textbox(
421
- value="""# Plotly Example 1: Simple Line Chart
422
- import plotly.graph_objects as go
423
- import numpy as np
424
-
425
- # Generate data
426
- x = np.linspace(0, 10, 100)
427
- y1 = np.sin(x)
428
- y2 = np.cos(x)
429
-
430
- # Create figure
431
- fig = go.Figure()
432
- fig.add_trace(go.Scatter(x=x, y=y1, name='sin(x)', line=dict(color='blue')))
433
- fig.add_trace(go.Scatter(x=x, y=y2, name='cos(x)', line=dict(color='red')))
434
-
435
- fig.update_layout(
436
- title='Interactive Sine and Cosine Waves',
437
- xaxis_title='X values',
438
- yaxis_title='Y values',
439
- hovermode='x unified'
440
- )
441
-
442
- fig.show()
443
- print("Interactive Plotly chart created! 🎉")""",
444
- lines=18,
445
- label="Python Code with Plotly"
446
- )
447
-
448
- with gr.Row():
449
- execute_btn = gr.Button("🚀 Execute", variant="primary", size="lg")
450
- examples_btn = gr.Button("📋 Load Examples", variant="secondary")
451
-
452
- with gr.Column(scale=1):
453
- gr.Markdown("### 🎛️ Controls")
454
-
455
- status_display = gr.Textbox(
456
- label="Status",
457
- interactive=False,
458
- lines=4
459
- )
460
-
461
- with gr.Row():
462
- check_btn = gr.Button("📊 Status", size="sm")
463
- debug_btn = gr.Button("🐛 Debug", size="sm")
464
-
465
- # Example code snippets
466
- examples = {
467
- "Plotly Bar Chart": """import plotly.express as px
468
- import pandas as pd
469
-
470
- # Sample data
471
- data = {
472
- 'Category': ['A', 'B', 'C', 'D', 'E'],
473
- 'Values': [23, 45, 56, 78, 32],
474
- 'Colors': ['red', 'blue', 'green', 'orange', 'purple']
475
- }
476
- df = pd.DataFrame(data)
477
-
478
- # Create bar chart
479
- fig = px.bar(df, x='Category', y='Values', color='Colors',
480
- title='Interactive Bar Chart',
481
- labels={'Values': 'Count'})
482
-
483
- fig.show()
484
- print("Bar chart created!")""",
485
-
486
- "Plotly 3D Scatter": """import plotly.graph_objects as go
487
- import numpy as np
488
-
489
- # Generate 3D data
490
- n = 100
491
- x = np.random.randn(n)
492
- y = np.random.randn(n)
493
- z = np.random.randn(n)
494
- colors = np.random.randn(n)
495
-
496
- # Create 3D scatter plot
497
- fig = go.Figure(data=go.Scatter3d(
498
- x=x, y=y, z=z,
499
- mode='markers',
500
- marker=dict(
501
- size=8,
502
- color=colors,
503
- colorscale='Viridis',
504
- showscale=True
505
- )
506
- ))
507
-
508
- fig.update_layout(
509
- title='Interactive 3D Scatter Plot',
510
- scene=dict(
511
- xaxis_title='X Axis',
512
- yaxis_title='Y Axis',
513
- zaxis_title='Z Axis'
514
- )
515
- )
516
-
517
- fig.show()
518
- print("3D scatter plot created!")""",
519
-
520
- "Plotly Dashboard": """import plotly.graph_objects as go
521
- from plotly.subplots import make_subplots
522
- import numpy as np
523
-
524
- # Generate sample data
525
- x = np.linspace(0, 10, 50)
526
- y1 = np.sin(x)
527
- y2 = np.cos(x)
528
- y3 = np.random.normal(0, 0.1, len(x))
529
-
530
- # Create subplots
531
- fig = make_subplots(
532
- rows=2, cols=2,
533
- subplot_titles=('Line Plot', 'Histogram', 'Box Plot', 'Heatmap'),
534
- specs=[[{"secondary_y": True}, {}],
535
- [{}, {}]]
536
- )
537
-
538
- # Add line plot
539
- fig.add_trace(go.Scatter(x=x, y=y1, name='sin(x)'), row=1, col=1)
540
- fig.add_trace(go.Scatter(x=x, y=y2, name='cos(x)', yaxis='y2'), row=1, col=1, secondary_y=True)
541
-
542
- # Add histogram
543
- fig.add_trace(go.Histogram(x=np.random.normal(0, 1, 1000), name='Normal Dist'), row=1, col=2)
544
-
545
- # Add box plot
546
- categories = ['A', 'B', 'C']
547
- values = [np.random.normal(i, 0.5, 100) for i in range(len(categories))]
548
- for i, (cat, vals) in enumerate(zip(categories, values)):
549
- fig.add_trace(go.Box(y=vals, name=cat), row=2, col=1)
550
-
551
- # Add heatmap
552
- z = np.random.randn(10, 10)
553
- fig.add_trace(go.Heatmap(z=z, colorscale='RdBu'), row=2, col=2)
554
-
555
- fig.update_layout(height=600, title_text="Multi-Plot Dashboard")
556
- fig.show()
557
- print("Dashboard created with multiple charts!")"""
558
- }
559
-
560
- def load_example():
561
- return examples["Plotly Bar Chart"]
562
-
563
- examples_btn.click(
564
- fn=load_example,
565
- inputs=[],
566
- outputs=[code_input]
567
- )
568
-
569
- # Event handlers
570
- execute_btn.click(
571
- fn=None,
572
- inputs=[code_input],
573
- outputs=[status_display],
574
- js="""
575
- function(code) {
576
- try {
577
- if (window.executePyodideCode) {
578
- return window.executePyodideCode(code);
579
- } else {
580
- return 'Execution function not available';
581
- }
582
- } catch (error) {
583
- return 'Error: ' + error.message;
584
- }
585
- }
586
- """
587
- )
588
-
589
- check_btn.click(
590
- fn=None,
591
- inputs=[],
592
- outputs=[status_display],
593
- js="""
594
- function() {
595
- try {
596
- const ready = window.checkPyodideStatus ? window.checkPyodideStatus() : false;
597
- return ready ? '✅ Ready for Plotly!' : '⏳ Still loading...';
598
- } catch (error) {
599
- return 'Status error: ' + error.message;
600
- }
601
- }
602
- """
603
- )
604
-
605
- debug_btn.click(
606
- fn=None,
607
- inputs=[],
608
- outputs=[status_display],
609
- js="""
610
- function() {
611
- try {
612
- if (window.toggleDebugMode) {
613
- return window.toggleDebugMode() ? '🐛 Debug ON' : '🐛 Debug OFF';
614
- }
615
- return 'Debug toggle unavailable';
616
- } catch (error) {
617
- return 'Debug error: ' + error.message;
618
- }
619
- }
620
- """
621
- )
622
-
623
- if __name__ == "__main__":
624
- print("🚀 Starting Pyodide + Plotly Interpreter...")
625
- demo.launch(
626
- server_name="0.0.0.0",
627
- server_port=7860,
628
- share=False
629
- )
 
1
  #!/usr/bin/env python3
2
  """
3
+ LibreChat Pyodide Code Interpreter - Working Plotly Integration
4
  """
5
 
6
  import gradio as gr
7
 
8
  def create_pyodide_interface():
9
+ """Create a Gradio interface with working Plotly support"""
10
 
11
  pyodide_html = """
12
  <div id="pyodide-container" style="border: 1px solid #ddd; padding: 15px; border-radius: 5px; margin: 10px 0;">
13
  <div id="pyodide-status" style="font-weight: bold; padding: 10px; background: #f0f0f0; border-radius: 3px;">
14
+ 🔄 Loading Pyodide with Plotly...
 
 
 
 
15
  </div>
16
  <div id="pyodide-output" style="display:none; margin-top: 10px;">
17
  <h4>Execution Results:</h4>
18
+ <pre id="output-text" style="background: #f8f8f8; padding: 10px; border-radius: 3px; max-height: 200px; overflow-y: auto; white-space: pre-wrap;"></pre>
19
+ <div id="plot-container" style="margin-top: 15px;"></div>
20
  </div>
21
  </div>
22
 
23
+ <!-- Load Plotly.js -->
24
  <script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
25
 
26
  <script>
 
27
  let pyodide = null;
28
  let pyodideReady = false;
29
+ let plotCounter = 0;
 
30
 
31
+ function updateStatus(message, color = 'black') {
 
32
  const statusDiv = document.getElementById('pyodide-status');
33
  if (statusDiv) {
34
  statusDiv.innerHTML = message;
35
  statusDiv.style.color = color;
36
  }
37
+ console.log('Status:', message);
38
  }
39
 
40
+ // Custom Plotly renderer for Pyodide
41
+ window.renderPlotlyFromPython = function(plotData, plotLayout, plotConfig) {
42
+ try {
43
+ plotCounter++;
44
+ const plotId = 'pyodide-plot-' + plotCounter;
45
+ const plotContainer = document.getElementById('plot-container');
46
+
47
+ if (!plotContainer) {
48
+ console.error('Plot container not found');
49
+ return false;
50
+ }
51
+
52
+ // Create new plot div
53
+ const plotDiv = document.createElement('div');
54
+ plotDiv.id = plotId;
55
+ plotDiv.style.width = '100%';
56
+ plotDiv.style.height = '500px';
57
+ plotDiv.style.margin = '10px 0';
58
+ plotDiv.style.border = '1px solid #ddd';
59
+ plotDiv.style.borderRadius = '5px';
60
+
61
+ // Add title
62
+ const title = document.createElement('h5');
63
+ title.textContent = '📈 Interactive Plotly Chart #' + plotCounter;
64
+ title.style.margin = '10px 0 5px 0';
65
+
66
+ plotContainer.appendChild(title);
67
+ plotContainer.appendChild(plotDiv);
68
+
69
+ // Parse data if it's a string
70
+ if (typeof plotData === 'string') {
71
+ plotData = JSON.parse(plotData);
72
+ }
73
+ if (typeof plotLayout === 'string') {
74
+ plotLayout = JSON.parse(plotLayout);
75
+ }
76
+ if (typeof plotConfig === 'string') {
77
+ plotConfig = JSON.parse(plotConfig);
78
+ }
79
+
80
+ // Create the plot
81
+ Plotly.newPlot(plotId, plotData, plotLayout, plotConfig || {responsive: true});
82
+
83
+ console.log('Plotly chart rendered successfully:', plotId);
84
+ return true;
85
+
86
+ } catch (error) {
87
+ console.error('Plotly rendering error:', error);
88
+ return false;
89
  }
90
+ };
91
 
92
  async function initPyodide() {
 
 
 
 
 
 
93
  try {
 
94
  if (typeof loadPyodide === 'undefined') {
95
  throw new Error('Pyodide CDN not loaded');
96
  }
 
98
  throw new Error('Plotly CDN not loaded');
99
  }
100
 
101
+ updateStatus('🔄 Loading Pyodide...', 'blue');
 
102
 
103
  pyodide = await loadPyodide({
104
  indexURL: "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/"
105
  });
106
 
107
+ updateStatus('📦 Installing packages...', 'blue');
 
108
 
109
+ // Install packages
110
+ await pyodide.loadPackage(['numpy', 'pandas']);
111
 
112
+ // Install plotly via micropip
113
+ await pyodide.loadPackage(['micropip']);
114
+ await pyodide.runPythonAsync(`
115
+ import micropip
116
+ await micropip.install('plotly')
117
+ `);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
+ updateStatus('🔧 Setting up Plotly integration...', 'blue');
120
 
121
+ // Setup Python environment with proper Plotly integration
122
  pyodide.runPython(`
123
  import sys
124
+ import json
125
+ from js import renderPlotlyFromPython
126
 
127
+ print("Setting up Plotly integration...")
 
 
128
 
129
+ # Global storage
130
+ _plots_created = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
+ def show_plotly_figure(fig):
133
+ """Custom show function that renders plots in the browser"""
134
+ global _plots_created
135
  try:
136
+ # Convert figure to JSON
137
+ fig_json = fig.to_json()
138
+ fig_dict = json.loads(fig_json)
139
+
140
+ # Extract components
141
+ data = json.dumps(fig_dict.get('data', []))
142
+ layout = json.dumps(fig_dict.get('layout', {}))
143
+ config = json.dumps({'responsive': True, 'displayModeBar': True})
144
+
145
+ # Call JavaScript renderer
146
+ success = renderPlotlyFromPython(data, layout, config)
147
+
148
+ if success:
149
+ _plots_created += 1
150
+ print(f"✅ Plot #{_plots_created} rendered successfully!")
151
+ return True
152
+ else:
153
+ print("❌ Plot rendering failed")
154
+ return False
155
+
156
  except Exception as e:
157
+ print(f" Plot error: {e}")
158
+ return False
159
 
160
+ # Patch Plotly's show methods
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  try:
162
  import plotly.graph_objects as go
163
  import plotly.express as px
164
 
165
+ # Override the show method for graph_objects
166
+ original_show = go.Figure.show
167
+ def custom_show(self, *args, **kwargs):
168
+ return show_plotly_figure(self)
169
+ go.Figure.show = custom_show
170
 
171
+ print("✅ Plotly graph_objects patched")
172
+
173
+ # Test basic functionality
174
+ print("✅ Plotly integration ready!")
175
+ print("Use fig.show() to display interactive plots")
176
 
 
 
177
  except ImportError as e:
178
+ print(f"❌ Plotly import failed: {e}")
 
 
 
 
 
 
 
179
 
180
+ # Also set up matplotlib fallback
181
  try:
182
+ import matplotlib
183
+ matplotlib.use('Agg')
184
+ import matplotlib.pyplot as plt
185
+ print("✅ Matplotlib also available")
186
  except ImportError:
187
+ print("❌ Matplotlib not available")
188
 
189
+ print("🎉 Python environment ready!")
190
  `);
191
 
192
  pyodideReady = true;
193
  updateStatus('✅ Pyodide + Plotly ready!', 'green');
 
194
 
195
  // Show output area
196
  const outputDiv = document.getElementById('pyodide-output');
 
198
 
199
  const outputText = document.getElementById('output-text');
200
  if (outputText) {
201
+ outputText.textContent = 'Pyodide ready with Plotly support! Try the examples.';
202
  }
203
 
204
  } catch (error) {
205
  console.error('Initialization error:', error);
 
206
  updateStatus('❌ Failed: ' + error.message, 'red');
207
  pyodideReady = false;
208
  }
209
  }
210
 
211
  async function executePyodideCode(code) {
 
 
212
  if (!pyodideReady) {
213
+ return 'Pyodide not ready. Please wait for green status.';
214
  }
215
 
216
  if (!code || code.trim() === '') {
217
+ return 'No code provided.';
218
  }
219
 
220
  try {
221
+ updateStatus('▶️ Executing...', 'blue');
 
222
 
223
  // Clear previous plots
224
+ const plotContainer = document.getElementById('plot-container');
225
+ if (plotContainer) plotContainer.innerHTML = '';
226
 
227
  // Capture stdout
228
  pyodide.runPython(`
 
241
  captured_output.getvalue()
242
  `);
243
 
244
+ // Display text output
 
 
 
 
 
 
245
  const outputText = document.getElementById('output-text');
 
 
 
246
  if (outputText) {
247
  let textOutput = stdout || '';
248
  if (result !== undefined && result !== null && result !== '') {
249
  if (textOutput) textOutput += '\\n';
250
  textOutput += 'Return: ' + result;
251
  }
252
+ outputText.textContent = textOutput || 'Code executed successfully';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  }
254
 
255
+ updateStatus('✅ Executed!', 'green');
256
  return stdout || 'Code executed successfully';
257
 
258
  } catch (error) {
259
  console.error('Execution error:', error);
 
260
 
261
  const outputText = document.getElementById('output-text');
262
  if (outputText) {
263
  outputText.textContent = 'Error: ' + error.toString();
264
  }
265
+ updateStatus('❌ Error', 'red');
266
  return 'Error: ' + error.toString();
267
  }
268
  }
269
 
270
+ // Wait for both CDNs
271
+ function waitForReady() {
272
+ if (typeof loadPyodide !== 'undefined' && typeof Plotly !== 'undefined') {
273
+ console.log('Both CDNs loaded, initializing...');
274
+ initPyodide();
275
+ } else {
276
+ console.log('Waiting for CDNs... Pyodide:', typeof loadPyodide !== 'undefined', 'Plotly:', typeof Plotly !== 'undefined');
277
+ setTimeout(waitForReady, 1000);
 
 
 
 
 
 
 
 
 
 
 
 
278
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  }
280
 
281
+ // Start when ready
282
  if (document.readyState === 'loading') {
283
+ document.addEventListener('DOMContentLoaded', waitForReady);
284
  } else {
285
+ waitForReady();
286
  }
287
 
288
  // Global functions
289
  window.executePyodideCode = executePyodideCode;
290
  window.checkPyodideStatus = () => pyodideReady;
 
 
 
 
 
 
291
 
292
  </script>
293
+ <script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js" async></script>