| | 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 () => { |
| | |
| | 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(); |
| | } |
| |
|
| | |
| | self.cells[id] = { |
| | id, |
| | status: 'running', |
| | result: null, |
| | stdout: '', |
| | stderr: '' |
| | }; |
| |
|
| | |
| | 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 { |
| | |
| | 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}` }); |
| | } |
| | }); |
| |
|
| | |
| | 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 { |
| | |
| | self.postMessage({ |
| | type: 'result', |
| | id, |
| | state: self.cells[id] |
| | }); |
| | } |
| | }; |
| |
|
| | |
| | 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': |
| | |
| | for (const key in self.cells) delete self.cells[key]; |
| | self.close(); |
| | break; |
| |
|
| | default: |
| | console.error(`Unknown message type: ${type}`); |
| | } |
| | }; |
| |
|