mic3333 commited on
Commit
4b10064
·
verified ·
1 Parent(s): c4a9b3e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -515
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,168 +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 div
146
- import plotly.offline as pyo
147
- import plotly.io as pio
148
 
149
- # Method 1: Try offline plot
150
- try:
151
- _plotly_data = pyo.plot(fig, output_type='div', include_plotlyjs=False, div_id='plotly-div')
152
- print("Plotly captured via offline.plot")
153
- return _plotly_data
154
- except:
155
- # Method 2: Try to_html
156
- _plotly_data = fig.to_html(include_plotlyjs=False, div_id='plotly-div')
157
- print("Plotly captured via to_html")
158
- return _plotly_data
 
 
 
 
 
159
 
160
  except Exception as e:
161
- print("Plotly capture error: " + str(e))
162
- # Method 3: Fallback to JSON
163
- try:
164
- import json
165
- _plotly_data = json.dumps(fig.to_dict())
166
- print("Plotly captured as JSON (fallback)")
167
- return _plotly_data
168
- except:
169
- return None
170
-
171
- def get_matplotlib_data():
172
- return _matplotlib_data
173
-
174
- def get_plotly_data():
175
- return _plotly_data
176
 
177
- def clear_plot_data():
178
- global _matplotlib_data, _plotly_data
179
- _matplotlib_data = None
180
- _plotly_data = None
181
-
182
- # Setup matplotlib if available
183
- try:
184
- import matplotlib
185
- matplotlib.use('Agg')
186
- import matplotlib.pyplot as plt
187
-
188
- original_show = plt.show
189
- def custom_show(*args, **kwargs):
190
- return capture_matplotlib()
191
- plt.show = custom_show
192
-
193
- print("✅ Matplotlib configured")
194
- except ImportError:
195
- print("❌ Matplotlib not available")
196
-
197
- # Setup plotly if available
198
  try:
199
  import plotly.graph_objects as go
200
  import plotly.express as px
201
 
202
- # Custom show function for Plotly
203
- def show_plotly(fig):
204
- return capture_plotly(fig)
 
 
205
 
206
- # Monkey patch plotly's show
207
- original_plotly_show = go.Figure.show
208
- def custom_plotly_show(self, *args, **kwargs):
209
- return capture_plotly(self)
210
- go.Figure.show = custom_plotly_show
211
 
212
- print("✅ Plotly configured")
213
- print("Available: plotly.graph_objects as 'go', plotly.express as 'px'")
214
  except ImportError as e:
215
- print("❌ Plotly not available: " + str(e))
216
-
217
- # Test basic functionality
218
- try:
219
- import numpy as np
220
- print("✅ NumPy available")
221
- except ImportError:
222
- print("❌ NumPy not available")
223
 
 
224
  try:
225
- import pandas as pd
226
- print("✅ Pandas available")
 
 
227
  except ImportError:
228
- print("❌ Pandas not available")
229
 
230
- print("Environment setup complete!")
231
  `);
232
 
233
  pyodideReady = true;
234
  updateStatus('✅ Pyodide + Plotly ready!', 'green');
235
- debugLog('Full initialization complete');
236
 
237
  // Show output area
238
  const outputDiv = document.getElementById('pyodide-output');
@@ -240,34 +198,31 @@ print("Environment setup complete!")
240
 
241
  const outputText = document.getElementById('output-text');
242
  if (outputText) {
243
- outputText.textContent = 'Pyodide ready with Plotly support!\\n\\nTry the examples below or write your own code.';
244
  }
245
 
246
  } catch (error) {
247
  console.error('Initialization error:', error);
248
- debugLog('Init error: ' + error.message);
249
  updateStatus('❌ Failed: ' + error.message, 'red');
250
  pyodideReady = false;
251
  }
252
  }
253
 
254
  async function executePyodideCode(code) {
255
- debugLog('Execute function called');
256
-
257
  if (!pyodideReady) {
258
- return 'Pyodide is not ready. Please wait for green status.';
259
  }
260
 
261
  if (!code || code.trim() === '') {
262
- return 'Error: No code provided.';
263
  }
264
 
265
  try {
266
- updateStatus('▶️ Executing Python...', 'blue');
267
- debugLog('Executing: ' + code.substring(0, 50) + '...');
268
 
269
  // Clear previous plots
270
- pyodide.runPython('clear_plot_data()');
 
271
 
272
  // Capture stdout
273
  pyodide.runPython(`
