modified: README.md
modified: __init__.py modified: js/nf_preview_selector.js modified: js/preview_dialog.js modified: nf_preview_selector.py
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# NF Review Selector
|
||||
# NF Preview Selector
|
||||
|
||||
A floating dialog-based image review and selection system for ComfyUI. Inspired by cg-image-filter but designed as a simpler, more focused solution for image review workflows.
|
||||
A floating dialog-based image review and selection system for ComfyUI. Inspired by cg-image-filter but designed as a simpler, more focused solution for image preview workflows.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -14,7 +14,7 @@ A floating dialog-based image review and selection system for ComfyUI. Inspired
|
||||
|
||||
## Usage
|
||||
|
||||
1. Add the "NF Review Selector" node to your workflow
|
||||
1. Add the "NF Preview Selector" node to your workflow
|
||||
2. Connect image input (and optionally latent input)
|
||||
3. Choose a mode:
|
||||
- **review_and_select**: Opens floating dialog for manual selection
|
||||
@@ -35,9 +35,9 @@ When in review mode, a floating dialog will appear showing:
|
||||
## Settings
|
||||
|
||||
Access via ComfyUI Settings menu:
|
||||
- **Dialog maximum width**: Determines the maximum width of the dialog in pixels. (Max 1800px)
|
||||
- **Remember window position**: Saves dialog position between uses
|
||||
- **Use compact mode**: Smaller layout for limited screen space
|
||||
- **Auto-select first image**: Automatically selects first image when dialog opens
|
||||
|
||||
|
||||
## Outputs
|
||||
|
||||
@@ -47,9 +47,10 @@ Access via ComfyUI Settings menu:
|
||||
|
||||
## Installation
|
||||
|
||||
1. Place this folder in `ComfyUI/custom_nodes/`
|
||||
1. Run git pull in the directory "ComfyUI/custom_nodes/".
|
||||
```git pull https://github.com/NyaFuP/ComfyUI_Preview_Selector.git```
|
||||
2. Restart ComfyUI
|
||||
3. The "NF Review Selector" node will appear in the "image/review" category
|
||||
3. The "NF Preview Selector" node will appear in the "image/review" category
|
||||
|
||||
## Technical Details
|
||||
|
||||
@@ -59,13 +60,6 @@ Access via ComfyUI Settings menu:
|
||||
- Supports both image and latent workflows
|
||||
- Minimal dependencies (uses ComfyUI's built-in APIs)
|
||||
|
||||
## Comparison to Alternatives
|
||||
|
||||
Unlike full-screen solutions, NF Review Selector:
|
||||
- Doesn't take over the entire interface
|
||||
- Allows interaction with other parts of ComfyUI while reviewing
|
||||
- Provides a cleaner, more focused review experience
|
||||
- Maintains workflow visibility
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+1
-1
@@ -9,4 +9,4 @@ WEB_DIRECTORY = "./js"
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||||
|
||||
print("NF Preview Selector: Loaded with floating dialog implementation")
|
||||
pass # NF Preview Selector loaded
|
||||
@@ -7,25 +7,23 @@ import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { nfPreviewDialog } from './preview_dialog.js';
|
||||
|
||||
console.log("NF Preview Selector: Loading extension...");
|
||||
// NF Preview Selector loading
|
||||
|
||||
app.registerExtension({
|
||||
name: "nf.preview_selector",
|
||||
|
||||
async setup() {
|
||||
console.log("NF Preview Selector: Setting up extension");
|
||||
console.log("NF Preview Selector: API object:", api);
|
||||
console.log("NF Preview Selector: API addEventListener available:", typeof api.addEventListener);
|
||||
// Setting up extension
|
||||
|
||||
// Register message handler using ComfyUI's system
|
||||
if (api.addEventListener) {
|
||||
// Register for our custom event type
|
||||
api.addEventListener("nf_preview_request", (event) => {
|
||||
console.log("NF Preview Selector: Received review request via event", event.detail);
|
||||
// Received review request
|
||||
nfPreviewDialog.show(event.detail);
|
||||
});
|
||||
|
||||
console.log("NF Preview Selector: Event listener registered for nf_preview_request");
|
||||
// Event listener registered
|
||||
}
|
||||
|
||||
// Event system is working correctly
|
||||
@@ -49,12 +47,12 @@ app.registerExtension({
|
||||
|
||||
// Auto-select first image setting removed - this is available in node UI
|
||||
|
||||
console.log("NF Preview Selector: Setup complete");
|
||||
// Setup complete
|
||||
},
|
||||
|
||||
async nodeCreated(node) {
|
||||
if (node.comfyClass === "NFPreviewSelector") {
|
||||
console.log("NF Preview Selector: Node created", node.id);
|
||||
// Node created
|
||||
|
||||
// Set node title
|
||||
node.title = "NF Preview Selector";
|
||||
@@ -68,7 +66,7 @@ app.registerExtension({
|
||||
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "NFPreviewSelector") {
|
||||
console.log("NF Preview Selector: Registering node definition");
|
||||
// Registering node definition
|
||||
|
||||
// Add any node definition modifications here
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
@@ -84,4 +82,4 @@ app.registerExtension({
|
||||
}
|
||||
});
|
||||
|
||||
console.log("NF Preview Selector: Extension loaded successfully");
|
||||
// Extension loaded successfully
|
||||
+14
-14
@@ -67,7 +67,7 @@ export class NFPreviewDialog {
|
||||
if (event.ctrlKey && event.key === 'Enter') {
|
||||
// Only handle if preview dialog is visible
|
||||
if (this.isVisible()) {
|
||||
console.log('NF Preview Dialog: Queue Prompt keybinding detected - stopping dialog and allowing queue to proceed');
|
||||
// Queue Prompt keybinding detected
|
||||
|
||||
// Stop the dialog processing immediately
|
||||
this.stopDialogForQueue();
|
||||
@@ -85,7 +85,7 @@ export class NFPreviewDialog {
|
||||
// If queue is starting and dialog is visible, stop the dialog
|
||||
if (status && status.exec_info && status.exec_info.queue_remaining > 0) {
|
||||
if (this.isVisible()) {
|
||||
console.log('NF Preview Dialog: Queue started programmatically - stopping dialog');
|
||||
// Queue started programmatically
|
||||
this.stopDialogForQueue();
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export class NFPreviewDialog {
|
||||
// Stop countdown immediately
|
||||
this.stopCountdown();
|
||||
|
||||
console.log('NF Preview Dialog: Stopping dialog processing for queue');
|
||||
// Stopping dialog processing for queue
|
||||
|
||||
// Send cancellation response to backend (empty selection, cancelled=true)
|
||||
this.sendSelection([], true);
|
||||
@@ -110,14 +110,14 @@ export class NFPreviewDialog {
|
||||
}
|
||||
|
||||
show(data) {
|
||||
console.log("NF Preview Dialog: Showing review dialog", data);
|
||||
// Showing review dialog
|
||||
|
||||
// Apply latest max width setting
|
||||
this.applyMaxWidthSetting();
|
||||
|
||||
// If dialog is already open for this review, don't create another
|
||||
if (this.reviewId === data.review_id && this.window) {
|
||||
console.log("NF Preview Dialog: Dialog already open for this review");
|
||||
// Dialog already open
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -284,12 +284,12 @@ export class NFPreviewDialog {
|
||||
// For landscape images, use slightly wider columns but still allow multiple columns
|
||||
const minColumnWidth = Math.max(200, Math.min(300, dialogWidth / 2)); // 200-300px, max 2 columns typically
|
||||
this.imageGrid.style.setProperty('--min-column-width', `${minColumnWidth}px`);
|
||||
console.log('NF Preview Dialog: Using smart grid layout optimized for landscape images, min width:', minColumnWidth);
|
||||
// Using landscape image layout
|
||||
} else {
|
||||
// For portrait/square images, use standard grid sizing
|
||||
const minColumnWidth = Math.max(120, Math.min(200, dialogWidth / 3)); // 120-200px, allow up to 3-4 columns
|
||||
this.imageGrid.style.setProperty('--min-column-width', `${minColumnWidth}px`);
|
||||
console.log('NF Preview Dialog: Using smart grid layout optimized for portrait/square images, min width:', minColumnWidth);
|
||||
// Using portrait/square image layout
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ export class NFPreviewDialog {
|
||||
this.updateSelectionInfo();
|
||||
this.updateConfirmButton();
|
||||
|
||||
console.log("NF Preview Dialog: Selected indices:", Array.from(this.selectedIndices));
|
||||
// Selected indices updated
|
||||
}
|
||||
|
||||
updateSelectionInfo() {
|
||||
@@ -335,7 +335,7 @@ export class NFPreviewDialog {
|
||||
this.updateCountdown();
|
||||
|
||||
if (this.remainingTime <= 0) {
|
||||
console.log("NF Preview Dialog: Timeout reached");
|
||||
// Timeout reached
|
||||
this.timeoutSelection();
|
||||
}
|
||||
}, 1000);
|
||||
@@ -358,7 +358,7 @@ export class NFPreviewDialog {
|
||||
this.stopCountdown();
|
||||
|
||||
const selection = Array.from(this.selectedIndices).sort((a, b) => a - b);
|
||||
console.log("NF Preview Dialog: Confirming selection:", selection);
|
||||
// Confirming selection
|
||||
|
||||
this.sendSelection(selection, false);
|
||||
this.hide();
|
||||
@@ -367,7 +367,7 @@ export class NFPreviewDialog {
|
||||
cancelSelection() {
|
||||
this.stopCountdown();
|
||||
|
||||
console.log("NF Preview Dialog: Cancelling selection");
|
||||
// Cancelling selection
|
||||
this.sendSelection([], true);
|
||||
this.hide();
|
||||
}
|
||||
@@ -377,7 +377,7 @@ export class NFPreviewDialog {
|
||||
|
||||
// On timeout, select all images or none based on setting
|
||||
const selection = []; // Default to empty selection on timeout
|
||||
console.log("NF Preview Dialog: Timeout - returning empty selection");
|
||||
// Timeout - returning empty selection
|
||||
|
||||
this.sendSelection(selection, false);
|
||||
this.hide();
|
||||
@@ -483,7 +483,7 @@ export class NFPreviewDialog {
|
||||
if (rememberPosition) {
|
||||
this.savePosition(x, y);
|
||||
}
|
||||
console.log("NF Preview Dialog: Window moved to", x, y);
|
||||
// Window moved
|
||||
}
|
||||
|
||||
onWindowResize(width, height) {
|
||||
@@ -514,7 +514,7 @@ export class NFPreviewDialog {
|
||||
this.applyImageLayout(landscapeCount, portraitCount);
|
||||
}
|
||||
|
||||
console.log("NF Preview Dialog: Window resized to", width, height);
|
||||
// Window resized
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -24,7 +24,7 @@ async def handle_review_response(request):
|
||||
selection = data.get('selection', [])
|
||||
cancelled = data.get('cancelled', False)
|
||||
|
||||
print(f"NF Preview Selector: Received response for review {review_id}: {selection}")
|
||||
pass # Response received
|
||||
|
||||
# Store response
|
||||
review_responses[review_id] = {
|
||||
@@ -34,7 +34,7 @@ async def handle_review_response(request):
|
||||
|
||||
return web.json_response({'status': 'success'})
|
||||
except Exception as e:
|
||||
print(f"NF Preview Selector: Error handling response: {e}")
|
||||
pass # Error handled
|
||||
return web.json_response({'status': 'error', 'message': str(e)}, status=500)
|
||||
|
||||
# Register API endpoint
|
||||
@@ -76,7 +76,7 @@ class NFPreviewSelector(PreviewImage):
|
||||
def preview_images(self, images, mode, timeout, unique_id=None, latents=None, selection_indices="", prompt=None, extra_pnginfo=None):
|
||||
"""Main function for image preview and selection"""
|
||||
|
||||
print(f"NF Preview Selector: Processing {len(images)} images with mode '{mode}'")
|
||||
pass # Processing images
|
||||
|
||||
# No flags needed anymore
|
||||
|
||||
@@ -113,9 +113,9 @@ class NFPreviewSelector(PreviewImage):
|
||||
# Use ComfyUI's message dispatch system
|
||||
try:
|
||||
PromptServer.instance.send_sync("nf_preview_request", message_data)
|
||||
print(f"NF Preview Selector: Sent review request via send_sync")
|
||||
pass # Request sent
|
||||
except Exception as e:
|
||||
print(f"NF Preview Selector: send_sync failed, trying direct WebSocket: {e}")
|
||||
pass # Fallback to direct WebSocket
|
||||
# Fallback to direct WebSocket
|
||||
message = {
|
||||
"type": "nf_preview_request",
|
||||
@@ -127,20 +127,20 @@ class NFPreviewSelector(PreviewImage):
|
||||
try:
|
||||
await ws.send_str(json.dumps(message))
|
||||
except Exception as e:
|
||||
print(f"Failed to send message to client: {e}")
|
||||
pass # Send failed
|
||||
|
||||
if PromptServer.instance.loop:
|
||||
asyncio.run_coroutine_threadsafe(send_message(), PromptServer.instance.loop)
|
||||
|
||||
print(f"NF Preview Selector: Sent review request for {len(images)} images")
|
||||
pass # Request sent
|
||||
|
||||
# Wait for response from frontend
|
||||
result = self.wait_for_response(review_id, timeout)
|
||||
if result == "CANCELLED":
|
||||
print(f"NF Preview Selector: Workflow cancelled by user")
|
||||
pass # Cancelled
|
||||
raise InterruptProcessingException()
|
||||
elif result == "TIMEOUT":
|
||||
print(f"NF Preview Selector: Workflow timed out")
|
||||
pass # Timed out
|
||||
raise InterruptProcessingException()
|
||||
else:
|
||||
selected_indices = result
|
||||
@@ -152,7 +152,7 @@ class NFPreviewSelector(PreviewImage):
|
||||
# Process selection
|
||||
if not selected_indices:
|
||||
# For timeout or other cases, return empty tensors
|
||||
print(f"NF Preview Selector: No selection made, returning empty tensors")
|
||||
pass # No selection
|
||||
empty_image = torch.zeros((1, images.shape[1], images.shape[2], images.shape[3]))
|
||||
empty_latent = {"samples": torch.zeros((1, 4, 64, 64))} if latents is None else {"samples": torch.zeros((1, latents["samples"].shape[1], latents["samples"].shape[2], latents["samples"].shape[3]))}
|
||||
return (empty_image, empty_latent, "")
|
||||
@@ -170,7 +170,7 @@ class NFPreviewSelector(PreviewImage):
|
||||
|
||||
indices_str = ",".join(str(i) for i in selected_indices)
|
||||
|
||||
print(f"NF Preview Selector: Selected {len(selected_indices)} images: [{indices_str}]")
|
||||
pass # Selection made
|
||||
|
||||
return {
|
||||
"result": (selected_images, selected_latents, indices_str),
|
||||
@@ -188,16 +188,16 @@ class NFPreviewSelector(PreviewImage):
|
||||
response = review_responses.pop(review_id)
|
||||
|
||||
if response['cancelled']:
|
||||
print(f"NF Preview Selector: Review {review_id} was cancelled")
|
||||
pass # Review cancelled
|
||||
return "CANCELLED"
|
||||
else:
|
||||
selection = response['selection']
|
||||
print(f"NF Preview Selector: Review {review_id} completed with selection: {selection}")
|
||||
pass # Review completed
|
||||
return selection
|
||||
|
||||
time.sleep(0.1) # Poll every 100ms
|
||||
|
||||
print(f"NF Preview Selector: Review {review_id} timed out")
|
||||
pass # Review timed out
|
||||
return "TIMEOUT" # Indicate timeout occurred
|
||||
|
||||
# Node registration
|
||||
|
||||
Reference in New Issue
Block a user