diff --git a/js/SAMDetectorIntegration.js b/js/SAMDetectorIntegration.js index af183e7..ef10215 100644 --- a/js/SAMDetectorIntegration.js +++ b/js/SAMDetectorIntegration.js @@ -6,6 +6,7 @@ import { uploadCanvasAsImage, uploadImageBlob } from "./utils/ImageUploadUtils.j import { processImageToMask } from "./utils/MaskProcessingUtils.js"; import { convertToImage } from "./utils/ImageUtils.js"; import { updateNodePreview } from "./utils/PreviewUtils.js"; +import { validateAndFixClipspace } from "./utils/ClipspaceUtils.js"; const log = createModuleLogger('SAMDetectorIntegration'); /** * SAM Detector Integration for LayerForge @@ -324,6 +325,8 @@ async function handleSAMDetectorResult(node, resultImage) { node.samOriginalImgSrc = null; } } +// Store original onClipspaceEditorSave function to restore later +let originalOnClipspaceEditorSave = null; // Function to setup SAM Detector hook in menu options export function setupSAMDetectorHook(node, options) { // Hook into "Open in SAM Detector" with delay since Impact Pack adds it asynchronously @@ -347,8 +350,39 @@ export function setupSAMDetectorHook(node, options) { // Set the image to the node for clipspace node.imgs = [uploadResult.imageElement]; node.clipspaceImg = uploadResult.imageElement; + // Ensure proper clipspace structure for updated ComfyUI + if (!ComfyApp.clipspace) { + ComfyApp.clipspace = {}; + } + // Set up clipspace with proper indices + ComfyApp.clipspace.imgs = [uploadResult.imageElement]; + ComfyApp.clipspace.selectedIndex = 0; + ComfyApp.clipspace.combinedIndex = 0; + ComfyApp.clipspace.img_paste_mode = 'selected'; // Copy to ComfyUI clipspace ComfyApp.copyToClipspace(node); + // Override onClipspaceEditorSave to fix clipspace structure before pasteFromClipspace + if (!originalOnClipspaceEditorSave) { + originalOnClipspaceEditorSave = ComfyApp.onClipspaceEditorSave; + ComfyApp.onClipspaceEditorSave = function () { + log.debug("SAM Detector onClipspaceEditorSave called, using unified clipspace validation"); + // Use the unified clipspace validation function + const isValid = validateAndFixClipspace(); + if (!isValid) { + log.error("Clipspace validation failed, cannot proceed with paste"); + return; + } + // Call the original function + if (originalOnClipspaceEditorSave) { + originalOnClipspaceEditorSave.call(ComfyApp); + } + // Restore the original function after use + if (originalOnClipspaceEditorSave) { + ComfyApp.onClipspaceEditorSave = originalOnClipspaceEditorSave; + originalOnClipspaceEditorSave = null; + } + }; + } // Start monitoring for SAM Detector results startSAMDetectorMonitoring(node); log.info("Canvas automatically sent to clipspace and monitoring started"); diff --git a/js/utils/ClipboardManager.js b/js/utils/ClipboardManager.js index 991ead4..e7fb998 100644 --- a/js/utils/ClipboardManager.js +++ b/js/utils/ClipboardManager.js @@ -1,10 +1,9 @@ import { createModuleLogger } from "./LoggerUtils.js"; import { showNotification, showInfoNotification } from "./NotificationUtils.js"; import { withErrorHandling, createValidationError, createNetworkError, createFileError } from "../ErrorHandler.js"; +import { safeClipspacePaste } from "./ClipspaceUtils.js"; // @ts-ignore import { api } from "../../../scripts/api.js"; -// @ts-ignore -import { ComfyApp } from "../../../scripts/app.js"; const log = createModuleLogger('ClipboardManager'); export class ClipboardManager { constructor(canvas) { @@ -39,7 +38,12 @@ export class ClipboardManager { */ this.tryClipspacePaste = withErrorHandling(async (addMode) => { log.info("Attempting to paste from ComfyUI Clipspace"); - ComfyApp.pasteFromClipspace(this.canvas.node); + // Use the unified clipspace validation and paste function + const pasteSuccess = safeClipspacePaste(this.canvas.node); + if (!pasteSuccess) { + log.debug("Safe clipspace paste failed"); + return false; + } if (this.canvas.node.imgs && this.canvas.node.imgs.length > 0) { const clipspaceImage = this.canvas.node.imgs[0]; if (clipspaceImage && clipspaceImage.src) { diff --git a/js/utils/ClipspaceUtils.js b/js/utils/ClipspaceUtils.js new file mode 100644 index 0000000..bf09afb --- /dev/null +++ b/js/utils/ClipspaceUtils.js @@ -0,0 +1,99 @@ +import { createModuleLogger } from "./LoggerUtils.js"; +// @ts-ignore +import { ComfyApp } from "../../../scripts/app.js"; +const log = createModuleLogger('ClipspaceUtils'); +/** + * Validates and fixes ComfyUI clipspace structure to prevent 'Cannot read properties of undefined' errors + * @returns {boolean} - True if clipspace is valid and ready to use, false otherwise + */ +export function validateAndFixClipspace() { + log.debug("Validating and fixing clipspace structure"); + // Check if clipspace exists + if (!ComfyApp.clipspace) { + log.debug("ComfyUI clipspace is not available"); + return false; + } + // Validate clipspace structure + if (!ComfyApp.clipspace.imgs || ComfyApp.clipspace.imgs.length === 0) { + log.debug("ComfyUI clipspace has no images"); + return false; + } + log.debug("Current clipspace state:", { + hasImgs: !!ComfyApp.clipspace.imgs, + imgsLength: ComfyApp.clipspace.imgs?.length, + selectedIndex: ComfyApp.clipspace.selectedIndex, + combinedIndex: ComfyApp.clipspace.combinedIndex, + img_paste_mode: ComfyApp.clipspace.img_paste_mode + }); + // Ensure required indices are set + if (ComfyApp.clipspace.selectedIndex === undefined || ComfyApp.clipspace.selectedIndex === null) { + ComfyApp.clipspace.selectedIndex = 0; + log.debug("Fixed clipspace selectedIndex to 0"); + } + if (ComfyApp.clipspace.combinedIndex === undefined || ComfyApp.clipspace.combinedIndex === null) { + ComfyApp.clipspace.combinedIndex = 0; + log.debug("Fixed clipspace combinedIndex to 0"); + } + if (!ComfyApp.clipspace.img_paste_mode) { + ComfyApp.clipspace.img_paste_mode = 'selected'; + log.debug("Fixed clipspace img_paste_mode to 'selected'"); + } + // Ensure indices are within bounds + const maxIndex = ComfyApp.clipspace.imgs.length - 1; + if (ComfyApp.clipspace.selectedIndex > maxIndex) { + ComfyApp.clipspace.selectedIndex = maxIndex; + log.debug(`Fixed clipspace selectedIndex to ${maxIndex} (max available)`); + } + if (ComfyApp.clipspace.combinedIndex > maxIndex) { + ComfyApp.clipspace.combinedIndex = maxIndex; + log.debug(`Fixed clipspace combinedIndex to ${maxIndex} (max available)`); + } + // Verify the image at combinedIndex exists and has src + const combinedImg = ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]; + if (!combinedImg || !combinedImg.src) { + log.debug("Image at combinedIndex is missing or has no src, trying to find valid image"); + // Try to use the first available image + for (let i = 0; i < ComfyApp.clipspace.imgs.length; i++) { + if (ComfyApp.clipspace.imgs[i] && ComfyApp.clipspace.imgs[i].src) { + ComfyApp.clipspace.combinedIndex = i; + log.debug(`Fixed combinedIndex to ${i} (first valid image)`); + break; + } + } + // Final check - if still no valid image found + const finalImg = ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]; + if (!finalImg || !finalImg.src) { + log.error("No valid images found in clipspace after attempting fixes"); + return false; + } + } + log.debug("Final clipspace structure:", { + selectedIndex: ComfyApp.clipspace.selectedIndex, + combinedIndex: ComfyApp.clipspace.combinedIndex, + img_paste_mode: ComfyApp.clipspace.img_paste_mode, + imgsLength: ComfyApp.clipspace.imgs?.length, + combinedImgSrc: ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]?.src?.substring(0, 50) + '...' + }); + return true; +} +/** + * Safely calls ComfyApp.pasteFromClipspace after validating clipspace structure + * @param {any} node - The ComfyUI node to paste to + * @returns {boolean} - True if paste was successful, false otherwise + */ +export function safeClipspacePaste(node) { + log.debug("Attempting safe clipspace paste"); + if (!validateAndFixClipspace()) { + log.debug("Clipspace validation failed, cannot paste"); + return false; + } + try { + ComfyApp.pasteFromClipspace(node); + log.debug("Successfully called pasteFromClipspace"); + return true; + } + catch (error) { + log.error("Error calling pasteFromClipspace:", error); + return false; + } +} diff --git a/src/SAMDetectorIntegration.ts b/src/SAMDetectorIntegration.ts index 72e1919..c6aa4b0 100644 --- a/src/SAMDetectorIntegration.ts +++ b/src/SAMDetectorIntegration.ts @@ -7,6 +7,7 @@ import { uploadCanvasAsImage, uploadImageBlob } from "./utils/ImageUploadUtils.j import { processImageToMask } from "./utils/MaskProcessingUtils.js"; import { convertToImage } from "./utils/ImageUtils.js"; import { updateNodePreview } from "./utils/PreviewUtils.js"; +import { validateAndFixClipspace } from "./utils/ClipspaceUtils.js"; import type { ComfyNode } from './types'; const log = createModuleLogger('SAMDetectorIntegration'); @@ -376,6 +377,9 @@ async function handleSAMDetectorResult(node: ComfyNode, resultImage: HTMLImageEl } +// Store original onClipspaceEditorSave function to restore later +let originalOnClipspaceEditorSave: (() => void) | null = null; + // Function to setup SAM Detector hook in menu options export function setupSAMDetectorHook(node: ComfyNode, options: any[]) { // Hook into "Open in SAM Detector" with delay since Impact Pack adds it asynchronously @@ -408,9 +412,46 @@ export function setupSAMDetectorHook(node: ComfyNode, options: any[]) { node.imgs = [uploadResult.imageElement]; (node as any).clipspaceImg = uploadResult.imageElement; + // Ensure proper clipspace structure for updated ComfyUI + if (!ComfyApp.clipspace) { + ComfyApp.clipspace = {}; + } + + // Set up clipspace with proper indices + ComfyApp.clipspace.imgs = [uploadResult.imageElement]; + ComfyApp.clipspace.selectedIndex = 0; + ComfyApp.clipspace.combinedIndex = 0; + ComfyApp.clipspace.img_paste_mode = 'selected'; + // Copy to ComfyUI clipspace ComfyApp.copyToClipspace(node); + // Override onClipspaceEditorSave to fix clipspace structure before pasteFromClipspace + if (!originalOnClipspaceEditorSave) { + originalOnClipspaceEditorSave = ComfyApp.onClipspaceEditorSave; + ComfyApp.onClipspaceEditorSave = function() { + log.debug("SAM Detector onClipspaceEditorSave called, using unified clipspace validation"); + + // Use the unified clipspace validation function + const isValid = validateAndFixClipspace(); + if (!isValid) { + log.error("Clipspace validation failed, cannot proceed with paste"); + return; + } + + // Call the original function + if (originalOnClipspaceEditorSave) { + originalOnClipspaceEditorSave.call(ComfyApp); + } + + // Restore the original function after use + if (originalOnClipspaceEditorSave) { + ComfyApp.onClipspaceEditorSave = originalOnClipspaceEditorSave; + originalOnClipspaceEditorSave = null; + } + }; + } + // Start monitoring for SAM Detector results startSAMDetectorMonitoring(node); diff --git a/src/utils/ClipboardManager.ts b/src/utils/ClipboardManager.ts index ecf4ef6..ffc49fb 100644 --- a/src/utils/ClipboardManager.ts +++ b/src/utils/ClipboardManager.ts @@ -1,6 +1,7 @@ import {createModuleLogger} from "./LoggerUtils.js"; import { showNotification, showInfoNotification } from "./NotificationUtils.js"; import { withErrorHandling, createValidationError, createNetworkError, createFileError } from "../ErrorHandler.js"; +import { safeClipspacePaste } from "./ClipspaceUtils.js"; // @ts-ignore import {api} from "../../../scripts/api.js"; @@ -56,7 +57,13 @@ export class ClipboardManager { */ tryClipspacePaste = withErrorHandling(async (addMode: AddMode): Promise => { log.info("Attempting to paste from ComfyUI Clipspace"); - ComfyApp.pasteFromClipspace(this.canvas.node); + + // Use the unified clipspace validation and paste function + const pasteSuccess = safeClipspacePaste(this.canvas.node); + if (!pasteSuccess) { + log.debug("Safe clipspace paste failed"); + return false; + } if (this.canvas.node.imgs && this.canvas.node.imgs.length > 0) { const clipspaceImage = this.canvas.node.imgs[0]; diff --git a/src/utils/ClipspaceUtils.ts b/src/utils/ClipspaceUtils.ts new file mode 100644 index 0000000..7402da4 --- /dev/null +++ b/src/utils/ClipspaceUtils.ts @@ -0,0 +1,114 @@ +import { createModuleLogger } from "./LoggerUtils.js"; +// @ts-ignore +import { ComfyApp } from "../../../scripts/app.js"; + +const log = createModuleLogger('ClipspaceUtils'); + +/** + * Validates and fixes ComfyUI clipspace structure to prevent 'Cannot read properties of undefined' errors + * @returns {boolean} - True if clipspace is valid and ready to use, false otherwise + */ +export function validateAndFixClipspace(): boolean { + log.debug("Validating and fixing clipspace structure"); + + // Check if clipspace exists + if (!ComfyApp.clipspace) { + log.debug("ComfyUI clipspace is not available"); + return false; + } + + // Validate clipspace structure + if (!ComfyApp.clipspace.imgs || ComfyApp.clipspace.imgs.length === 0) { + log.debug("ComfyUI clipspace has no images"); + return false; + } + + log.debug("Current clipspace state:", { + hasImgs: !!ComfyApp.clipspace.imgs, + imgsLength: ComfyApp.clipspace.imgs?.length, + selectedIndex: ComfyApp.clipspace.selectedIndex, + combinedIndex: ComfyApp.clipspace.combinedIndex, + img_paste_mode: ComfyApp.clipspace.img_paste_mode + }); + + // Ensure required indices are set + if (ComfyApp.clipspace.selectedIndex === undefined || ComfyApp.clipspace.selectedIndex === null) { + ComfyApp.clipspace.selectedIndex = 0; + log.debug("Fixed clipspace selectedIndex to 0"); + } + + if (ComfyApp.clipspace.combinedIndex === undefined || ComfyApp.clipspace.combinedIndex === null) { + ComfyApp.clipspace.combinedIndex = 0; + log.debug("Fixed clipspace combinedIndex to 0"); + } + + if (!ComfyApp.clipspace.img_paste_mode) { + ComfyApp.clipspace.img_paste_mode = 'selected'; + log.debug("Fixed clipspace img_paste_mode to 'selected'"); + } + + // Ensure indices are within bounds + const maxIndex = ComfyApp.clipspace.imgs.length - 1; + if (ComfyApp.clipspace.selectedIndex > maxIndex) { + ComfyApp.clipspace.selectedIndex = maxIndex; + log.debug(`Fixed clipspace selectedIndex to ${maxIndex} (max available)`); + } + if (ComfyApp.clipspace.combinedIndex > maxIndex) { + ComfyApp.clipspace.combinedIndex = maxIndex; + log.debug(`Fixed clipspace combinedIndex to ${maxIndex} (max available)`); + } + + // Verify the image at combinedIndex exists and has src + const combinedImg = ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]; + if (!combinedImg || !combinedImg.src) { + log.debug("Image at combinedIndex is missing or has no src, trying to find valid image"); + // Try to use the first available image + for (let i = 0; i < ComfyApp.clipspace.imgs.length; i++) { + if (ComfyApp.clipspace.imgs[i] && ComfyApp.clipspace.imgs[i].src) { + ComfyApp.clipspace.combinedIndex = i; + log.debug(`Fixed combinedIndex to ${i} (first valid image)`); + break; + } + } + + // Final check - if still no valid image found + const finalImg = ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]; + if (!finalImg || !finalImg.src) { + log.error("No valid images found in clipspace after attempting fixes"); + return false; + } + } + + log.debug("Final clipspace structure:", { + selectedIndex: ComfyApp.clipspace.selectedIndex, + combinedIndex: ComfyApp.clipspace.combinedIndex, + img_paste_mode: ComfyApp.clipspace.img_paste_mode, + imgsLength: ComfyApp.clipspace.imgs?.length, + combinedImgSrc: ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]?.src?.substring(0, 50) + '...' + }); + + return true; +} + +/** + * Safely calls ComfyApp.pasteFromClipspace after validating clipspace structure + * @param {any} node - The ComfyUI node to paste to + * @returns {boolean} - True if paste was successful, false otherwise + */ +export function safeClipspacePaste(node: any): boolean { + log.debug("Attempting safe clipspace paste"); + + if (!validateAndFixClipspace()) { + log.debug("Clipspace validation failed, cannot paste"); + return false; + } + + try { + ComfyApp.pasteFromClipspace(node); + log.debug("Successfully called pasteFromClipspace"); + return true; + } catch (error) { + log.error("Error calling pasteFromClipspace:", error); + return false; + } +}