File size: 14,216 Bytes
d1ae699
 
81b031b
d1ae699
 
 
81b031b
 
 
 
8ec449d
81b031b
8ec449d
 
 
 
 
 
 
 
81b031b
 
 
 
 
 
8ec449d
 
 
 
 
 
 
 
 
d1ae699
81b031b
8ec449d
 
 
81b031b
8ec449d
 
 
 
 
 
5d6ace3
8ec449d
5d6ace3
 
81b031b
76038a5
81b031b
 
 
 
 
 
5d6ace3
 
 
81b031b
 
5d6ace3
81b031b
5d6ace3
76038a5
5d6ace3
76038a5
5d6ace3
76038a5
8ec449d
76038a5
 
 
 
 
 
 
5d6ace3
76038a5
5d6ace3
8ec449d
5d6ace3
8ec449d
81b031b
5d6ace3
 
 
 
 
 
 
 
76038a5
5d6ace3
81b031b
 
5d6ace3
81b031b
8ec449d
5d6ace3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81b031b
 
8ec449d
 
 
 
 
 
81b031b
8ec449d
 
 
d1ae699
81b031b
d1ae699
81b031b
8ec449d
 
 
 
 
 
 
81b031b
8ec449d
d1ae699
81b031b
 
8ec449d
 
76038a5
 
 
81b031b
 
 
 
 
 
 
 
 
 
 
76038a5
81b031b
 
 
 
 
5d6ace3
 
 
76038a5
 
5d6ace3
 
 
 
81b031b
 
 
 
 
 
 
 
76038a5
 
81b031b
5d6ace3
 
 
 
 
76038a5
5d6ace3
 
 
 
76038a5
 
5d6ace3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76038a5
5d6ace3
e286f46
5d6ace3
e286f46
5d6ace3
81b031b
8ec449d
e45c34b
81b031b
5d6ace3
 
81b031b
8ec449d
81b031b
8ec449d
81b031b
 
e45c34b
 
8ec449d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eebd9e7
76038a5
81b031b
8ec449d
81b031b
 
65e8ad7
81b031b
eebd9e7
81b031b
 
 
 
8ec449d
81b031b
 
 
65e8ad7
 
 
 
8ec449d
 
 
81b031b
8ec449d
 
 
 
 
 
 
81b031b
 
 
 
 
8ec449d
81b031b
 
8ec449d
81b031b
8ec449d
81b031b
 
 
65e8ad7
81b031b
65e8ad7
8ec449d
81b031b
 
 
 
 
 
 
8ec449d
81b031b
 
 
d1ae699
81b031b
8ec449d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81b031b
d1ae699
 
 
81b031b
 
65e8ad7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
#!/usr/bin/env python3
"""
LibreChat Pyodide Code Interpreter - Client-Side Python Execution
"""

import gradio as gr

