|
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}`); |
|
} |
|
}; |
|
|