const PYODIDE_URL = "https://cdn.jsdelivr.net/pyodide/v0.26.1/full/pyodide.mjs"; import managerCode from "../../../backend/src/manager.py?raw"; import logicCode from "../../../backend/src/logic.py?raw"; import type { CsvParseResult, CsvSettings, DataPoints, DecisionBoundaryResult, DecisionBoundarySettings, ModelSettings } from "./types"; export class PyodideBackend { private initialized: boolean = false; private initPromise: Promise | null = null; private chain: Promise = Promise.resolve(); private pyodide: any = null; private manager: any = null; private errorHandler: ((message: string) => void) | null = null; setErrorHandler(handler: (message: string) => void): void { this.errorHandler = handler; } async init(): Promise { if (this.initialized) { return; } if (this.initPromise) { return this.initPromise; } this.initPromise = (async () => { const { loadPyodide } = await import(/* @vite-ignore */ PYODIDE_URL); this.pyodide = await loadPyodide({ indexURL: "https://cdn.jsdelivr.net/pyodide/v0.26.1/full/" }); await this.pyodide.loadPackage(["numpy", "scikit-learn", "pandas"]); this.pyodide.FS.writeFile("logic.py", logicCode); this.pyodide.FS.writeFile("manager.py", managerCode); this.pyodide.runPython(`from manager import Manager; manager = Manager();`); this.manager = this.pyodide.globals.get("manager"); if (!this.manager) { throw new Error("Failed to initialize pyodide manager"); } console.log("Pyodide initialized"); this.initialized = true; })(); return this.initPromise; } async setDataset2d(dataset: DataPoints): Promise { console.log("setting datset 2d"); await this.init(); const inputs = dataset.xPoints.map((x, i) => [x, dataset.yPoints[i]]); const outputs = dataset.labels; await this.handleCall("handle_set_dataset", { inputs, outputs }); } async setModelConfig(settings: ModelSettings): Promise { await this.handleCall( "handle_set_model_config", { type: settings.type, arguments: settings.arguments } ); } async buildModel(): Promise { await this.handleCall("handle_build_model"); } async getDecisionBoundary(settings: DecisionBoundarySettings): Promise { return await this.handleCall("handle_get_decision_boundary", settings); } async setDatasetCsv(buffer: ArrayBuffer, settings: CsvSettings): Promise { return await this.handleCall( "handle_set_dataset_csv", { buffer: buffer, settings } ); } async setCsvSettings(settings: CsvSettings): Promise { return await this.handleCall("handle_set_csv_settings", settings); } async getDatasetCsv(): Promise { return await this.handleCall("handle_get_dataset_csv"); } private enqueue(fn: () => Promise): Promise { const next = this.chain.then(() => fn(), () => fn()); this.chain = next.then(() => undefined, () => undefined); return next; } private async handleCall(methodName: string, args?: unknown): Promise { await this.init(); return this.enqueue(async () => { let pyArgs: any = null; let pyResult: any = null; try { if (args !== undefined) { pyArgs = this.pyodide.toPy(args); } const fn = this.manager[methodName]; if (!fn) { throw new Error(`Manager has no method named ${methodName}`); } pyResult = args === undefined ? fn.call(this.manager) : fn.call(this.manager, pyArgs); const result = pyResult && typeof pyResult.toJs === "function" ? pyResult.toJs({ dict_converter: Object.fromEntries }) : pyResult; if (result?.status === "USER_ERROR") { const message = result.message || "An error occurred in the backend"; throw new Error(message); } return result as T; } catch (error) { const message = error instanceof Error ? error.message : String(error); this.errorHandler?.(message); throw error instanceof Error ? error : new Error(message); } finally { if (pyArgs && typeof pyArgs.destroy === "function") { pyArgs.destroy(); } if (pyResult && typeof pyResult.destroy === "function") { pyResult.destroy(); } } }); } };