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