Refactor: load panel detection libs from vendor dir

Update PanelDetector to load OpenCV, TensorFlow.js, and COCO-SSD
from local vendor directory instead of importing npm packages.
This ensures the libraries are available after build and avoids
runtime dependency resolution issues.

- Change imports to use ../vendor paths instead of npm packages
- Add caching to prevent reloading libraries
- Add error handling for failed library loads with console warnings
- Update OpenCV loading to handle both default and named exports
This commit is contained in:
2026-04-13 18:50:19 -04:00
parent fc29b463f4
commit 1223876b9d
2 changed files with 45 additions and 23 deletions
+32 -15
View File
@@ -97,23 +97,40 @@ export class PanelDetector {
} }
async #loadOpenCV() { async #loadOpenCV() {
const { default: cv } = await import("@techstark/opencv-js"); if (this.#opencv) return this.#opencv;
await new Promise((resolve, reject) => {
const check = () => {
if (cv && cv.Mat) resolve();
else if (!cv || cv.readyState === "complete")
reject(new Error("OpenCV failed to load"));
else setTimeout(check, 50);
};
check();
});
return cv;
}
try {
const cv = await import("../vendor/opencv/opencv.js");
await new Promise((resolve, reject) => {
const check = () => {
if (cv && cv.Mat) resolve();
else if (cv.readyState === "complete")
reject(new Error("OpenCV failed to load"));
else setTimeout(check, 50);
};
check();
});
this.#opencv = cv.default || cv;
return this.#opencv;
} catch (e) {
console.warn("Failed to load OpenCV:", e);
return null;
}
}
async #loadModel() { async #loadModel() {
const tf = await import("@tensorflow/tfjs"); if (this.#model) return this.#model;
const cocoSsd = await import("@tensorflow-models/coco-ssd");
return await cocoSsd.load({ base: "lite_mobilenet_v2" }); try {
await import("../vendor/tfjs/tf.min.js");
const cocoSsd = await import("../vendor/coco-ssd/coco-ssd.min.js");
this.#model = await cocoSsd.load({ base: "lite_mobilenet_v2" });
return this.#model;
} catch (e) {
console.warn("Failed to load ML model:", e);
return null;
}
} }
clear() { clear() {
+13 -8
View File
@@ -104,18 +104,23 @@ class Reader {
{ {
name: "panel-mode", name: "panel-mode",
label: "Panel Mode", label: "Panel Mode",
type: "checkbox", type: "radio",
checked: false, items: [
onclick: () => { ["Off", "off"],
// ← No 'value' parameter for checkbox ["On", "on"],
],
onclick: (value) => {
const renderer = this.view?.renderer; const renderer = this.view?.renderer;
if (!renderer?.togglePanelMode) return; if (!renderer?.togglePanelMode) return;
const isActive = renderer.hasAttribute("panel-mode"); if (value === "on") {
if (isActive) { if (!renderer.hasAttribute("panel-mode")) {
renderer.removeAttribute("panel-mode"); renderer.togglePanelMode();
}
} else { } else {
renderer.togglePanelMode(); if (renderer.hasAttribute("panel-mode")) {
renderer.togglePanelMode();
}
} }
}, },
}, },