def create_pyodide_interface():
    """Create a Gradio interface that uses Pyodide for client-side Python execution"""
    
    # Enhanced HTML/JS with better error handling and status updates
    pyodide_html = """
    <div id="pyodide-container" style="border: 1px solid #ddd; padding: 15px; border-radius: 5px; margin: 10px 0;">
        <div id="pyodide-status" style="font-weight: bold; padding: 10px; background: #f0f0f0; border-radius: 3px;">
            πŸ”„ Loading Pyodide... This may take 10-30 seconds on first load.
        </div>
        <div id="pyodide-output" style="display:none; margin-top: 10px;">
            <h4>Execution Results:</h4>
            <pre id="output-text" style="background: #f8f8f8; padding: 10px; border-radius: 3px; max-height: 300px; overflow-y: auto;"></pre>
            <div id="plot-container" style="text-align: center; margin-top: 10px;"></div>
        </div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js"></script>
    <script>
        let pyodide = null;
        let pyodideReady = false;
        let initializationStarted = false;
        
        function updateStatus(message, color = 'black') {
            const statusDiv = document.getElementById('pyodide-status');
            statusDiv.innerHTML = message;
            statusDiv.style.color = color;
            console.log('Pyodide Status:', message);
        }
        
        async function initPyodide() {
            if (initializationStarted) return;
            initializationStarted = true;
            
            try {
                updateStatus('πŸ”„ Loading Pyodide core...', 'blue');
                
                pyodide = await loadPyodide({
                    indexURL: "https://cdn.jsdelivr.net/pyodide/v0.24.1/full/"
                });
                
                updateStatus('πŸ“¦ Loading Python packages (numpy, matplotlib, pandas, plotly)...', 'blue');
                
                await pyodide.loadPackage(["numpy", "matplotlib", "pandas", "plotly"]);
                updateStatus('πŸ“¦ All packages loaded, setting up backends...', 'blue');
                
                // Set up matplotlib backend for web - IMPROVED VERSION
                pyodide.runPython(`
                    import matplotlib
                    matplotlib.use('AGG')
                    import matplotlib.pyplot as plt
                    import numpy as np
                    import pandas as pd
                    import plotly.graph_objects as go
                    import plotly.express as px
                    import plotly
                    import io
                    import base64
                    import json
                    
                    # Global variables for plot data
                    _current_plot_data = None
                    _current_plotly_data = None
                    
                    def capture_matplotlib():
                        global _current_plot_data
                        try:
                            if len(plt.get_fignums()) > 0:
                                buffer = io.BytesIO()
                                plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100)
                                buffer.seek(0)
                                plot_data = buffer.getvalue()
                                buffer.close()
                                _current_plot_data = base64.b64encode(plot_data).decode()
                                plt.close('all')
                                return _current_plot_data
                            return None
                        except Exception as e:
                            print(f"Matplotlib capture error: {e}")
                            return None
                    
                    def capture_plotly(fig):
                        global _current_plotly_data
                        try:
                            _current_plotly_data = fig.to_html(include_plotlyjs='cdn', div_id='plotly-div')
                            return _current_plotly_data
                        except Exception as e:
                            print(f"Plotly capture error: {e}")
                            return None
                    
                    # Override functions
                    original_show = plt.show
                    def custom_show(*args, **kwargs):
                        return capture_matplotlib()
                    plt.show = custom_show
                    
                    # Override plotly show
                    original_plotly_show = go.Figure.show
                    def custom_plotly_show(self, *args, **kwargs):
                        return capture_plotly(self)
                    go.Figure.show = custom_plotly_show
                    
                    def get_matplotlib_data():
                        return _current_plot_data
                    
                    def get_plotly_data():
                        return _current_plotly_data
                    
                    def clear_plot_data():
                        global _current_plot_data, _current_plotly_data
                        _current_plot_data = None
                        _current_plotly_data = None
                    
                    print("Pyodide ready with matplotlib AND plotly support!")
                `);
                
                pyodideReady = true;
                updateStatus('βœ… Pyodide ready! You can now execute Python code.', 'green');
                
                document.getElementById('pyodide-output').style.display = 'block';
                document.getElementById('output-text').textContent = 'Pyodide initialization complete! Ready to execute Python code.';
                
            } catch (error) {
                console.error('Pyodide initialization error:', error);
                updateStatus('❌ Failed to initialize Pyodide: ' + error.toString(), 'red');
                pyodideReady = false;
            }
        }
        
        async function executePyodideCode(code) {
            console.log('Execute called, pyodideReady:', pyodideReady);
            
            if (!pyodideReady) {
                updateStatus('⏳ Pyodide is still initializing. Please wait...', 'orange');
                return "Pyodide is still loading. Please wait for the green 'ready' status above.";
            }
            
            if (!pyodide) {
                return "Error: Pyodide not available.";
            }
            
            try {
                updateStatus('▢️ Executing Python code...', 'blue');
                
                // Clear any previous plot data
                pyodide.runPython('clear_plot_data()');
                
                // Capture stdout
                pyodide.runPython(`
                    import sys
                    from io import StringIO
                    old_stdout = sys.stdout
                    sys.stdout = captured_output = StringIO()
                `);
                
                // Execute user code
                let result = pyodide.runPython(code);
                
                // Get captured output (this should NOT contain base64 data)
                let stdout = pyodide.runPython(`
                    sys.stdout = old_stdout
                    captured_output.getvalue()
                `);
                
                // Get both types of plot data
                let matplotlibData = pyodide.runPython('get_matplotlib_data()');
                let plotlyData = pyodide.runPython('get_plotly_data()');
                
                console.log('Stdout length:', stdout ? stdout.length : 0);
                console.log('Matplotlib data length:', matplotlibData ? matplotlibData.length : 0);
                console.log('Plotly data length:', plotlyData ? plotlyData.length : 0);
                console.log('Matplotlib data preview:', matplotlibData ? matplotlibData.substring(0, 50) + '...' : 'None');
                console.log('Plotly data preview:', plotlyData ? plotlyData.substring(0, 50) + '...' : 'None');
                
                // Display results
                let outputDiv = document.getElementById('pyodide-output');
                let outputText = document.getElementById('output-text');
                let plotContainer = document.getElementById('plot-container');
                
                outputDiv.style.display = 'block';
                
                let textOutput = stdout || (result !== undefined ? String(result) : '');
                outputText.textContent = textOutput || 'Code executed successfully (no output)';
                
                // Display plots
                let plotHtml = '';
                
                if (matplotlibData && matplotlibData.length > 100) {
                    plotHtml += `
                        <div style="margin: 10px 0;">
                            <h5>πŸ“Š Matplotlib Plot:</h5>
                            <img src="data:image/png;base64,${matplotlibData}" 
                                 style="max-width: 100%; height: auto; border: 1px solid #ddd;" 
                                 alt="Matplotlib Plot">
                        </div>
                    `;
                }
                
                if (plotlyData) {
                    plotHtml += `
                        <div style="margin: 10px 0;">
                            <h5>πŸ“Š Interactive Plotly Plot:</h5>
                            <div style="border: 1px solid #ddd; border-radius: 3px;">
                                ${plotlyData}
                            </div>
                        </div>
                    `;
                }
                
                plotContainer.innerHTML = plotHtml;
                
                if (plotHtml) {
                    if (textOutput.trim()) {
                        outputText.textContent += '\\n\\nπŸ“Š Plot(s) generated and displayed below!';
                    } else {
                        outputText.textContent = 'πŸ“Š Plot(s) generated and displayed below!';
                    }
                    updateStatus('βœ… Code executed with plot(s) generated!', 'green');
                } else {
                    updateStatus('βœ… Code executed successfully!', 'green');
                }
                
                return textOutput || 'Code executed successfully';
                
            } catch (error) {
                console.error('Execution error:', error);
                document.getElementById('output-text').textContent = 'Error: ' + error.toString();
                updateStatus('❌ Execution error: ' + error.toString(), 'red');
                return 'Error: ' + error.toString();
            }
        }
        
        // Auto-retry initialization if it fails
        async function safeInitPyodide() {
            try {
                await initPyodide();
            } catch (error) {
                console.error('Init failed, retrying in 3 seconds:', error);
                updateStatus('πŸ”„ Initialization failed, retrying in 3 seconds...', 'orange');
                setTimeout(safeInitPyodide, 3000);
            }
        }
        
        // Initialize when page loads
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', safeInitPyodide);
        } else {
            safeInitPyodide();
        }
        
        // Make functions globally available
        window.executePyodideCode = executePyodideCode;
        window.checkPyodideStatus = () => pyodideReady;
    </script>
    """
    
    return pyodide_html

