|
@@ -0,0 +1,123 @@
|
|
|
+import { loadPyodide, type PyodideInterface } from 'pyodide';
|
|
|
+
|
|
|
+declare global {
|
|
|
+ interface Window {
|
|
|
+ stdout: string | null;
|
|
|
+ stderr: string | null;
|
|
|
+ pyodide: PyodideInterface;
|
|
|
+ cells: Record<string, CellState>;
|
|
|
+ indexURL: string;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+type CellState = {
|
|
|
+ id: string;
|
|
|
+ status: 'idle' | 'running' | 'completed' | 'error';
|
|
|
+ result: any;
|
|
|
+ stdout: string;
|
|
|
+ stderr: string;
|
|
|
+};
|
|
|
+
|
|
|
+const initializePyodide = async () => {
|
|
|
+ // Ensure Pyodide is loaded once and cached in the worker's global scope
|
|
|
+ if (!self.pyodide) {
|
|
|
+ self.indexURL = '/pyodide/';
|
|
|
+ self.stdout = '';
|
|
|
+ self.stderr = '';
|
|
|
+ self.cells = {};
|
|
|
+
|
|
|
+ self.pyodide = await loadPyodide({
|
|
|
+ indexURL: self.indexURL
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const executeCode = async (id: string, code: string) => {
|
|
|
+ if (!self.pyodide) {
|
|
|
+ await initializePyodide();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update the cell state to "running"
|
|
|
+ self.cells[id] = {
|
|
|
+ id,
|
|
|
+ status: 'running',
|
|
|
+ result: null,
|
|
|
+ stdout: '',
|
|
|
+ stderr: ''
|
|
|
+ };
|
|
|
+
|
|
|
+ // Redirect stdout/stderr to stream updates
|
|
|
+ self.pyodide.setStdout({
|
|
|
+ batched: (msg: string) => {
|
|
|
+ self.cells[id].stdout += msg;
|
|
|
+ self.postMessage({ type: 'stdout', id, message: msg });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ self.pyodide.setStderr({
|
|
|
+ batched: (msg: string) => {
|
|
|
+ self.cells[id].stderr += msg;
|
|
|
+ self.postMessage({ type: 'stderr', id, message: msg });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Dynamically load required packages based on imports in the Python code
|
|
|
+ await self.pyodide.loadPackagesFromImports(code, {
|
|
|
+ messageCallback: (msg: string) => {
|
|
|
+ self.postMessage({ type: 'stdout', id, package: true, message: `[package] ${msg}` });
|
|
|
+ },
|
|
|
+ errorCallback: (msg: string) => {
|
|
|
+ self.postMessage({ type: 'stderr', id, package: true, message: `[package] ${msg}` });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Execute the Python code
|
|
|
+ const result = await self.pyodide.runPythonAsync(code);
|
|
|
+ self.cells[id].result = result;
|
|
|
+ self.cells[id].status = 'completed';
|
|
|
+ } catch (error) {
|
|
|
+ self.cells[id].status = 'error';
|
|
|
+ self.cells[id].stderr += `\n${error.toString()}`;
|
|
|
+ } finally {
|
|
|
+ // Notify parent thread when execution completes
|
|
|
+ self.postMessage({
|
|
|
+ type: 'result',
|
|
|
+ id,
|
|
|
+ state: self.cells[id]
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// Handle messages from the main thread
|
|
|
+self.onmessage = async (event) => {
|
|
|
+ const { type, id, code, ...args } = event.data;
|
|
|
+
|
|
|
+ switch (type) {
|
|
|
+ case 'initialize':
|
|
|
+ await initializePyodide();
|
|
|
+ self.postMessage({ type: 'initialized' });
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'execute':
|
|
|
+ if (id && code) {
|
|
|
+ await executeCode(id, code);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'getState':
|
|
|
+ self.postMessage({
|
|
|
+ type: 'kernelState',
|
|
|
+ state: self.cells
|
|
|
+ });
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'terminate':
|
|
|
+ // Explicitly clear the worker for cleanup
|
|
|
+ for (const key in self.cells) delete self.cells[key];
|
|
|
+ self.close();
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ console.error(`Unknown message type: ${type}`);
|
|
|
+ }
|
|
|
+};
|