import { loadPyodide, type PyodideInterface } from 'pyodide'; declare global { interface Window { stdout: string | null; stderr: string | null; // eslint-disable-next-line @typescript-eslint/no-explicit-any result: any; pyodide: PyodideInterface; packages: string[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } } async function loadPyodideAndPackages(packages: string[] = []) { self.stdout = null; self.stderr = null; self.result = null; self.pyodide = await loadPyodide({ indexURL: '/pyodide/', stdout: (text) => { console.log('Python output:', text); if (self.stdout) { self.stdout += `${text}\n`; } else { self.stdout = `${text}\n`; } }, stderr: (text) => { console.log('An error occurred:', text); if (self.stderr) { self.stderr += `${text}\n`; } else { self.stderr = `${text}\n`; } }, packages: ['micropip'] }); let mountDir = '/mnt'; self.pyodide.FS.mkdirTree(mountDir); // self.pyodide.FS.mount(self.pyodide.FS.filesystems.IDBFS, {}, mountDir); // // Load persisted files from IndexedDB (Initial Sync) // await new Promise((resolve, reject) => { // self.pyodide.FS.syncfs(true, (err) => { // if (err) { // console.error('Error syncing from IndexedDB:', err); // reject(err); // } else { // console.log('Successfully loaded from IndexedDB.'); // resolve(); // } // }); // }); const micropip = self.pyodide.pyimport('micropip'); // await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json'); await micropip.install(packages); } self.onmessage = async (event) => { const { id, code, ...context } = event.data; console.log(event.data); // The worker copies the context in its own "memory" (an object mapping name to values) for (const key of Object.keys(context)) { self[key] = context[key]; } // make sure loading is done await loadPyodideAndPackages(self.packages); try { // check if matplotlib is imported in the code if (code.includes('matplotlib')) { // Override plt.show() to return base64 image await self.pyodide.runPythonAsync(`import base64 import os from io import BytesIO # before importing matplotlib # to avoid the wasm backend (which needs js.document', not available in worker) os.environ["MPLBACKEND"] = "AGG" import matplotlib.pyplot _old_show = matplotlib.pyplot.show assert _old_show, "matplotlib.pyplot.show" def show(*, block=None): buf = BytesIO() matplotlib.pyplot.savefig(buf, format="png") buf.seek(0) # encode to a base64 str img_str = base64.b64encode(buf.read()).decode('utf-8') matplotlib.pyplot.clf() buf.close() print(f"data:image/png;base64,{img_str}") matplotlib.pyplot.show = show`); } self.result = await self.pyodide.runPythonAsync(code); // Safely process and recursively serialize the result self.result = processResult(self.result); console.log('Python result:', self.result); // Persist any changes to IndexedDB // await new Promise((resolve, reject) => { // self.pyodide.FS.syncfs(false, (err) => { // if (err) { // console.error('Error syncing to IndexedDB:', err); // reject(err); // } else { // console.log('Successfully synced to IndexedDB.'); // resolve(); // } // }); // }); } catch (error) { self.stderr = error.toString(); } self.postMessage({ id, result: self.result, stdout: self.stdout, stderr: self.stderr }); }; function processResult(result: any): any { // Catch and always return JSON-safe string representations try { if (result == null) { // Handle null and undefined return null; } if (typeof result === 'string' || typeof result === 'number' || typeof result === 'boolean') { // Handle primitive types directly return result; } if (typeof result === 'bigint') { // Convert BigInt to a string for JSON-safe representation return result.toString(); } if (Array.isArray(result)) { // If it's an array, recursively process items return result.map((item) => processResult(item)); } if (typeof result.toJs === 'function') { // If it's a Pyodide proxy object (e.g., Pandas DF, Numpy Array), convert to JS and process recursively return processResult(result.toJs()); } if (typeof result === 'object') { // Convert JS objects to a recursively serialized representation const processedObject: { [key: string]: any } = {}; for (const key in result) { if (Object.prototype.hasOwnProperty.call(result, key)) { processedObject[key] = processResult(result[key]); } } return processedObject; } // Stringify anything that's left (e.g., Proxy objects that cannot be directly processed) return JSON.stringify(result); } catch (err) { // In case something unexpected happens, we return a stringified fallback return `[processResult error]: ${err.message || err.toString()}`; } } export default {};