diff --git a/pyproject.toml b/pyproject.toml index 2b2b6b6..4ee5822 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui_fill-nodes" description = "Fill-Nodes is a versatile collection of custom nodes for ComfyUI that extends functionality across multiple domains. Features include advanced image processing (pixelation, slicing, masking), visual effects generation (glitch, halftone, pixel art), comprehensive file handling (PDF creation/extraction, Google Drive integration), AI model interfaces (GPT, DALL-E, Hugging Face), utility nodes for workflow enhancement, and specialized tools for video processing, captioning, and batch operations. The pack provides both practical workflow solutions and creative tools within a unified node collection." -version = "2.3.9" +version = "2.4.0" license = "LICENSE" dependencies = ["librosa", "sounddevice", "glitch_this", "PyOpenGL", "glfw", "scipy>=1.13.1", "requests", "aiohttp", "moviepy", "matplotlib", "reportlab", "openai", "PyPDF2", "pdf2image", "PyMuPDF", "reportlab", "PyPDF2", "ollama", "kornia", "opencv-python", "gdown", "open_clip_torch", "google-genai"] diff --git a/web/nodes/image/FL_ImagePicker.js b/web/nodes/image/FL_ImagePicker.js index 27db7c2..5dc5f34 100644 --- a/web/nodes/image/FL_ImagePicker.js +++ b/web/nodes/image/FL_ImagePicker.js @@ -190,7 +190,11 @@ function showImagePickerModal(sessionId, images, batchSize, timeoutSeconds) { clearInterval(countdownInterval); countdownInterval = null; // Timeout - cancel the job instead of sending images through - await sendSelection(sessionId, [], true); + try { + await sendSelection(sessionId, [], true); + } catch (err) { + console.error('[FL_ImagePicker] Timeout cancel failed:', err); + } closeModal(); } }, 1000); @@ -434,13 +438,23 @@ function showImagePickerModal(sessionId, images, batchSize, timeoutSeconds) { `; const cancelBtn = createButton('Cancel', '#d9534f', async () => { - await sendSelection(sessionId, [], true); + try { + await sendSelection(sessionId, [], true); + } catch (e) { + console.error('[FL_ImagePicker] Cancel submit failed:', e); + } closeModal(); }); const confirmBtn = createButton('Continue with Selected', '#4ECDC4', async () => { const selection = Array.from(selectedIndices).sort((a, b) => a - b); - await sendSelection(sessionId, selection, false); + try { + await sendSelection(sessionId, selection, false); + } catch (e) { + console.error('[FL_ImagePicker] Confirm submit failed:', e); + alert('Failed to submit selection to backend. Check console for details.'); + return; // Don't close — let user retry + } closeModal(); }, true); @@ -470,12 +484,22 @@ function showImagePickerModal(sessionId, images, batchSize, timeoutSeconds) { if (e.key === 'Escape') { e.preventDefault(); - await sendSelection(sessionId, [], true); + try { + await sendSelection(sessionId, [], true); + } catch (err) { + console.error('[FL_ImagePicker] ESC cancel failed:', err); + } closeModal(); } else if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); const selection = Array.from(selectedIndices).sort((a, b) => a - b); - await sendSelection(sessionId, selection, false); + try { + await sendSelection(sessionId, selection, false); + } catch (err) { + console.error('[FL_ImagePicker] Ctrl+Enter confirm failed:', err); + alert('Failed to submit selection to backend. Check console for details.'); + return; + } closeModal(); } }; @@ -532,25 +556,25 @@ function createButton(text, color, onClick, isPrimary = false) { async function sendSelection(sessionId, selection, cancelled) { console.log(`[FL_ImagePicker] Sending selection: session=${sessionId}, selection=${JSON.stringify(selection)}, cancelled=${cancelled}`); - try { - const response = await fetch('/fl_image_picker/select', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - session_id: sessionId, - selection: selection, - cancelled: cancelled - }) - }); - const result = await response.json(); - console.log('[FL_ImagePicker] Selection sent successfully:', result); - return result; - } catch (error) { - console.error('[FL_ImagePicker] Error sending selection:', error); - throw error; + const response = await fetch('/fl_image_picker/select', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + session_id: sessionId, + selection: selection, + cancelled: cancelled + }) + }); + if (!response.ok) { + const msg = `[FL_ImagePicker] Selection POST failed with status ${response.status}`; + console.error(msg); + throw new Error(msg); } + const result = await response.json(); + console.log('[FL_ImagePicker] Selection sent successfully:', result); + return result; } // Full-resolution preview modal diff --git a/web/nodes/wip/FL_PathAnimator.js b/web/nodes/wip/FL_PathAnimator.js index 9e0a3cd..733241a 100644 --- a/web/nodes/wip/FL_PathAnimator.js +++ b/web/nodes/wip/FL_PathAnimator.js @@ -279,20 +279,24 @@ class PathEditorModal { } async submitBlocking(pathsJson, cancelled) { - try { - await fetch('/fl_path_animator/submit', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - session_id: this.sessionId, - paths_data: pathsJson, - cancelled: cancelled, - }), - }); - console.log(`[FL_PathAnimator] Submitted session ${this.sessionId} (cancelled=${cancelled})`); - } catch (e) { - console.error('[FL_PathAnimator] Submit failed:', e); + console.log(`[FL_PathAnimator] Submitting session ${this.sessionId} (cancelled=${cancelled})`); + const response = await fetch('/fl_path_animator/submit', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + session_id: this.sessionId, + paths_data: pathsJson, + cancelled: cancelled, + }), + }); + if (!response.ok) { + const msg = `Submit failed with status ${response.status}`; + console.error(`[FL_PathAnimator] ${msg}`); + throw new Error(msg); } + const result = await response.json(); + console.log(`[FL_PathAnimator] Submit OK:`, result); + return result; } refreshFooterForMode() { @@ -343,11 +347,18 @@ class PathEditorModal { if (e.key === 'Escape') { if (this.mode === 'blocking' && !this.submitted) { this.submitted = true; - this.submitBlocking(null, true); + (async () => { + try { + await this.submitBlocking(null, true); + } catch (err) { + console.error('[FL_PathAnimator] ESC cancel failed:', err); + } + this.close(); + })(); } else { this.savePaths(); + this.close(); } - this.close(); } // Ctrl+V to paste image from clipboard @@ -579,11 +590,15 @@ class PathEditorModal { this.overlay.appendChild(this.container); // Close on overlay click (cancel in blocking mode) - this.overlay.addEventListener('click', (e) => { + this.overlay.addEventListener('click', async (e) => { if (e.target === this.overlay) { if (this.mode === 'blocking' && !this.submitted) { this.submitted = true; - this.submitBlocking(null, true); + try { + await this.submitBlocking(null, true); + } catch (err) { + console.error('[FL_PathAnimator] Overlay click cancel failed:', err); + } } this.close(); } @@ -675,10 +690,14 @@ class PathEditorModal { closeBtn.style.borderColor = 'rgba(255, 255, 255, 0.1)'; closeBtn.style.transform = 'scale(1)'; }; - closeBtn.onclick = () => { + closeBtn.onclick = async () => { if (this.mode === 'blocking' && !this.submitted) { this.submitted = true; - this.submitBlocking(null, true); + try { + await this.submitBlocking(null, true); + } catch (err) { + console.error('[FL_PathAnimator] Header close cancel failed:', err); + } } this.close(); }; @@ -1940,10 +1959,16 @@ class PathEditorModal { cursor: pointer; font-size: 14px; `; - cancelBtn.onclick = () => { + cancelBtn.onclick = async () => { if (this.mode === 'blocking' && !this.submitted) { this.submitted = true; - this.submitBlocking(null, true); + cancelBtn.disabled = true; + saveBtn.disabled = true; + try { + await this.submitBlocking(null, true); + } catch (e) { + console.error('[FL_PathAnimator] Cancel submit failed:', e); + } } this.close(); }; @@ -1960,11 +1985,25 @@ class PathEditorModal { font-size: 14px; font-weight: 500; `; - saveBtn.onclick = () => { + saveBtn.onclick = async () => { this.savePaths(); if (this.mode === 'blocking' && !this.submitted) { this.submitted = true; - this.submitBlocking(this.pathsDataWidget.value, false); + cancelBtn.disabled = true; + saveBtn.disabled = true; + saveBtn.textContent = 'Sending...'; + try { + await this.submitBlocking(this.pathsDataWidget.value, false); + } catch (e) { + console.error('[FL_PathAnimator] Continue submit failed:', e); + // Re-enable so user can retry or cancel + this.submitted = false; + cancelBtn.disabled = false; + saveBtn.disabled = false; + saveBtn.textContent = 'Continue'; + alert('Failed to submit paths to backend. Check console for details.'); + return; + } } this.close(); }; @@ -2018,10 +2057,14 @@ class PathEditorModal { close() { // Safeguard: if blocking mode and we haven't submitted yet, cancel the session - // so the Python thread never permanently blocks + // so the Python thread never permanently blocks. Note: this is fire-and-forget + // because close() is synchronous — all primary exit paths (Continue, Cancel, ESC, + // overlay-click, header-close) already await submitBlocking before calling close(). if (this.mode === 'blocking' && !this.submitted) { this.submitted = true; - this.submitBlocking(null, true); + this.submitBlocking(null, true).catch(err => { + console.error('[FL_PathAnimator] Safeguard cancel failed:', err); + }); } // Clear countdown timer