pyodide hook for python execution

This commit is contained in:
2025-05-21 21:03:28 +01:00
parent 82f80b929f
commit f74f1d89b6

View File

@@ -0,0 +1,123 @@
"use client";
import { useState, useEffect, useCallback, useRef } from "react";
declare global {
interface Window {
loadPyodide: (config: { indexURL: string }) => Promise<PyodideInterface>;
}
}
interface PyodideInterface {
runPythonAsync: (code: string) => Promise<unknown>;
globals: {
get: (name: string) => unknown;
};
}
interface PyodideState {
pyodide: PyodideInterface | null;
loading: boolean;
error: string | null;
}
interface RunResult {
output: unknown;
error: string | null;
}
const PYODIDE_CDN = "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/";
export function usePyodide() {
const [state, setState] = useState<PyodideState>({
pyodide: null,
loading: true,
error: null,
});
const loadingRef = useRef(false);
useEffect(() => {
if (loadingRef.current || state.pyodide) return;
loadingRef.current = true;
const loadPyodideScript = async () => {
// Check if script is already loaded
if (window.loadPyodide) {
try {
const pyodide = await window.loadPyodide({ indexURL: PYODIDE_CDN });
setState({ pyodide, loading: false, error: null });
} catch (err) {
setState({
pyodide: null,
loading: false,
error: err instanceof Error ? err.message : "Failed to load Pyodide",
});
}
return;
}
// Load the script
const script = document.createElement("script");
script.src = `${PYODIDE_CDN}pyodide.js`;
script.async = true;
script.onload = async () => {
try {
const pyodide = await window.loadPyodide({ indexURL: PYODIDE_CDN });
setState({ pyodide, loading: false, error: null });
} catch (err) {
setState({
pyodide: null,
loading: false,
error: err instanceof Error ? err.message : "Failed to load Pyodide",
});
}
};
script.onerror = () => {
setState({
pyodide: null,
loading: false,
error: "Failed to load Pyodide script",
});
};
document.head.appendChild(script);
};
loadPyodideScript();
}, [state.pyodide]);
const runPython = useCallback(
async (code: string, timeout = 5000): Promise<RunResult> => {
if (!state.pyodide) {
return { output: null, error: "Pyodide not loaded" };
}
return new Promise((resolve) => {
const timeoutId = setTimeout(() => {
resolve({ output: null, error: "Execution timed out" });
}, timeout);
state.pyodide!
.runPythonAsync(code)
.then((result) => {
clearTimeout(timeoutId);
resolve({ output: result, error: null });
})
.catch((err: Error) => {
clearTimeout(timeoutId);
resolve({ output: null, error: err.message });
});
});
},
[state.pyodide]
);
return {
pyodide: state.pyodide,
loading: state.loading,
error: state.error,
runPython,
};
}