diff --git a/package-lock.json b/package-lock.json
index 39390c6..18ee46c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
"ace-builds": "^1.19.0",
"autoprefixer": "^10.4.7",
"clsx": "^1.2.0",
+ "comlink": "^4.4.1",
"fast-glob": "^3.2.11",
"feed": "^4.2.2",
"focus-visible": "^5.2.0",
diff --git a/package.json b/package.json
index 65f0045..267cd3c 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"ace-builds": "^1.19.0",
"autoprefixer": "^10.4.7",
"clsx": "^1.2.0",
+ "comlink": "^4.4.1",
"fast-glob": "^3.2.11",
"feed": "^4.2.2",
"focus-visible": "^5.2.0",
diff --git a/src/components/PythonContext/Go.tsx b/src/components/PythonContext/Go.tsx
new file mode 100644
index 0000000..2da421f
--- /dev/null
+++ b/src/components/PythonContext/Go.tsx
@@ -0,0 +1,36 @@
+import React, { useEffect } from 'react'
+import { useWorker } from './WorkerContext'
+
+export default function Go() {
+ const { runner, output, runCode } = useWorker()
+ const [count, setCount] = React.useState(0)
+ const [stdout, setStdout] = React.useState('')
+ const id = '4022iiuj'
+ useEffect(() => {
+ // check that length is greater than 0 to avoid the initial empty array
+ if (output.length > 0 && output[0] === id) {
+ // join them all with newlines except the first one
+ setStdout(output.slice(1).join('\n'))
+ }
+ }, [output])
+ // useEffect to check the state of stdout and stderr
+ useEffect(() => {
+ if (stdout) {
+ console.log(stdout)
+ console.log(output)
+ }
+ }, [stdout])
+
+ return (
+
+
{
+ setCount(count + 1)
+ await runCode('print(1+2)', '4022iiuj')
+ }}
+ >
+ Go
+
+
+ )
+}
diff --git a/src/components/PythonContext/WorkerContext.tsx b/src/components/PythonContext/WorkerContext.tsx
new file mode 100644
index 0000000..aae67a1
--- /dev/null
+++ b/src/components/PythonContext/WorkerContext.tsx
@@ -0,0 +1,109 @@
+import React, {
+ createContext,
+ useContext,
+ useRef,
+ useEffect,
+ ReactNode,
+ useState,
+ useCallback,
+} from 'react'
+import { proxy, Remote, wrap } from 'comlink'
+import { Runner, PythonRunner } from './types'
+
+interface WorkerContextProps {
+ runner: React.MutableRefObject | undefined>
+ runCode: (code: string, id: string) => Promise
+ isLoading: boolean
+ pyodideVersion: string | undefined
+ output: string[]
+}
+
+// Create the context
+const WorkerContext = createContext(undefined)
+
+interface WorkerProviderProps {
+ children: ReactNode
+}
+
+// Create a provider component
+export const WorkerProvider: React.FC = ({ children }) => {
+ const workerRef = useRef(null)
+ const runnerRef = useRef>()
+ const [isLoading, setIsLoading] = useState(false)
+ const [pyodideVersion, setPyodideVersion] = useState()
+ const [output, setOutput] = useState([])
+ const [stderr, setStderr] = useState('')
+
+ const runCode = useCallback(async (code: string, id: string) => {
+ if (!runnerRef.current) {
+ throw new Error('Pyodide is not loaded')
+ }
+ try {
+ setOutput([id])
+ await runnerRef.current.run(code)
+ } catch (error) {
+ console.error('Error:', error)
+ setStderr('Traceback (most recent call): \n' + error.message)
+ }
+ }, [])
+ // useEffect to check on stdErr
+ useEffect(() => {
+ if (stderr) {
+ console.log(stderr)
+ }
+ }, [stderr])
+
+ useEffect(() => {
+ const worker = new Worker(new URL('./python-worker', import.meta.url))
+ workerRef.current = worker
+ const init = async () => {
+ try {
+ setIsLoading(true)
+ const runner: Remote = wrap(workerRef.current as Worker)
+ runnerRef.current = runner
+
+ await runner.init(
+ proxy((msg: string) => {
+ setOutput((prev) => [...prev, msg])
+ }),
+ proxy(({ version }) => {
+ // The runner is ready once the Pyodide version has been set
+ setPyodideVersion(version)
+ console.debug('Loaded pyodide version:', version)
+ }),
+ [[], []]
+ )
+ } catch (error) {
+ console.error('Error loading Pyodide:', error)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+ init()
+ // Cleanup worker on unmount
+ return () => {
+ if (workerRef.current) {
+ workerRef.current.terminate()
+ workerRef.current = null
+ }
+ }
+ }, [])
+
+ // The value provided by the context includes both the worker and functions to interact with it
+ return (
+
+ {children}
+
+ )
+}
+
+// Create a custom hook to access the worker
+export const useWorker = (): WorkerContextProps => {
+ const context = useContext(WorkerContext)
+ if (context === undefined) {
+ throw new Error('useWorker must be used within a WorkerProvider')
+ }
+ return context
+}
diff --git a/src/components/PythonContext/python-worker.ts b/src/components/PythonContext/python-worker.ts
new file mode 100644
index 0000000..f3d7e73
--- /dev/null
+++ b/src/components/PythonContext/python-worker.ts
@@ -0,0 +1,78 @@
+importScripts('https://cdn.jsdelivr.net/pyodide/v0.22.0/full/pyodide.js')
+
+interface Pyodide {
+ loadPackage: (packages: string[]) => Promise
+ pyimport: (pkg: string) => micropip
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ runPythonAsync: (code: string, namespace?: any) => Promise
+ version: string
+ FS: {
+ readFile: (name: string, options: unknown) => void
+ writeFile: (name: string, data: string, options: unknown) => void
+ mkdir: (name: string) => void
+ rmdir: (name: string) => void
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ globals: any
+ isPyProxy: (value: unknown) => boolean
+}
+
+interface micropip {
+ install: (packages: string[]) => Promise
+}
+
+declare global {
+ interface Window {
+ loadPyodide: ({
+ stdout,
+ }: {
+ stdout?: (msg: string) => void
+ }) => Promise
+ pyodide: Pyodide
+ }
+}
+
+// Monkey patch console.log to prevent the script from outputting logs
+// eslint-disable-next-line @typescript-eslint/no-empty-function
+console.log = () => {}
+
+import { expose } from 'comlink'
+
+const python = {
+ async init(
+ stdout: (msg: string) => void,
+ onLoad: ({ version, banner }: { version: string; banner?: string }) => void,
+ packages: string[][]
+ ) {
+ self.pyodide = await self.loadPyodide({
+ stdout,
+ })
+ if (packages[0].length > 0) {
+ await self.pyodide.loadPackage(packages[0])
+ }
+ if (packages[1].length > 0) {
+ await self.pyodide.loadPackage(['micropip'])
+ const micropip = self.pyodide.pyimport('micropip')
+ await micropip.install(packages[1])
+ }
+ const version = self.pyodide.version
+ onLoad({ version })
+ },
+ async run(code: string) {
+ await self.pyodide.runPythonAsync(code)
+ },
+ readFile(name: string) {
+ return self.pyodide.FS.readFile(name, { encoding: 'utf8' })
+ },
+ writeFile(name: string, data: string) {
+ return self.pyodide.FS.writeFile(name, data, { encoding: 'utf8' })
+ },
+ mkdir(name: string) {
+ self.pyodide.FS.mkdir(name)
+ },
+ rmdir(name: string) {
+ self.pyodide.FS.rmdir(name)
+ },
+}
+
+expose(python)
diff --git a/src/components/PythonContext/types.ts b/src/components/PythonContext/types.ts
new file mode 100644
index 0000000..6c245c5
--- /dev/null
+++ b/src/components/PythonContext/types.ts
@@ -0,0 +1,20 @@
+export interface Runner {
+ init: (
+ stdout: (msg: string) => void,
+ onLoad: ({ version, banner }: { version: string; banner?: string }) => void,
+ packages?: string[][]
+ ) => Promise
+ interruptExecution: () => void
+ readFile: (name: string) => void
+ writeFile: (name: string, data: string) => void
+ mkdir: (name: string) => void
+ rmdir: (name: string) => void
+}
+
+export interface PythonRunner extends Runner {
+ run: (code: string) => Promise
+}
+
+export interface PythonConsoleRunner extends Runner {
+ run: (code: string) => Promise<{ state: string; error?: string }>
+}
diff --git a/src/components/Sidebar/SidebarContext.tsx b/src/components/Sidebar/SidebarContext.tsx
index 6b67090..43876c1 100644
--- a/src/components/Sidebar/SidebarContext.tsx
+++ b/src/components/Sidebar/SidebarContext.tsx
@@ -1,4 +1,4 @@
-import { useReducer, createContext, useContext, Reducer } from 'react'
+import React, { useReducer, createContext, useContext, Reducer } from 'react'
import { produce, Draft } from 'immer'
export type RawStatefulNode = {
diff --git a/src/pages/f.tsx b/src/pages/f.tsx
new file mode 100644
index 0000000..100aae7
--- /dev/null
+++ b/src/pages/f.tsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import { WorkerProvider } from '../components/PythonContext/WorkerContext'
+import Go from '../components/PythonContext/Go'
+
+export default function f() {
+ return (
+
+
+
+
+
+ )
+}