@@ -286,383 +241,53 @@ sys.stdout = old_stdout
286
  captured_output.getvalue()
287
  `);
288
 
289
- // Get plot data
290
- let matplotlibData = pyodide.runPython('get_matplotlib_data()');
291
- let plotlyData = pyodide.runPython('get_plotly_data()');
292
-
293
- debugLog('Execution completed');
294
-
295
- // Display results
296
  const outputText = document.getElementById('output-text');
297
- const plotContainer = document.getElementById('plot-container');
298
-
299
- // Handle text output
300
  if (outputText) {
301
  let textOutput = stdout || '';
302
  if (result !== undefined && result !== null && result !== '') {
303
  if (textOutput) textOutput += '\\n';
304
  textOutput += 'Return: ' + result;
305
  }
306
- outputText.textContent = textOutput || 'Code executed successfully (no text output)';
307
- }
308
-
309
- // Handle plots
310
- let plotHTML = '';
311
-
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
- }
322
-
323
- if (plotlyData && plotlyData.length > 100) {
324
- // Check if it's JSON (fallback method)
325
- if (plotlyData.startsWith('{') || plotlyData.startsWith('[')) {
326
- try {
327
- const plotData = JSON.parse(plotlyData);
328
- plotHTML += `
329
- <div style="margin: 10px 0;">
330
- <h5>📈 Interactive Plotly Chart:</h5>
331
- <div id="plotly-chart-${Date.now()}" style="width: 100%; height: 500px; border: 1px solid #ddd;"></div>
332
- </div>
333
- `;
334
- // Render after DOM update
335
- setTimeout(() => {
336
- const chartId = document.querySelector('[id^="plotly-chart-"]').id;
337
- Plotly.newPlot(chartId, plotData.data, plotData.layout);
338
- }, 100);
339
- } catch (e) {
340
- debugLog('JSON plot rendering failed: ' + e.message);
341
- }
342
- } else {
343
- // HTML method
344
- plotHTML += `
345
- <div style="margin: 10px 0;">
346
- <h5>📈 Interactive Plotly Chart:</h5>
347
- <div style="border: 1px solid #ddd; border-radius: 5px; padding: 10px;">
348
- ${plotlyData}
349
- </div>
350
- </div>
351
- `;
352
- }
353
- }
354
-
355
- if (plotContainer) {
356
- plotContainer.innerHTML = plotHTML;
357
- }
358
-
359
- if (plotHTML) {
360
- updateStatus('✅ Executed with plot(s)!', 'green');
361
- } else {
362
- updateStatus('✅ Executed successfully!', 'green');
363
  }
364
 
 
365
  return stdout || 'Code executed successfully';
366
 
367
  } catch (error) {
368
  console.error('Execution error:', error);
369
- debugLog('Error: ' + error.message);
370
 
371
  const outputText = document.getElementById('output-text');
372
  if (outputText) {
373
  outputText.textContent = 'Error: ' + error.toString();
374
  }
375
- updateStatus('❌ Execution failed', 'red');
376
  return 'Error: ' + error.toString();
377
  }
378
  }
379
 
380
- // Safe initialization with retry
381
- async function safeInit() {
382
- let retries = 0;
383
- const maxRetries = 3;
384
-
385
- while (retries < maxRetries) {
386
- try {
387
- if (typeof loadPyodide !== 'undefined' && typeof Plotly !== 'undefined') {
388
- await initPyodide();
389
- return;
390
- }
391
- } catch (error) {
392
- debugLog(`Init attempt ${retries + 1} failed: ${error.message}`);
393
- }
394
-
395
- retries++;
396
- if (retries < maxRetries) {
397
- debugLog(`Retrying in ${retries * 2} seconds...`);
398
- await new Promise(resolve => setTimeout(resolve, retries * 2000));
399
- }
400
  }
401
-
402
- updateStatus('❌ Initialization failed after ' + maxRetries + ' attempts', 'red');
403
  }
404
 
405
- // Wait for both CDNs to load
406
- function waitForCDNs() {
407
- const checkInterval = setInterval(() => {
408
- if (typeof loadPyodide !== 'undefined' && typeof Plotly !== 'undefined') {
409
- clearInterval(checkInterval);
410
- debugLog('Both CDNs loaded, starting init');
411
- safeInit();
412
- } else {
413
- debugLog('Waiting for CDNs... Pyodide: ' + (typeof loadPyodide !== 'undefined') + ', Plotly: ' + (typeof Plotly !== 'undefined'));
414
- }
415
- }, 1000);
416
-
417
- // Timeout after 30 seconds
418
- setTimeout(() => {
419
- clearInterval(checkInterval);
420
- if (!pyodideReady) {
421
- updateStatus('❌ CDN loading timeout', 'red');
422
- }
423
- }, 30000);
424
- }
425
-
426
- // Start when DOM is ready
427
  if (document.readyState === 'loading') {
428
- document.addEventListener('DOMContentLoaded', waitForCDNs);
429
  } else {
430
- waitForCDNs();
431
  }
432
 
433
  // Global functions
434
  window.executePyodideCode = executePyodideCode;
435
  window.checkPyodideStatus = () => pyodideReady;
436
- window.toggleDebugMode = function() {
437
- debugMode = !debugMode;
438
- const debugDiv = document.getElementById('debug-info');
439
- if (debugDiv) debugDiv.style.display = debugMode ? 'block' : 'none';
440
- return debugMode;
441
- };
442
 
443
  </script>
444
- <script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js" async></script>
445
- """
446
-
447
- return pyodide_html
448
-
449
- # Create the Gradio interface
450
- with gr.Blocks(title="Pyodide + Plotly Code Interpreter") as demo:
451
- gr.Markdown("# 🐍📈 Pyodide + Plotly Code Interpreter")
452
- gr.Markdown("**Interactive Python with Plotly charts** - runs entirely in your browser!")
453
-
454
- # Pyodide interface
455
- pyodide_interface = gr.HTML(create_pyodide_interface())
456
-
457
- with gr.Row():
458
- with gr.Column(scale=2):
459
- code_input = gr.Textbox(
460
- value="""# Plotly Example 1: Simple Line Chart
461
- import plotly.graph_objects as go
462
- import numpy as np
463
-
464
- # Generate data
465
- x = np.linspace(0, 10, 100)
466
- y1 = np.sin(x)
467
- y2 = np.cos(x)
468
-
469
- # Create figure
470
- fig = go.Figure()
471
- fig.add_trace(go.Scatter(x=x, y=y1, name='sin(x)', line=dict(color='blue')))
472
- fig.add_trace(go.Scatter(x=x, y=y2, name='cos(x)', line=dict(color='red')))
473
-
474
- fig.update_layout(
475
- title='Interactive Sine and Cosine Waves',
476
- xaxis_title='X values',
477
- yaxis_title='Y values',
478
- hovermode='x unified'
479
- )
480
-
481
- fig.show()
482
- print("Interactive Plotly chart created! 🎉")""",
483
- lines=18,
484
- label="Python Code with Plotly"
485
- )
486
-
487
- with gr.Row():
488
- execute_btn = gr.Button("🚀 Execute", variant="primary", size="lg")
489
- examples_btn = gr.Button("📋 Load Examples", variant="secondary")
490
-
491
- with gr.Column(scale=1):
492
- gr.Markdown("### 🎛️ Controls")
493
-
494
- status_display = gr.Textbox(
495
- label="Status",
496
- interactive=False,
497
- lines=4
498
- )
499
-
500
- with gr.Row():
501
- check_btn = gr.Button("📊 Status", size="sm")
502
- debug_btn = gr.Button("🐛 Debug", size="sm")
503
-
504
- # Example code snippets
505
- examples = {
506
- "Plotly Bar Chart": """import plotly.express as px
507
- import pandas as pd
508
-
509
- # Sample data
510
- data = {
511
- 'Category': ['A', 'B', 'C', 'D', 'E'],
512
- 'Values': [23, 45, 56, 78, 32],
513
- 'Colors': ['red', 'blue', 'green', 'orange', 'purple']
514
- }
515
- df = pd.DataFrame(data)
516
-
517
- # Create bar chart
518
- fig = px.bar(df, x='Category', y='Values', color='Colors',
519
- title='Interactive Bar Chart',
520
- labels={'Values': 'Count'})
521
-
522
- fig.show()
523
- print("Bar chart created!")""",
524
-
525
- "Plotly 3D Scatter": """import plotly.graph_objects as go
526
- import numpy as np
527
-
528
- # Generate 3D data
529
- n = 100
530
- x = np.random.randn(n)
531
- y = np.random.randn(n)
532
- z = np.random.randn(n)
533
- colors = np.random.randn(n)
534
-
535
- # Create 3D scatter plot
536
- fig = go.Figure(data=go.Scatter3d(
537
- x=x, y=y, z=z,
538
- mode='markers',
539
- marker=dict(
540
- size=8,
541
- color=colors,
542
- colorscale='Viridis',
543
- showscale=True
544
- )
545
- ))
546
-
547
- fig.update_layout(
548
- title='Interactive 3D Scatter Plot',
549
- scene=dict(
550
- xaxis_title='X Axis',
551
- yaxis_title='Y Axis',
552
- zaxis_title='Z Axis'
553
- )
554
- )
555
-
556
- fig.show()
557
- print("3D scatter plot created!")""",
558
-
559
- "Plotly Dashboard": """import plotly.graph_objects as go
560
- from plotly.subplots import make_subplots
561
- import numpy as np
562
-
563
- # Generate sample data
564
- x = np.linspace(0, 10, 50)
565
- y1 = np.sin(x)
566
- y2 = np.cos(x)
567
- y3 = np.random.normal(0, 0.1, len(x))
568
-
569
- # Create subplots
570
- fig = make_subplots(
571
- rows=2, cols=2,
572
- subplot_titles=('Line Plot', 'Histogram', 'Box Plot', 'Heatmap'),
573
- specs=[[{"secondary_y": True}, {}],
574
- [{}, {}]]
575
- )
576
-
577
- # Add line plot
578
- fig.add_trace(go.Scatter(x=x, y=y1, name='sin(x)'), row=1, col=1)
579
- fig.add_trace(go.Scatter(x=x, y=y2, name='cos(x)', yaxis='y2'), row=1, col=1, secondary_y=True)
580
-
581
- # Add histogram
582
- fig.add_trace(go.Histogram(x=np.random.normal(0, 1, 1000), name='Normal Dist'), row=1, col=2)
583
-
584
- # Add box plot
585
- categories = ['A', 'B', 'C']
586
- values = [np.random.normal(i, 0.5, 100) for i in range(len(categories))]
587
- for i, (cat, vals) in enumerate(zip(categories, values)):
588
- fig.add_trace(go.Box(y=vals, name=cat), row=2, col=1)
589
-
590
- # Add heatmap
591
- z = np.random.randn(10, 10)
592
- fig.add_trace(go.Heatmap(z=z, colorscale='RdBu'), row=2, col=2)
593
-
594
- fig.update_layout(height=600, title_text="Multi-Plot Dashboard")
595
- fig.show()
596
- print("Dashboard created with multiple charts!")"""
597
- }
598
-
599
- def load_example():
600
- return examples["Plotly Bar Chart"]
601
-
602
- examples_btn.click(
603
- fn=load_example,
604
- inputs=[],
605
- outputs=[code_input]
606
- )
607
-
608
- # Event handlers
609
- execute_btn.click(
610
- fn=None,
611
- inputs=[code_input],
612
- outputs=[status_display],
613
- js="""
614
- function(code) {
615
- try {
616
- if (window.executePyodideCode) {
617
- return window.executePyodideCode(code);
618
- } else {
619
- return 'Execution function not available';
620
- }
621
- } catch (error) {
622
- return 'Error: ' + error.message;
623
- }
624
- }
625
- """
626
- )
627
-
628
- check_btn.click(
629
- fn=None,
630
- inputs=[],
631
- outputs=[status_display],
632
- js="""
633
- function() {
634
- try {
635
- const ready = window.checkPyodideStatus ? window.checkPyodideStatus() : false;
636
- return ready ? '✅ Ready for Plotly!' : '⏳ Still loading...';
637
- } catch (error) {
638
- return 'Status error: ' + error.message;
639
- }
640
- }
641
- """
642
- )
643
-
644
- debug_btn.click(
645
- fn=None,
646
- inputs=[],
647
- outputs=[status_display],
648
- js="""
649
- function() {
650
- try {
651
- if (window.toggleDebugMode) {
652
- return window.toggleDebugMode() ? '🐛 Debug ON' : '🐛 Debug OFF';
653
- }
654
- return 'Debug toggle unavailable';
655
- } catch (error) {
656
- return 'Debug error: ' + error.message;
657
- }
658
- }
659
- """
660
- )
661
-
662
- if __name__ == "__main__":
663
- print("🚀 Starting Pyodide + Plotly Interpreter...")
664
- demo.launch(
665
- server_name="0.0.0.0",
666
- server_port=7860,
667
- share=False
668
- )
 
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>