# Create the Gradio interface
with gr.Blocks(title="LibreChat Pyodide Code Interpreter") as demo:
    gr.Markdown("# LibreChat Pyodide Code Interpreter")
    gr.Markdown("**Client-side Python execution** using Pyodide - Python runs directly in your browser!")
    gr.Markdown("⏳ **Please wait for Pyodide to fully initialize before executing code (watch the status above).**")
    
    # Pyodide interface
    pyodide_interface = gr.HTML(create_pyodide_interface())
    
    with gr.Row():
        with gr.Column():
            code_input = gr.Textbox(
                value="""# Simple test - try this first
print("Hello from browser-based Python!")
print("2 + 2 =", 2 + 2)

# Then try this matplotlib example:
# import matplotlib.pyplot as plt
# import numpy as np
# x = np.linspace(0, 10, 50)
# plt.plot(x, np.sin(x))
# plt.title('Sine Wave')
# plt.show()""",
                lines=15,
                label="Python Code (executes in your browser)"
            )
            
            execute_btn = gr.Button("Execute with Pyodide", variant="primary")
            check_status_btn = gr.Button("Check Pyodide Status", variant="secondary")
        
        with gr.Column():
            gr.Markdown("### Execution Status")
            status_display = gr.Textbox(
                label="Last Execution Result",
                interactive=False,
                lines=3
            )
    
    # JavaScript execution
    execute_btn.click(
        fn=None,
        inputs=[code_input],
        outputs=[status_display],
        js="""
        function(code) {
            if (window.executePyodideCode) {
                return window.executePyodideCode(code);
            } else {
                return "Pyodide functions not available. Check browser console for errors.";
            }
        }
        """
    )
    
    # Status check button
    check_status_btn.click(
        fn=None,
        inputs=[],
        outputs=[status_display],
        js="""
        function() {
            if (window.checkPyodideStatus) {
                return window.checkPyodideStatus() ? "βœ… Pyodide is ready!" : "⏳ Pyodide is still loading...";
            } else {
                return "❌ Pyodide not available.";
            }
        }
        """
    )

if __name__ == "__main__":
    demo.launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=False
    )