Spaces:
Running
Running
| 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<void> | null = null; | |
| private chain: Promise<void> = 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<void> { | |
| 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<void> { | |
| 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<void>("handle_set_dataset", { inputs, outputs }); | |
| } | |
| async setModelConfig(settings: ModelSettings): Promise<void> { | |
| await this.handleCall<void>( | |
| "handle_set_model_config", { type: settings.type, arguments: settings.arguments } | |
| ); | |
| } | |
| async buildModel(): Promise<void> { | |
| await this.handleCall<void>("handle_build_model"); | |
| } | |
| async getDecisionBoundary(settings: DecisionBoundarySettings): Promise<DecisionBoundaryResult> { | |
| return await this.handleCall<DecisionBoundaryResult>("handle_get_decision_boundary", settings); | |
| } | |
| async setDatasetCsv(buffer: ArrayBuffer, settings: CsvSettings): Promise<CsvParseResult> { | |
| return await this.handleCall<CsvParseResult>( | |
| "handle_set_dataset_csv", { buffer: buffer, settings } | |
| ); | |
| } | |
| async setCsvSettings(settings: CsvSettings): Promise<CsvParseResult> { | |
| return await this.handleCall<CsvParseResult>("handle_set_csv_settings", settings); | |
| } | |
| async getDatasetCsv(): Promise<string> { | |
| return await this.handleCall<string>("handle_get_dataset_csv"); | |
| } | |
| private enqueue<T>(fn: () => Promise<T>): Promise<T> { | |
| const next = this.chain.then(() => fn(), () => fn()); | |
| this.chain = next.then(() => undefined, () => undefined); | |
| return next; | |
| } | |
| private async handleCall<T>(methodName: string, args?: unknown): Promise<T> { | |
| 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(); | |
| } | |
| } | |
| }); | |
| } | |
| }; | |