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:
NyaFuP
2025-07-15 23:00:48 +09:00
parent 6dbfad1be0
commit c4c607704b
5 changed files with 45 additions and 53 deletions
+8 -14
View File
@@ -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
View File
@@ -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
+8 -10
View File
@@ -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
View File
@@ -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
View File
@@ -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