pyodide hook for python execution
This commit is contained in:
123
frontend/src/hooks/use-pyodide.ts
Normal file
123
frontend/src/hooks/use-pyodide.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user