This commit is contained in:
Ole
2026-05-31 20:25:41 +00:00
commit 0a07ab8593
275 changed files with 52660 additions and 0 deletions
@@ -0,0 +1,146 @@
/**
* usePyodideRuntime — React hook for managing PyodideRuntime lifecycle.
*
* Separate entry point so the core atlas-agent bundle stays Pyodide-free.
*/
import { useState, useRef, useEffect } from 'react';
import type { CogniteClient } from '@cognite/sdk';
import { getGlobalPyodideRuntime } from './pyodide-runtime';
import type { PyodideRuntimeConfig } from './pyodide-runtime';
import type { PythonRuntime } from './types';
// ============================================================================
// Types
// ============================================================================
export interface PyodideProgress {
stage: string;
percent: number;
}
export interface UsePyodideRuntimeOptions {
/** The `loadPyodide` function from the `pyodide` package. */
loadPyodide: PyodideRuntimeConfig['loadPyodide'];
/** CogniteClient for SDK credential injection. `null` disables initialization. */
client: CogniteClient | null;
/** Additional Python packages to install via micropip. */
requirements?: string[];
/** CDN URL for Pyodide files. */
cdnUrl?: string;
}
export interface UsePyodideRuntimeReturn {
/** The initialized runtime, or undefined if not yet ready. */
runtime: PythonRuntime | undefined;
/** True while Pyodide is loading / initializing. */
loading: boolean;
/** Error message if initialization failed. */
error: string | null;
/** Current initialization progress. */
progress: PyodideProgress;
/** Convenience: true when runtime is ready to use. */
isReady: boolean;
}
// ============================================================================
// Hook
// ============================================================================
const DEFAULT_BASE_URL = 'https://api.cognitedata.com';
/**
* Manages PyodideRuntime initialization lifecycle.
*
* Loads Pyodide, installs packages, injects Cognite SDK credentials,
* and returns a ready-to-use `PythonRuntime` with loading/error state.
*
* ```tsx
* import { loadPyodide } from 'pyodide';
*
* const { runtime, loading, progress, isReady } = usePyodideRuntime({
* loadPyodide,
* client: sdk,
* requirements: ['pandas', 'numpy'],
* });
* ```
*/
export function usePyodideRuntime(
options: UsePyodideRuntimeOptions,
): UsePyodideRuntimeReturn {
const { client } = options;
const [runtime, setRuntime] = useState<PythonRuntime>();
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [progress, setProgress] = useState<PyodideProgress>({ stage: '', percent: 0 });
// Refs for values that shouldn't trigger re-initialization
const loadPyodideRef = useRef(options.loadPyodide);
const requirementsRef = useRef(options.requirements);
const cdnUrlRef = useRef(options.cdnUrl);
loadPyodideRef.current = options.loadPyodide;
requirementsRef.current = options.requirements;
cdnUrlRef.current = options.cdnUrl;
useEffect(() => {
if (!client) {
setLoading(false);
return;
}
let mounted = true;
(async () => {
try {
setLoading(true);
setError(null);
const instance = getGlobalPyodideRuntime({
loadPyodide: loadPyodideRef.current,
requirements: requirementsRef.current,
cdnUrl: cdnUrlRef.current,
onProgress: (stage, percent) => {
if (mounted) setProgress({ stage, percent });
},
});
if (!instance.isInitialized) {
if (mounted) setProgress({ stage: 'Initializing...', percent: 0 });
const headers = client.getDefaultRequestHeaders();
const token = headers.Authorization?.split(' ')[1] ?? '';
await instance.initialize({
project: client.project,
baseUrl: client.getBaseUrl?.() ?? DEFAULT_BASE_URL,
token,
});
}
if (mounted) {
setRuntime(instance);
setProgress({ stage: 'Ready', percent: 100 });
}
} catch (err) {
if (mounted) {
setError(err instanceof Error ? err.message : String(err));
}
} finally {
if (mounted) setLoading(false);
}
})();
return () => {
mounted = false;
};
}, [client]);
return {
runtime,
loading,
error,
progress,
isReady: !loading && !error && runtime !== undefined,
};
}