decision_boundary / frontends /react /src /PyodideBackend.ts
joel-woodfield's picture
Add user error handling
17411d4
Raw
History Blame Contribute Delete
4.57 kB
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();
}
}
});
}
};