"use client"; import { useState, useEffect, useCallback, useRef } from "react"; declare global { interface Window { loadPyodide: (config: { indexURL: string }) => Promise; } } interface PyodideInterface { runPythonAsync: (code: string) => Promise; 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({ 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 => { 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, }; }