diff --git a/canvas_node.py b/canvas_node.py index f792c65..2b4f031 100644 --- a/canvas_node.py +++ b/canvas_node.py @@ -612,11 +612,11 @@ class BiRefNetMatting: "models") def load_model(self, model_path): + from json.decoder import JSONDecodeError try: if model_path not in self.model_cache: full_model_path = os.path.join(self.base_path, "BiRefNet") log_info(f"Loading BiRefNet model from {full_model_path}...") - try: self.model = AutoModelForImageSegmentation.from_pretrained( "ZhengPeng7/BiRefNet", @@ -628,6 +628,13 @@ class BiRefNetMatting: self.model = self.model.cuda() self.model_cache[model_path] = self.model log_info("Model loaded successfully from Hugging Face") + except JSONDecodeError as e: + log_error(f"JSONDecodeError: Failed to load model from {full_model_path}. The model's config.json may be corrupted.") + raise RuntimeError( + "The matting model's configuration file (config.json) appears to be corrupted. " + f"Please manually delete the directory '{full_model_path}' and try again. " + "This will force a fresh download of the model." + ) from e except Exception as e: log_error(f"Failed to load model from Hugging Face: {str(e)}") # Re-raise with a more informative message @@ -799,6 +806,12 @@ async def matting(request): "error": "Network Connection Error", "details": "Failed to download the matting model from Hugging Face. Please check your internet connection." }, status=400) + except RuntimeError as e: + log_error(f"Runtime error during matting: {e}") + return web.json_response({ + "error": "Matting Model Error", + "details": str(e) + }, status=500) except Exception as e: log_exception(f"Error in matting endpoint: {e}") # Check for offline error message from Hugging Face diff --git a/js/CanvasView.js b/js/CanvasView.js index ad31e06..c7513fb 100644 --- a/js/CanvasView.js +++ b/js/CanvasView.js @@ -339,11 +339,15 @@ async function createCanvasWidget(node, widget, app) { } catch (error) { log.error("Matting error:", error); - alert(`Matting process failed:\n\n${error.message}`); + const errorMessage = error.message || "An unknown error occurred."; + const errorDetails = error.stack || (error.details ? JSON.stringify(error.details, null, 2) : "No details available."); + showErrorDialog(errorMessage, errorDetails); } finally { button.classList.remove('loading'); - button.removeChild(spinner); + if (button.contains(spinner)) { + button.removeChild(spinner); + } } } }), @@ -669,6 +673,55 @@ async function createCanvasWidget(node, widget, app) { panel: controlPanel }; } +function showErrorDialog(message, details) { + const dialog = $el("div.painter-dialog.error-dialog", { + style: { + position: 'fixed', + left: '50%', + top: '50%', + transform: 'translate(-50%, -50%)', + zIndex: '9999', + padding: '20px', + background: '#282828', + border: '1px solid #ff4444', + borderRadius: '8px', + minWidth: '400px', + maxWidth: '80vw', + } + }, [ + $el("h3", { textContent: "Matting Error", style: { color: "#ff4444", marginTop: "0" } }), + $el("p", { textContent: message, style: { color: "white" } }), + $el("pre.error-details", { + textContent: details, + style: { + background: "#1e1e1e", + border: "1px solid #444", + padding: "10px", + maxHeight: "300px", + overflowY: "auto", + whiteSpace: "pre-wrap", + wordBreak: "break-all", + color: "#ccc" + } + }), + $el("div.dialog-buttons", { style: { textAlign: "right", marginTop: "20px" } }, [ + $el("button", { + textContent: "Copy Details", + onclick: () => { + navigator.clipboard.writeText(details) + .then(() => alert("Error details copied to clipboard!")) + .catch(err => alert("Failed to copy details: " + err)); + } + }), + $el("button", { + textContent: "Close", + style: { marginLeft: "10px" }, + onclick: () => document.body.removeChild(dialog) + }) + ]) + ]); + document.body.appendChild(dialog); +} const canvasNodeInstances = new Map(); app.registerExtension({ name: "Comfy.CanvasNode", diff --git a/src/CanvasView.ts b/src/CanvasView.ts index f76e905..595e878 100644 --- a/src/CanvasView.ts +++ b/src/CanvasView.ts @@ -371,10 +371,14 @@ async function createCanvasWidget(node: ComfyNode, widget: any, app: ComfyApp): canvas.saveState(); } catch (error: any) { log.error("Matting error:", error); - alert(`Matting process failed:\n\n${error.message}`); + const errorMessage = error.message || "An unknown error occurred."; + const errorDetails = error.stack || (error.details ? JSON.stringify(error.details, null, 2) : "No details available."); + showErrorDialog(errorMessage, errorDetails); } finally { button.classList.remove('loading'); - button.removeChild(spinner); + if (button.contains(spinner)) { + button.removeChild(spinner); + } } } }), @@ -734,6 +738,57 @@ async function createCanvasWidget(node: ComfyNode, widget: any, app: ComfyApp): }; } +function showErrorDialog(message: string, details: string) { + const dialog = $el("div.painter-dialog.error-dialog", { + style: { + position: 'fixed', + left: '50%', + top: '50%', + transform: 'translate(-50%, -50%)', + zIndex: '9999', + padding: '20px', + background: '#282828', + border: '1px solid #ff4444', + borderRadius: '8px', + minWidth: '400px', + maxWidth: '80vw', + } + }, [ + $el("h3", { textContent: "Matting Error", style: { color: "#ff4444", marginTop: "0" } }), + $el("p", { textContent: message, style: { color: "white" } }), + $el("pre.error-details", { + textContent: details, + style: { + background: "#1e1e1e", + border: "1px solid #444", + padding: "10px", + maxHeight: "300px", + overflowY: "auto", + whiteSpace: "pre-wrap", + wordBreak: "break-all", + color: "#ccc" + } + }), + $el("div.dialog-buttons", { style: { textAlign: "right", marginTop: "20px" } }, [ + $el("button", { + textContent: "Copy Details", + onclick: () => { + navigator.clipboard.writeText(details) + .then(() => alert("Error details copied to clipboard!")) + .catch(err => alert("Failed to copy details: " + err)); + } + }), + $el("button", { + textContent: "Close", + style: { marginLeft: "10px" }, + onclick: () => document.body.removeChild(dialog) + }) + ]) + ]); + + document.body.appendChild(dialog); +} + const canvasNodeInstances = new Map(); app.registerExtension({