Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64e844ec42 | ||
|
|
3ed188d63f | ||
|
|
b9cc9f295d | ||
|
|
271cd020c1 | ||
|
|
e34807855a | ||
|
|
d32e18f844 |
@@ -29,6 +29,8 @@ Calculate upscaled dimensions from image or latent inputs with precision.
|
||||
- Ensure ComfyUI tensor compatibility
|
||||
- Optimize batch processing workflows
|
||||
|
||||

|
||||
|
||||
#### 📏 Width Height Selector
|
||||
Advanced preset-based dimension selection with visual swap button.
|
||||
|
||||
@@ -60,6 +62,8 @@ Advanced seed tracking with interactive history management and UI.
|
||||
- Maintain reproducibility across sessions
|
||||
- Compare results from different seeds efficiently
|
||||
|
||||
![Seed History functionality is shown in various workflow examples]
|
||||
|
||||
#### ⚙️ Sampler Combo
|
||||
Unified sampling configuration interface combining sampler, scheduler, steps, and CFG.
|
||||
|
||||
@@ -92,6 +96,8 @@ Advanced empty latent creation with preset support and batch processing capabili
|
||||
- Optimize memory usage with batch size planning
|
||||
- Quick preset-based latent generation for different aspect ratios
|
||||
|
||||

|
||||
|
||||
#### 💾 Kiko Save Image
|
||||
Enhanced image saving with format selection, quality control, and floating popup viewer.
|
||||
|
||||
@@ -104,6 +110,28 @@ Enhanced image saving with format selection, quality control, and floating popup
|
||||
- **Smart UI**: Auto-hide/show, minimize/maximize, roll-up functionality
|
||||
- **Popup Toggle**: Enable/disable popup viewer per save operation
|
||||
|
||||

|
||||
|
||||
#### 📋 Display Text
|
||||
Advanced text display node with intelligent formatting and enhanced user interaction.
|
||||
|
||||
- **Smart Prompt Detection**: Automatically detects positive/negative prompt pairs and displays in split view
|
||||
- **Text Wrapping**: Proper word wrapping that reflows when node is resized
|
||||
- **Scrollable Content**: Mouse wheel scrolling for long texts with visual scroll indicators
|
||||
- **Copy Functionality**: Always-visible copy button with visual feedback
|
||||
- **Split View Mode**: Automatic detection and formatting of SDXL-style prompts
|
||||
- **Responsive Design**: Content adapts to node resizing with proper text reflow
|
||||
- **Clean Formatting**: Strips prompt labels when copying for direct use
|
||||
|
||||
**Use Cases:**
|
||||
- Display generated prompts with proper formatting
|
||||
- Compare positive and negative prompts side-by-side
|
||||
- Copy prompts without manual label removal
|
||||
- View long text content with proper wrapping
|
||||
- Debug prompt generation workflows
|
||||
|
||||

|
||||
|
||||
#### 🤖 Gemini Prompt Engineer
|
||||
AI-powered image analysis using Google's Gemini to generate optimized prompts for various models.
|
||||
|
||||
@@ -114,6 +142,9 @@ AI-powered image analysis using Google's Gemini to generate optimized prompts fo
|
||||
- **Flexible API Key Management**: Environment variable, config file, or direct input
|
||||
- **Visual Status Feedback**: Real-time processing indicators and error states
|
||||
- **Help Integration**: Built-in setup guide and documentation
|
||||
- **Dynamic Model Refresh**: Fetch latest Gemini models with refresh button
|
||||
- **Model Caching**: Persistent model list storage for offline access
|
||||
- **Enhanced SDXL Prompts**: Improved formatting with layered structure and quality boosters
|
||||
|
||||
**Use Cases:**
|
||||
- Reverse-engineer prompts from reference images
|
||||
@@ -121,6 +152,45 @@ AI-powered image analysis using Google's Gemini to generate optimized prompts fo
|
||||
- Generate consistent style descriptions across workflows
|
||||
- Create detailed scene breakdowns for complex compositions
|
||||
- Analyze and replicate lighting/mood from existing artwork
|
||||
- Access latest Gemini models including 2.0 and 2.5 versions
|
||||
|
||||

|
||||
|
||||
#### 🔍 Display Any
|
||||
Universal debugging node that displays any type of input value or tensor information.
|
||||
|
||||
- **Universal Input Acceptance**: Works with any data type (tensors, strings, numbers, lists, dicts)
|
||||
- **Two Display Modes**: Raw value showing string representation, or tensor shape extraction
|
||||
- **Nested Structure Support**: Finds tensors within complex nested data structures
|
||||
- **Debugging Focus**: Essential tool for understanding data flow and tensor dimensions
|
||||
- **Clean Output**: Formatted display directly in ComfyUI interface
|
||||
|
||||
**Use Cases:**
|
||||
- Debug tensor dimensions at any point in workflow
|
||||
- Inspect latent space data structures
|
||||
- View metadata and configuration objects
|
||||
- Track shape changes through processing nodes
|
||||
- Understand complex data types in ComfyUI
|
||||
|
||||

|
||||
|
||||
#### 🖼️ Image to Multiple Of
|
||||
Adjusts image dimensions to be multiples of a specified value for model compatibility.
|
||||
|
||||
- **Dimension Adjustment**: Ensures image dimensions are multiples of specified value (e.g., 64, 128)
|
||||
- **Two Processing Methods**: Center crop for minimal loss, or rescale to fit
|
||||
- **Model Compatibility**: Essential for models requiring specific dimension constraints
|
||||
- **Flexible Multiple Values**: Support from 1 to 256 with 16-step increments
|
||||
- **Preserves Quality**: Smart processing maintains image quality
|
||||
|
||||
**Use Cases:**
|
||||
- Prepare images for VAE encoding (multiple of 8 requirement)
|
||||
- Ensure compatibility with specific model architectures
|
||||
- Standardize dimensions across image batches
|
||||
- Fix dimension errors in complex workflows
|
||||
- Optimize for tiled processing requirements
|
||||
|
||||

|
||||
|
||||
### 💾 Kiko Save Image Features
|
||||
|
||||
@@ -245,18 +315,55 @@ Generate Image → Kiko Save Image → Floating Popup Viewer
|
||||
**Features:** Click images to open in new tabs, download individual files, batch selection
|
||||
**Advantages:** Immediate preview without file explorer, multi-format comparison, advanced quality controls
|
||||
|
||||
### Display Text Example
|
||||
|
||||
```
|
||||
Gemini Prompt → Display Text → Copy to Clipboard
|
||||
📋 SDXL prompt ↘ auto-split ↘ [📋 Positive] [📋 Negative]
|
||||
view → formatted display
|
||||
```
|
||||
|
||||
**Input:** Text with "Positive prompt:" and "Negative prompt:" sections
|
||||
**Output:** Split view with individual copy buttons
|
||||
**Features:** Text wrapping, scrolling, responsive resizing
|
||||
**Smart Detection:** Automatically formats SDXL-style prompts
|
||||
|
||||
### Gemini Prompt Engineer Example
|
||||
```
|
||||
Load Image → Gemini Prompt → Text Generation Model
|
||||
🖼️ reference ↘ type: FLUX ↘ "majestic landscape..."
|
||||
[API key] → FLUX model
|
||||
Load Image → Gemini Prompt → Display Text → Text Generation Model
|
||||
🖼️ reference ↘ type: SDXL ↘ split view ↘ "detailed portrait..."
|
||||
[Refresh Models] → SDXL model
|
||||
```
|
||||
|
||||
**Input:** Reference image for style analysis
|
||||
**Prompt Type:** FLUX (detailed artistic prompts)
|
||||
**Output:** Optimized prompt with style, lighting, composition details
|
||||
**Prompt Type:** SDXL (positive/negative pairs with layered structure)
|
||||
**Model Selection:** Dynamic list with latest Gemini models (2.0, 2.5)
|
||||
**Output:** Optimized prompts following community best practices
|
||||
**API:** Requires Gemini API key (free tier available)
|
||||
**Use Case:** Recreate similar style/mood from reference images
|
||||
**Refresh:** Click button to fetch latest available models
|
||||
|
||||
### Display Any Example
|
||||
```
|
||||
Any Node → Display Any → Debug Output
|
||||
🔍 tensor ↘ mode: shape ↘ "[[1, 3, 512, 512]]"
|
||||
```
|
||||
|
||||
**Input:** Any data type (image, latent, config, etc.)
|
||||
**Mode:** "raw value" or "tensor shape"
|
||||
**Output:** Formatted display of value or tensor dimensions
|
||||
**Use Case:** Debug workflows, inspect data structures
|
||||
|
||||
### Image to Multiple Of Example
|
||||
```
|
||||
Load Image → Image to Multiple Of → VAE Encode → KSampler
|
||||
🖼️ 513×769 ↘ multiple: 64 ↘ 512×768 → latent
|
||||
method: crop
|
||||
```
|
||||
|
||||
**Input:** Image with arbitrary dimensions
|
||||
**Multiple Of:** 64 (common for VAE compatibility)
|
||||
**Method:** "center crop" or "rescale"
|
||||
**Output:** Adjusted image with compatible dimensions
|
||||
|
||||
### Common Workflows
|
||||
|
||||
@@ -300,6 +407,10 @@ Load Image → Gemini Prompt → Text Generation Model
|
||||
| **Sampler Combo** | Unified sampling configuration with smart recommendations | ✅ Complete | [Docs](examples/documentation/sampler_combo.md) |
|
||||
| **Empty Latent Batch** | Create empty latent batches with preset support | ✅ Complete | [Docs](examples/documentation/empty_latent_batch.md) |
|
||||
| **Kiko Save Image** | Enhanced image saving with popup viewer and multi-format support | ✅ Complete | [Docs](examples/documentation/kiko_save_image.md) |
|
||||
| **Display Text** | Advanced text display with smart prompt detection and split view | ✅ Complete | [Docs](examples/documentation/display_text.md) |
|
||||
| **Gemini Prompt Engineer** | AI-powered image analysis with dynamic model refresh | ✅ Complete | [Docs](examples/documentation/gemini_prompt.md) |
|
||||
| **Display Any** | Universal debugging tool for any data type or tensor shapes | ✅ Complete | [Docs](examples/documentation/display_any.md) |
|
||||
| **Image to Multiple Of** | Adjust image dimensions to multiples for model compatibility | ✅ Complete | [Docs](examples/documentation/image_to_multiple_of.md) |
|
||||
| **Batch Image Processor** | Process multiple images with consistent settings | 🚧 Planned | Coming Soon |
|
||||
| **Advanced Prompt Utilities** | Enhanced prompt manipulation and generation | 🚧 Planned | Coming Soon |
|
||||
|
||||
@@ -593,14 +704,15 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
||||
|
||||
## 📈 Stats
|
||||
|
||||
- **Nodes**: 6 (Resolution Calculator, Width Height Selector, Seed History, Sampler Combo, Empty Latent Batch, Kiko Save Image)
|
||||
- **Nodes**: 10 (Resolution Calculator, Width Height Selector, Seed History, Sampler Combo, Empty Latent Batch, Kiko Save Image, Display Text, Gemini Prompt Engineer, Display Any, Image to Multiple Of)
|
||||
- **Format Support**: 3 (PNG, JPEG, WebP with advanced controls)
|
||||
- **Presets**: 26 curated resolution presets
|
||||
- **Interactive Features**: 4 (Width/Height Swap Button, Seed History UI, Empty Latent Batch Swap Button, Kiko Save Image Popup Viewer)
|
||||
- **Interactive Features**: 6 (Width/Height Swap Button, Seed History UI, Empty Latent Batch Swap Button, Kiko Save Image Popup Viewer, Display Text Split View, Gemini Model Refresh)
|
||||
- **AI Integration**: Gemini API with 40+ model support
|
||||
- **Test Coverage**: 100% (200+ comprehensive tests)
|
||||
- **Python Version**: 3.8+
|
||||
- **ComfyUI Compatibility**: Latest
|
||||
- **Dependencies**: Minimal (PyTorch, NumPy, Pillow)
|
||||
- **Dependencies**: Minimal (PyTorch, NumPy, Pillow, google-generativeai for Gemini)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# Display Text
|
||||
|
||||
The Display Text node provides advanced text display capabilities with smart formatting, interactive features, and responsive design for ComfyUI workflows.
|
||||
|
||||
## Features
|
||||
|
||||
- **Smart Prompt Detection**: Automatically detects and formats SDXL-style positive/negative prompt pairs
|
||||
- **Text Wrapping**: Proper word wrapping that reflows when node is resized
|
||||
- **Scrollable Content**: Mouse wheel scrolling for long texts with visual indicators
|
||||
- **Copy Functionality**: Always-visible copy button with visual feedback
|
||||
- **Split View Mode**: Side-by-side display for prompt pairs
|
||||
- **Responsive Design**: Content adapts to node resizing
|
||||
|
||||
## Inputs
|
||||
|
||||
- **text** (STRING): The text to display
|
||||
- Can be a single text block
|
||||
- Can contain "Positive prompt:" and "Negative prompt:" sections for automatic split view
|
||||
|
||||
## Outputs
|
||||
|
||||
- **text** (STRING): Pass-through of the input text
|
||||
|
||||
## Display Modes
|
||||
|
||||
### Single Text Mode
|
||||
|
||||
When the input is regular text without prompt markers, it displays as a single scrollable text area with:
|
||||
- Word wrapping at word boundaries
|
||||
- Vertical scrolling for long content
|
||||
- Single copy button for the entire text
|
||||
|
||||
### Split View Mode
|
||||
|
||||
Automatically activated when text contains both "Positive prompt:" and "Negative prompt:" sections:
|
||||
- Side-by-side display with 50/50 split
|
||||
- Independent scrolling for each section
|
||||
- Separate copy buttons for each prompt
|
||||
- Labels are stripped when copying (clean prompts)
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### 1. Display Generated Prompts
|
||||
|
||||
```
|
||||
Gemini Prompt → Display Text → Copy to workflow
|
||||
```
|
||||
The node automatically detects SDXL format and shows positive/negative prompts side-by-side.
|
||||
|
||||
### 2. Debug Text Processing
|
||||
|
||||
```
|
||||
Text Processing → Display Text → Further Processing
|
||||
```
|
||||
View intermediate text processing results with proper formatting.
|
||||
|
||||
### 3. Show Long Descriptions
|
||||
|
||||
```
|
||||
Load Text → Display Text → Review
|
||||
```
|
||||
Display long text content with scrolling and word wrapping.
|
||||
|
||||
## Interactive Features
|
||||
|
||||
### Copy Button
|
||||
- Always visible in the top-right corner
|
||||
- Shows "✓ Copied!" feedback on click
|
||||
- In split view: separate buttons for each section
|
||||
- Strips prompt labels for clean copying
|
||||
|
||||
### Scrolling
|
||||
- Mouse wheel scrolling when hovering over text
|
||||
- Visual indicators appear when content is scrollable
|
||||
- Smooth scrolling with proper boundaries
|
||||
- Independent scrolling in split view mode
|
||||
|
||||
### Resizing
|
||||
- Text reflows when node width changes
|
||||
- Maintains readability at different sizes
|
||||
- Split view maintains 50/50 proportions
|
||||
- Minimum height ensures usability
|
||||
|
||||
## Smart Prompt Detection
|
||||
|
||||
The node intelligently detects prompt formats:
|
||||
|
||||
1. **SDXL Format**:
|
||||
- Looks for "Positive prompt:" and "Negative prompt:" markers
|
||||
- Case-insensitive detection
|
||||
- Handles various formatting styles
|
||||
|
||||
2. **Label Stripping**:
|
||||
- When copying from split view, labels are removed
|
||||
- "Positive prompt: beautiful sunset" → "beautiful sunset"
|
||||
- Clean prompts ready for direct use
|
||||
|
||||
## Styling
|
||||
|
||||
- **Font**: Monospace for consistent alignment
|
||||
- **Colors**:
|
||||
- Text: Light gray (#ddd) on dark background
|
||||
- Background: Semi-transparent dark (#1a1a1a)
|
||||
- Borders: Subtle gray (#333)
|
||||
- **Spacing**: Comfortable padding and line height
|
||||
- **Visual Feedback**: Hover effects on interactive elements
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Prompt Engineering Workflows
|
||||
- Display AI-generated prompts with proper formatting
|
||||
- Compare positive and negative prompts side-by-side
|
||||
- Copy refined prompts without manual cleanup
|
||||
|
||||
### Text Processing Pipelines
|
||||
- Debug text transformations at each step
|
||||
- View formatted outputs from text nodes
|
||||
- Monitor prompt construction workflows
|
||||
|
||||
### Documentation and Notes
|
||||
- Display workflow instructions
|
||||
- Show generation parameters
|
||||
- Present formatted metadata
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Text Processing**: Preserves original text while adding display formatting
|
||||
- **Responsive Design**: CSS-based layout adapts to node dimensions
|
||||
- **Event Handling**: Proper event propagation for ComfyUI compatibility
|
||||
- **Memory Efficient**: Only renders visible text portions
|
||||
|
||||
## Tips
|
||||
|
||||
1. **For Long Prompts**: The scrolling feature handles texts of any length efficiently
|
||||
2. **Quick Copy**: Use the copy buttons to quickly grab prompts for other nodes
|
||||
3. **Resizing**: Drag node edges to find optimal display width for your content
|
||||
4. **Split View**: Works best with SDXL-format prompts but handles any dual-section text
|
||||
|
||||
## Integration Example
|
||||
|
||||
```
|
||||
[Gemini Prompt Engineer] → [Display Text] → [Copy Button Click]
|
||||
↓ ↓ ↓
|
||||
SDXL Format Split View Display Clean Prompts
|
||||
```
|
||||
|
||||
This creates a seamless workflow from prompt generation to usage, with the Display Text node providing the visual interface for review and interaction.
|
||||
@@ -8,6 +8,9 @@ The Gemini Prompt Engineer node uses Google's Gemini AI to analyze images and ge
|
||||
- **Custom Prompts**: Override templates with your own system prompts
|
||||
- **Visual Feedback**: UI shows processing status and error states
|
||||
- **Flexible API Key Management**: Multiple ways to provide API credentials
|
||||
- **Dynamic Model Selection**: Fetch and use latest Gemini models with refresh button
|
||||
- **Model Caching**: Persistent storage of available models for offline access
|
||||
- **Help Integration**: Built-in setup guide accessible via help button
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -49,6 +52,10 @@ Choose one of these methods:
|
||||
- `sdxl`: Positive/negative prompt pairs with weight emphasis
|
||||
- `danbooru`: Anime-style booru tags with underscores
|
||||
- `video`: Motion and temporal descriptions for video generation
|
||||
- **model** (DROPDOWN): Gemini model selection
|
||||
- Dynamically populated list of available models
|
||||
- Includes latest models like gemini-2.0-flash-exp
|
||||
- Click refresh button to update model list
|
||||
- **api_key** (STRING, optional): Gemini API key if not set elsewhere
|
||||
- **custom_prompt** (STRING, optional): Override template with custom system prompt
|
||||
|
||||
@@ -72,15 +79,27 @@ majestic mountain landscape at golden hour, oil painting style, dramatic lightin
|
||||
```
|
||||
|
||||
### SDXL Format
|
||||
Generates positive and negative prompt pairs:
|
||||
- Detailed positive prompts with weight emphasis
|
||||
Generates positive and negative prompt pairs with enhanced structure:
|
||||
- Layered positive prompts: main subject → style → composition → technical
|
||||
- Comprehensive negative prompts to avoid common issues
|
||||
- Uses parentheses for emphasis: `(detailed eyes:1.2)`
|
||||
- Includes quality boosters and technical specifications
|
||||
|
||||
Example output:
|
||||
```
|
||||
Positive: beautiful woman, (detailed eyes:1.2), flowing red dress, golden hour lighting, professional photography, 85mm lens, shallow depth of field, bokeh, high resolution, masterpiece
|
||||
Negative: low quality, blurry, distorted features, bad anatomy, poorly drawn, amateur, oversaturated, jpeg artifacts
|
||||
Positive prompt:
|
||||
beautiful woman with flowing red hair, elegant pose, (detailed eyes:1.2), serene expression
|
||||
oil painting style, renaissance art influence, classical portraiture
|
||||
golden hour lighting, warm color palette, soft shadows, dramatic chiaroscuro
|
||||
centered composition, rule of thirds, shallow depth of field, bokeh background
|
||||
masterpiece, best quality, highly detailed, 8k uhd, professional artwork
|
||||
|
||||
Negative prompt:
|
||||
low quality, worst quality, blurry, out of focus, pixelated, low resolution
|
||||
bad anatomy, deformed features, extra limbs, missing limbs, disconnected limbs
|
||||
poorly drawn face, poorly drawn hands, amateur drawing, bad proportions
|
||||
oversaturated, overexposed, underexposed, bad lighting, harsh shadows
|
||||
jpeg artifacts, watermark, signature, text, cropped, duplicate
|
||||
```
|
||||
|
||||
### Danbooru Format
|
||||
@@ -130,12 +149,40 @@ The node provides clear error messages for common issues:
|
||||
|
||||
Errors are displayed in the prompt output for easy debugging.
|
||||
|
||||
## Model Selection
|
||||
|
||||
### Dynamic Model List
|
||||
- Click the refresh button (🔄) next to the model dropdown to fetch latest models
|
||||
- Models are fetched from Google's API and include all available versions
|
||||
- Common models include:
|
||||
- `gemini-2.0-flash-exp`: Latest experimental flash model
|
||||
- `gemini-1.5-pro`: Advanced model with larger context
|
||||
- `gemini-1.5-flash`: Fast and efficient for most tasks
|
||||
|
||||
### Model Caching
|
||||
- Available models are cached locally for offline access
|
||||
- Cache persists across ComfyUI sessions
|
||||
- Refresh button updates the cache with latest models
|
||||
|
||||
## UI Features
|
||||
|
||||
### Help Button
|
||||
- Click the help button (?) for quick setup instructions
|
||||
- Shows API key setup methods
|
||||
- Links to Google AI Studio for key generation
|
||||
|
||||
### Status Indicators
|
||||
- Processing spinner during API calls
|
||||
- Error messages displayed in red
|
||||
- Success feedback when prompt is generated
|
||||
|
||||
## Tips
|
||||
|
||||
1. **API Usage**: Gemini has generous free tier limits, but be mindful of rate limits
|
||||
2. **Image Quality**: Higher resolution images provide better analysis results
|
||||
3. **Prompt Refinement**: You can chain multiple Gemini nodes with different custom prompts
|
||||
4. **Caching**: Results are not cached, so identical images will make new API calls
|
||||
5. **Model Selection**: Use flash models for faster responses, pro models for complex analysis
|
||||
|
||||
## Example Workflow
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
# Kiko Save Image
|
||||
|
||||
Enhanced image saving node with multiple format support, quality controls, and an interactive floating popup viewer for ComfyUI.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Format Support**: Save as PNG, JPEG, or WebP with format-specific optimizations
|
||||
- **Advanced Quality Controls**: Fine-tune compression settings per format
|
||||
- **Floating Popup Viewer**: Interactive window showing saved images immediately
|
||||
- **Batch Operations**: Multi-select images for bulk actions
|
||||
- **File Size Display**: Real-time feedback on compression effectiveness
|
||||
- **Smart UI**: Auto-hide, draggable, resizable popup window
|
||||
|
||||
## Inputs
|
||||
|
||||
- **images** (IMAGE): Batch of images to save
|
||||
- **filename_prefix** (STRING): Prefix for saved filenames
|
||||
- Default: "KikoSave"
|
||||
- Supports subfolder paths (e.g., "outputs/renders/final")
|
||||
- **format** (DROPDOWN): Output format selection
|
||||
- `PNG`: Lossless compression, best quality
|
||||
- `JPEG`: Lossy compression, smaller files
|
||||
- `WEBP`: Modern format, best compression ratio
|
||||
- **quality** (INT): JPEG/WebP quality level
|
||||
- Range: 1-100 (default: 90)
|
||||
- Higher values = better quality, larger files
|
||||
- **png_compress_level** (INT): PNG compression level
|
||||
- Range: 0-9 (default: 4)
|
||||
- Higher values = smaller files, slower saving
|
||||
- **webp_lossless** (BOOLEAN): Use lossless WebP compression
|
||||
- Default: False (lossy)
|
||||
- True: Lossless compression like PNG
|
||||
- **popup** (BOOLEAN): Enable popup viewer window
|
||||
- Default: True
|
||||
- Toggle per save operation
|
||||
|
||||
## Outputs
|
||||
|
||||
- **UI**: Enhanced preview data with interactive popup viewer
|
||||
|
||||
## Popup Viewer Features
|
||||
|
||||
### Window Controls
|
||||
- **Drag Handle**: Click and drag the header to move window
|
||||
- **Minimize Button**: Collapse to title bar only
|
||||
- **Maximize Button**: Expand to larger viewing size
|
||||
- **Roll-up Button**: Show/hide content area
|
||||
- **Close Button**: Hide the popup (can reopen with toggle)
|
||||
|
||||
### Image Grid
|
||||
- **Thumbnails**: Click any image to open full-size in new tab
|
||||
- **File Info**: Shows filename and size for each image
|
||||
- **Quality Indicators**:
|
||||
- PNG: Compression level (0-9)
|
||||
- JPEG/WebP: Quality percentage
|
||||
- **Batch Selection**: Checkboxes for multi-select operations
|
||||
|
||||
### Bulk Actions
|
||||
- **Open All Selected**: Opens selected images in new tabs
|
||||
- **Download All Selected**: Downloads selected images as a batch
|
||||
- **Individual Downloads**: Download button per image
|
||||
|
||||
### Smart Behavior
|
||||
- **Auto-positioning**: Appears in convenient screen location
|
||||
- **Persistence**: Stays open across multiple saves
|
||||
- **Auto-hide**: Can be minimized when not needed
|
||||
- **Responsive**: Adapts to different image counts
|
||||
|
||||
## Format Details
|
||||
|
||||
### PNG Format
|
||||
- **Pros**: Lossless quality, transparency support, wide compatibility
|
||||
- **Cons**: Larger file sizes
|
||||
- **Best for**: Final outputs, images with transparency, archival
|
||||
- **Compression**: 0 (none) to 9 (maximum)
|
||||
- Level 4 (default) balances size and speed
|
||||
- Level 9 for maximum compression (slow)
|
||||
|
||||
### JPEG Format
|
||||
- **Pros**: Smaller files, fast loading, universal support
|
||||
- **Cons**: Lossy compression, no transparency
|
||||
- **Best for**: Web images, previews, photos
|
||||
- **Quality**: 1-100%
|
||||
- 90% (default) excellent quality with good compression
|
||||
- 95%+ for near-lossless quality
|
||||
- 70-85% for web optimization
|
||||
|
||||
### WebP Format
|
||||
- **Pros**: Best compression ratios, supports transparency, modern
|
||||
- **Cons**: Limited software support
|
||||
- **Best for**: Web deployment, storage optimization
|
||||
- **Modes**:
|
||||
- Lossy (default): Excellent compression with quality control
|
||||
- Lossless: PNG-like quality with better compression
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### High-Quality Archive
|
||||
```
|
||||
Format: PNG
|
||||
Compression: 0-2
|
||||
Use Case: Final renders for portfolio or client delivery
|
||||
```
|
||||
|
||||
### Web Optimization
|
||||
```
|
||||
Format: JPEG or WebP
|
||||
Quality: 80-85
|
||||
Use Case: Website images, social media posts
|
||||
```
|
||||
|
||||
### Balanced Storage
|
||||
```
|
||||
Format: WebP
|
||||
Quality: 90
|
||||
Lossless: False
|
||||
Use Case: Large batches with storage constraints
|
||||
```
|
||||
|
||||
### Transparency Preservation
|
||||
```
|
||||
Format: PNG or WebP (lossless)
|
||||
Use Case: Logos, UI elements, cutout images
|
||||
```
|
||||
|
||||
## Workflow Integration
|
||||
|
||||
### Basic Save
|
||||
```
|
||||
Generate → Kiko Save Image
|
||||
format: PNG
|
||||
popup: enabled
|
||||
```
|
||||
|
||||
### Format Comparison
|
||||
```
|
||||
Generate → Kiko Save Image (PNG) → Compare file sizes
|
||||
↘ Kiko Save Image (JPEG) ↗
|
||||
↘ Kiko Save Image (WebP) ↗
|
||||
```
|
||||
|
||||
### Batch Processing
|
||||
```
|
||||
Batch Generate → Kiko Save Image → Popup Viewer
|
||||
↓ ↓
|
||||
4 images Select best results
|
||||
```
|
||||
|
||||
## Tips and Best Practices
|
||||
|
||||
1. **Format Selection**:
|
||||
- Use PNG for maximum quality and transparency
|
||||
- Use JPEG for photographs without transparency
|
||||
- Use WebP for modern web deployment
|
||||
|
||||
2. **Quality Settings**:
|
||||
- Start with defaults (90 for JPEG/WebP, 4 for PNG)
|
||||
- Adjust based on file size requirements
|
||||
- Preview results in popup before finalizing
|
||||
|
||||
3. **Popup Management**:
|
||||
- Drag to second monitor for larger workspace
|
||||
- Use roll-up to save screen space
|
||||
- Disable popup for automated workflows
|
||||
|
||||
4. **Batch Operations**:
|
||||
- Use checkboxes to select multiple images
|
||||
- Open all in tabs for side-by-side comparison
|
||||
- Download all for quick collection
|
||||
|
||||
5. **File Organization**:
|
||||
- Use subfolders in filename_prefix
|
||||
- Include descriptive prefixes
|
||||
- Let ComfyUI handle timestamp suffixes
|
||||
|
||||
## Advantages Over Standard Save Image
|
||||
|
||||
- **Immediate Preview**: No need to navigate file system
|
||||
- **Format Flexibility**: Choose optimal format per use case
|
||||
- **Quality Control**: Fine-tune compression settings
|
||||
- **Batch Management**: Handle multiple images efficiently
|
||||
- **Modern UI**: Floating interface doesn't interrupt workflow
|
||||
- **File Size Awareness**: See compression effectiveness immediately
|
||||
- **Quick Access**: One-click opening and downloading
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Image Processing**: Uses Pillow for format conversion
|
||||
- **Metadata**: Preserves ComfyUI metadata in saved files
|
||||
- **File Naming**: Automatic timestamp and counter suffixes
|
||||
- **Memory Efficiency**: Processes images individually
|
||||
- **Thread Safety**: Proper handling of concurrent saves
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Popup not appearing**:
|
||||
- Check that popup input is enabled
|
||||
- Look for minimized window
|
||||
- Try toggling the popup button in node
|
||||
|
||||
**WebP not working**:
|
||||
- Ensure Pillow has WebP support
|
||||
- Update Pillow: `pip install --upgrade pillow`
|
||||
|
||||
**Large file sizes**:
|
||||
- Increase compression (PNG) or reduce quality (JPEG/WebP)
|
||||
- Consider switching formats
|
||||
- Check image dimensions
|
||||
|
||||
**Can't see all images**:
|
||||
- Scroll within the popup grid
|
||||
- Maximize the popup window
|
||||
- Images are shown newest first
|
||||
@@ -0,0 +1,379 @@
|
||||
{
|
||||
"id": "display-any-example",
|
||||
"revision": 0,
|
||||
"last_node_id": 11,
|
||||
"last_link_id": 9,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
400,
|
||||
270
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
8
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"tensor shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
50,
|
||||
270
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1,
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"example.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
400,
|
||||
520
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
70
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
9
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"raw value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "VAELoader",
|
||||
"pos": [
|
||||
-250,
|
||||
410
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
6
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "VAELoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ae.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "VAEEncode",
|
||||
"pos": [
|
||||
430,
|
||||
390
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pixels",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
7
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "VAEEncode"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
640,
|
||||
270
|
||||
],
|
||||
"size": [
|
||||
450,
|
||||
278
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
640,
|
||||
610
|
||||
],
|
||||
"size": [
|
||||
590,
|
||||
278
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 9
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
-320,
|
||||
530
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
270
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Display Any Example\n\nUniversal debugging tool:\n- Accepts ANY input type\n- Two modes: raw value or tensor shape\n- Finds tensors in nested structures\n\nUse cases:\n- Debug tensor dimensions\n- Inspect latent data\n- View config objects\n- Track data flow\n\nConnect anything to see its contents!"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
5,
|
||||
2,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
6,
|
||||
7,
|
||||
0,
|
||||
8,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
7,
|
||||
8,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
8,
|
||||
1,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
9,
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Display Any",
|
||||
"bounding": [
|
||||
-400,
|
||||
130,
|
||||
1740,
|
||||
850
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.6115909044841477,
|
||||
"offset": [
|
||||
689.1392030323894,
|
||||
-27.435530779258137
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 466 KiB |
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"id": "display-text-example",
|
||||
"revision": 0,
|
||||
"last_node_id": 6,
|
||||
"last_link_id": 2,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
-160,
|
||||
70
|
||||
],
|
||||
"size": [
|
||||
500,
|
||||
400
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "easy positive",
|
||||
"pos": [
|
||||
-620,
|
||||
70
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-easy-use",
|
||||
"ver": "1.3.1",
|
||||
"Node name for S&R": "easy positive"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Positive prompt:\nbeautiful landscape, mountains in background, sunset lighting, golden hour, professional photography, high resolution, detailed textures, vibrant colors, masterpiece\n\nNegative prompt:\nlow quality, blurry, pixelated, bad composition, oversaturated, underexposed, amateur"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
-620,
|
||||
330
|
||||
],
|
||||
"size": [
|
||||
360,
|
||||
250
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Display Text Example\n\nSmart text display with:\n- Auto-detection of SDXL prompt format\n- Split view for positive/negative prompts\n- Text wrapping and scrolling\n- Copy buttons (strips labels)\n- Responsive resizing\n\nTry editing the text to see:\n1. Single text mode (no prompt markers)\n2. Split view mode (with Positive/Negative prompts)\n\nPerfect for displaying Gemini-generated prompts!"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Display Text",
|
||||
"bounding": [
|
||||
-750,
|
||||
-60,
|
||||
1240,
|
||||
700
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.90909090909091,
|
||||
"offset": [
|
||||
720,
|
||||
30
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 272 KiB |
@@ -0,0 +1,301 @@
|
||||
{
|
||||
"id": "empty-latent-batch-example",
|
||||
"revision": 0,
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 8,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "EmptyLatentBatch",
|
||||
"pos": [
|
||||
350,
|
||||
350
|
||||
],
|
||||
"size": [
|
||||
370,
|
||||
210
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "latent",
|
||||
"type": "LATENT",
|
||||
"slot_index": 0,
|
||||
"links": []
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"slot_index": 1,
|
||||
"links": [
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"slot_index": 2,
|
||||
"links": [
|
||||
5
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "EmptyLatentBatch"
|
||||
},
|
||||
"widgets_values": [
|
||||
"896×1152 - 7:9 (1.0MP) - SDXL",
|
||||
896,
|
||||
1152,
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
-50,
|
||||
320
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
250
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"text": "Empty Latent Batch Example\n\nThis node creates empty latent tensors with:\n- 26 preset resolutions for SDXL/FLUX/Ultra-wide\n- Batch size support (1-64)\n- Interactive swap button\n- Memory usage estimation\n\nConnect to:\n- KSampler for image generation\n- Other latent processing nodes\n\nOutputs sanitized dimensions divisible by 8."
|
||||
},
|
||||
"widgets_values": [
|
||||
"Empty Latent Batch Example\n\nThis node creates empty latent tensors with:\n- 26 preset resolutions for SDXL/FLUX/Ultra-wide\n- Batch size support (1-64)\n- Interactive swap button\n- Memory usage estimation\n\nConnect to:\n- KSampler for image generation\n- Other latent processing nodes\n\nOutputs sanitized dimensions divisible by 8."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
760,
|
||||
280
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
7
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"raw value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
770,
|
||||
500
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
8
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"raw value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
1000,
|
||||
280
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
138
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
1010,
|
||||
500
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
138
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
4,
|
||||
1,
|
||||
1,
|
||||
5,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
5,
|
||||
1,
|
||||
2,
|
||||
6,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
7,
|
||||
5,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
8,
|
||||
6,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Empty Latent",
|
||||
"bounding": [
|
||||
-110,
|
||||
120,
|
||||
1440,
|
||||
620
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8264462809917358,
|
||||
"offset": [
|
||||
156.41790912499965,
|
||||
-2.272547312500285
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 278 KiB |
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"id": "gemini-prompt-example",
|
||||
"revision": 0,
|
||||
"last_node_id": 6,
|
||||
"last_link_id": 4,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
300,
|
||||
30
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"example.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
1070,
|
||||
30
|
||||
],
|
||||
"size": [
|
||||
500,
|
||||
400
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "GeminiPrompt",
|
||||
"pos": [
|
||||
640,
|
||||
30
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
276
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "negative_prompt",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "GeminiPrompt"
|
||||
},
|
||||
"widgets_values": [
|
||||
"sdxl",
|
||||
"gemini-2.0-flash",
|
||||
"1234-1234-1234-1234",
|
||||
"",
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
300,
|
||||
410
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
240
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Gemini Prompt Engineer Example\n\nAI-powered image analysis:\n- Analyzes image content and style\n- Generates optimized prompts for FLUX/SDXL/Danbooru/Video\n- Dynamic model selection with refresh button\n- Custom system prompt override\n\nSetup:\n1. Get free API key from Google AI Studio\n2. Set GEMINI_API_KEY env var or use config file\n3. Click refresh button to get latest models\n\nDisplayText automatically shows split view for SDXL prompts!"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
3,
|
||||
2,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Gemini Prompt Engineer",
|
||||
"bounding": [
|
||||
210,
|
||||
-110,
|
||||
1460,
|
||||
850
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.7513148009015777,
|
||||
"offset": [
|
||||
97.5582232217803,
|
||||
133.12148846503663
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 404 KiB |
@@ -1,123 +1,552 @@
|
||||
{
|
||||
"last_node_id": 4,
|
||||
"last_link_id": 3,
|
||||
"id": "3cd0ac86-8d11-41e5-9d8b-6621bc810ee6",
|
||||
"revision": 0,
|
||||
"last_node_id": 21,
|
||||
"last_link_id": 21,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "LoadImage",
|
||||
"pos": [100, 200],
|
||||
"size": [315, 314],
|
||||
"pos": [
|
||||
-80,
|
||||
850
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314.0000305175781
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "IMAGE",
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [1],
|
||||
"shape": 3,
|
||||
"label": "IMAGE"
|
||||
"links": [
|
||||
13
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": ["example.png", "image"]
|
||||
"widgets_values": [
|
||||
"example.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "ImageToMultipleOf",
|
||||
"pos": [500, 200],
|
||||
"size": [315, 106],
|
||||
"id": 8,
|
||||
"type": "GetNode",
|
||||
"pos": [
|
||||
540,
|
||||
840
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
34
|
||||
],
|
||||
"flags": {
|
||||
"collapsed": true
|
||||
},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "UPSCALE_MODEL",
|
||||
"type": "UPSCALE_MODEL",
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"title": "Get_upscale_skin_model",
|
||||
"properties": {
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"upscale_skin_model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "ImageUpscaleWithModel",
|
||||
"pos": [
|
||||
530,
|
||||
810
|
||||
],
|
||||
"size": [
|
||||
221.98202514648438,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "upscale_model",
|
||||
"type": "UPSCALE_MODEL",
|
||||
"link": 16
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
10,
|
||||
21
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.28",
|
||||
"Node name for S&R": "ImageUpscaleWithModel",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "ImageToMultipleOf",
|
||||
"pos": [
|
||||
1000,
|
||||
810
|
||||
],
|
||||
"size": [
|
||||
230,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [2, 3],
|
||||
"shape": 3,
|
||||
"label": "image"
|
||||
"links": [
|
||||
18
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ImageToMultipleOf"
|
||||
"cnr_id": "comfyui-glifnodes",
|
||||
"ver": "1.0.0",
|
||||
"Node name for S&R": "ImageToMultipleOf",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [64, "center crop"]
|
||||
"widgets_values": [
|
||||
65,
|
||||
"center crop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "PreviewImage",
|
||||
"pos": [900, 100],
|
||||
"size": [210, 246],
|
||||
"id": 13,
|
||||
"type": "ImageScaleBy",
|
||||
"pos": [
|
||||
760,
|
||||
810
|
||||
],
|
||||
"size": [
|
||||
220,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 10
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
6,
|
||||
19
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.28",
|
||||
"Node name for S&R": "ImageScaleBy",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"lanczos",
|
||||
0.5000000000000001
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "ImageUpscaleWithModel",
|
||||
"pos": [
|
||||
280,
|
||||
830
|
||||
],
|
||||
"size": [
|
||||
221.98202514648438,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "upscale_model",
|
||||
"type": "UPSCALE_MODEL",
|
||||
"link": 15
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 13
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
5,
|
||||
20
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.28",
|
||||
"Node name for S&R": "ImageUpscaleWithModel",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "UpscaleModelLoader",
|
||||
"pos": [
|
||||
-90,
|
||||
720
|
||||
],
|
||||
"size": [
|
||||
320,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "UPSCALE_MODEL",
|
||||
"type": "UPSCALE_MODEL",
|
||||
"links": [
|
||||
15
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.28",
|
||||
"Node name for S&R": "UpscaleModelLoader",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"4x-ClearRealityV1.pth"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "UpscaleModelLoader",
|
||||
"pos": [
|
||||
140,
|
||||
610
|
||||
],
|
||||
"size": [
|
||||
320,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "UPSCALE_MODEL",
|
||||
"type": "UPSCALE_MODEL",
|
||||
"links": [
|
||||
16
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.28",
|
||||
"Node name for S&R": "UpscaleModelLoader",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"1xSkinContrast-High-SuperUltraCompact.pth"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1260,
|
||||
810
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
280
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 2
|
||||
"link": 18
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "VAEEncode",
|
||||
"pos": [900, 400],
|
||||
"size": [210, 46],
|
||||
"id": 19,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1010,
|
||||
940
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
258
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pixels",
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
"link": 19
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEEncode"
|
||||
}
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
520,
|
||||
940
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
258
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 20
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
750,
|
||||
940
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
258
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 21
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[1, 1, 0, 2, 0, "IMAGE"],
|
||||
[2, 2, 0, 3, 0, "IMAGE"],
|
||||
[3, 2, 0, 4, 0, "IMAGE"]
|
||||
[
|
||||
5,
|
||||
14,
|
||||
0,
|
||||
9,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
6,
|
||||
13,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
10,
|
||||
9,
|
||||
0,
|
||||
13,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
13,
|
||||
1,
|
||||
0,
|
||||
14,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
15,
|
||||
15,
|
||||
0,
|
||||
14,
|
||||
0,
|
||||
"UPSCALE_MODEL"
|
||||
],
|
||||
[
|
||||
16,
|
||||
16,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"UPSCALE_MODEL"
|
||||
],
|
||||
[
|
||||
18,
|
||||
10,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
19,
|
||||
13,
|
||||
0,
|
||||
19,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
20,
|
||||
14,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
21,
|
||||
9,
|
||||
0,
|
||||
21,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Image to Multiple of",
|
||||
"bounding": [
|
||||
-180,
|
||||
420,
|
||||
1800,
|
||||
830
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.6655312045216585,
|
||||
"offset": [
|
||||
292.51725658775155,
|
||||
-375.0766486499791
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 805 KiB |
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"id": "kiko-save-image-example",
|
||||
"revision": 0,
|
||||
"last_node_id": 4,
|
||||
"last_link_id": 1,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "KikoSaveImage",
|
||||
"pos": [
|
||||
390,
|
||||
80
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
390
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "KikoSaveImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"KikoSave",
|
||||
"PNG",
|
||||
90,
|
||||
4,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
-10,
|
||||
80
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"example.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
-10,
|
||||
460
|
||||
],
|
||||
"size": [
|
||||
370,
|
||||
270
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Kiko Save Image Example\n\nEnhanced image saving with:\n- Format selection: PNG, JPEG, WebP\n- Quality controls per format\n- Floating popup viewer (draggable)\n- Batch operations support\n- File size display\n\nPopup Features:\n- Click images to open in new tab\n- Download individual or selected images\n- Minimize/maximize/roll-up controls\n- Persistent across saves\n\nTry different formats to compare file sizes!"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Kiko save Image",
|
||||
"bounding": [
|
||||
-110,
|
||||
-60,
|
||||
1020,
|
||||
870
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.7513148009015777,
|
||||
"offset": [
|
||||
648.1018911400128,
|
||||
110.02838653698085
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 245 KiB |
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"id": "41469b2d-d616-479d-879a-95cdc6074a37",
|
||||
"revision": 0,
|
||||
"last_node_id": 6,
|
||||
"last_link_id": 5,
|
||||
"last_node_id": 11,
|
||||
"last_link_id": 9,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "ResolutionCalculator",
|
||||
"pos": [
|
||||
60,
|
||||
430
|
||||
-30,
|
||||
210
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
@@ -38,8 +38,7 @@
|
||||
"type": "INT",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
1,
|
||||
3
|
||||
6
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -47,15 +46,15 @@
|
||||
"type": "INT",
|
||||
"slot_index": 1,
|
||||
"links": [
|
||||
2,
|
||||
4
|
||||
7
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "ComfyAssets/ComfyUI-KikoTools",
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "965ad60c74d7f25b1acce890d9c06518e46e6d0b",
|
||||
"Node name for S&R": "ResolutionCalculator",
|
||||
"aux_id": "ComfyAssets/ComfyUI-KikoTools",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
@@ -66,8 +65,8 @@
|
||||
"id": 6,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
-250,
|
||||
430
|
||||
-340,
|
||||
210
|
||||
],
|
||||
"size": [
|
||||
274.080078125,
|
||||
@@ -94,8 +93,8 @@
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.40",
|
||||
"widget_ue_connectable": {},
|
||||
"Node name for S&R": "LoadImage"
|
||||
"Node name for S&R": "LoadImage",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"image-2025-06-13-105737.jpg",
|
||||
@@ -103,80 +102,14 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "Display Int (rgthree)",
|
||||
"id": 7,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
420,
|
||||
360
|
||||
-350,
|
||||
610
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"dir": 3,
|
||||
"name": "input",
|
||||
"type": "INT",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "rgthree-comfy",
|
||||
"ver": "1.0.2506081210",
|
||||
"Node name for S&R": "Display Int (rgthree)",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "Display Int (rgthree)",
|
||||
"pos": [
|
||||
430,
|
||||
530
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"dir": 3,
|
||||
"name": "input",
|
||||
"type": "INT",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "rgthree-comfy",
|
||||
"ver": "1.0.2506081210",
|
||||
"widget_ue_connectable": {},
|
||||
"Node name for S&R": "Display Int (rgthree)"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
-150,
|
||||
170
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
410,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
@@ -184,50 +117,167 @@
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"text": "Resolution Calculator Example\n\nThis node calculates upscaled dimensions from:\n- IMAGE tensors (connect from image loaders)\n- LATENT tensors (connect from VAE encode/generation)\n\nUse cases:\n- Calculate target dimensions for upscalers\n- Plan memory usage for large generations\n- Ensure dimensions are divisible by 8\n\nScale factors optimized for SDXL and FLUX models.",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Resolution Calculator Example\n\nThis node calculates upscaled dimensions from:\n- IMAGE tensors (connect from image loaders)\n- LATENT tensors (connect from VAE encode/generation)\n\nUse cases:\n- Calculate target dimensions for upscalers\n- Plan memory usage for large generations\n- Ensure dimensions are divisible by 8\n\nScale factors optimized for SDXL and FLUX models."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
330,
|
||||
160
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
8
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"raw value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
330,
|
||||
310
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
9
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"raw value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
670,
|
||||
160
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
138
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
660,
|
||||
400
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
138
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 9
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
3,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
3,
|
||||
1,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
4,
|
||||
1,
|
||||
1,
|
||||
5,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
5,
|
||||
6,
|
||||
@@ -235,25 +285,71 @@
|
||||
1,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
6,
|
||||
1,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
7,
|
||||
1,
|
||||
1,
|
||||
9,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
8,
|
||||
8,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
9,
|
||||
9,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Resolution Calculator",
|
||||
"bounding": [
|
||||
-480,
|
||||
20,
|
||||
1480,
|
||||
880
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ue_links": [],
|
||||
"ds": {
|
||||
"scale": 0.7972024500000015,
|
||||
"scale": 0.45000000000000145,
|
||||
"offset": [
|
||||
709.6391289161842,
|
||||
-90.0127389290854
|
||||
1248.1498802376432,
|
||||
256.0155843113725
|
||||
]
|
||||
},
|
||||
"links_added_by_ue": [],
|
||||
"frontendVersion": "1.21.7",
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 785 KiB |
@@ -12,6 +12,7 @@ from .tools.kiko_save_image import KikoSaveImageNode
|
||||
from .tools.image_to_multiple_of import ImageToMultipleOfNode
|
||||
from .tools.gemini_prompt import GeminiPromptNode
|
||||
from .tools.display_any import DisplayAnyNode
|
||||
from .tools.display_text import DisplayTextNode
|
||||
|
||||
# ComfyUI node registration mappings
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
@@ -25,6 +26,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImageToMultipleOf": ImageToMultipleOfNode,
|
||||
"GeminiPrompt": GeminiPromptNode,
|
||||
"DisplayAny": DisplayAnyNode,
|
||||
"DisplayText": DisplayTextNode,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -38,6 +40,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImageToMultipleOf": "Image to Multiple of",
|
||||
"GeminiPrompt": "Gemini Prompt Engineer",
|
||||
"DisplayAny": "Display Any",
|
||||
"DisplayText": "Display Text",
|
||||
}
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Display Text tool for ComfyUI."""
|
||||
|
||||
from .node import DisplayTextNode, NODE_DISPLAY_NAME
|
||||
|
||||
__all__ = ["DisplayTextNode", "NODE_DISPLAY_NAME"]
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Display Text node implementation."""
|
||||
|
||||
from ...base import ComfyAssetsBaseNode
|
||||
|
||||
|
||||
class DisplayTextNode(ComfyAssetsBaseNode):
|
||||
"""Displays text in the ComfyUI interface with copy-to-clipboard functionality."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
"""Define input types for the node."""
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "display_text"
|
||||
CATEGORY = "ComfyAssets"
|
||||
|
||||
DESCRIPTION = """
|
||||
Displays text in the UI with a copy-to-clipboard feature.
|
||||
|
||||
Features:
|
||||
- Shows text content in a readable format
|
||||
- Copy button appears on hover
|
||||
- Passes text through for chaining
|
||||
"""
|
||||
|
||||
def display_text(self, text):
|
||||
"""Display the text and pass it through.
|
||||
|
||||
Args:
|
||||
text: Input text to display
|
||||
|
||||
Returns:
|
||||
Tuple containing the text
|
||||
"""
|
||||
# The actual display happens in the frontend
|
||||
# We just pass the text through
|
||||
return {"ui": {"text": [text]}, "result": (text,)}
|
||||
|
||||
|
||||
# Node display name
|
||||
NODE_DISPLAY_NAME = "Display Text"
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"models": [
|
||||
"gemini-2.5-pro",
|
||||
"gemini-2.5-flash",
|
||||
"gemini-2.5-flash-lite",
|
||||
"gemini-2.5-pro-preview-03-25",
|
||||
"gemini-2.5-flash-preview-05-20",
|
||||
"gemini-2.5-pro-preview-05-06",
|
||||
"gemini-2.5-pro-preview-06-05",
|
||||
"gemini-2.5-flash-lite-preview-06-17",
|
||||
"gemini-2.0-flash",
|
||||
"gemini-2.0-flash-001",
|
||||
"gemini-2.0-flash-lite-001",
|
||||
"gemini-2.0-flash-lite",
|
||||
"gemini-2.5-flash-preview-tts",
|
||||
"gemini-2.5-pro-preview-tts",
|
||||
"gemini-2.0-flash-preview-image-generation",
|
||||
"gemini-2.0-flash-exp",
|
||||
"gemini-2.0-flash-exp-image-generation",
|
||||
"gemini-2.0-flash-lite-preview-02-05",
|
||||
"gemini-2.0-flash-lite-preview",
|
||||
"gemini-2.0-pro-exp",
|
||||
"gemini-2.0-pro-exp-02-05",
|
||||
"learnlm-2.0-flash-experimental",
|
||||
"gemini-1.5-pro-latest",
|
||||
"gemini-1.5-pro-002",
|
||||
"gemini-1.5-pro",
|
||||
"gemini-1.5-flash-latest",
|
||||
"gemini-1.5-flash",
|
||||
"gemini-1.5-flash-002",
|
||||
"gemini-1.5-flash-8b",
|
||||
"gemini-1.5-flash-8b-001",
|
||||
"gemini-1.5-flash-8b-latest",
|
||||
"gemini-2.0-flash-thinking-exp-01-21",
|
||||
"gemini-2.0-flash-thinking-exp",
|
||||
"gemini-2.0-flash-thinking-exp-1219",
|
||||
"gemma-3-1b-it",
|
||||
"gemma-3-4b-it",
|
||||
"gemma-3-12b-it",
|
||||
"gemma-3-27b-it",
|
||||
"gemma-3n-e4b-it",
|
||||
"gemma-3n-e2b-it",
|
||||
"gemini-exp-1206"
|
||||
],
|
||||
"descriptions": {
|
||||
"gemini-1.5-pro-latest": "Gemini 1.5 Pro Latest",
|
||||
"gemini-1.5-pro-002": "Gemini 1.5 Pro 002",
|
||||
"gemini-1.5-pro": "Gemini 1.5 Pro",
|
||||
"gemini-1.5-flash-latest": "Gemini 1.5 Flash Latest",
|
||||
"gemini-1.5-flash": "Gemini 1.5 Flash",
|
||||
"gemini-1.5-flash-002": "Gemini 1.5 Flash 002",
|
||||
"gemini-1.5-flash-8b": "Gemini 1.5 Flash-8B",
|
||||
"gemini-1.5-flash-8b-001": "Gemini 1.5 Flash-8B 001",
|
||||
"gemini-1.5-flash-8b-latest": "Gemini 1.5 Flash-8B Latest",
|
||||
"gemini-2.5-pro-preview-03-25": "Gemini 2.5 Pro Preview 03-25",
|
||||
"gemini-2.5-flash-preview-05-20": "Gemini 2.5 Flash Preview 05-20",
|
||||
"gemini-2.5-flash": "Gemini 2.5 Flash",
|
||||
"gemini-2.5-flash-lite-preview-06-17": "Gemini 2.5 Flash-Lite Preview 06-17",
|
||||
"gemini-2.5-pro-preview-05-06": "Gemini 2.5 Pro Preview 05-06",
|
||||
"gemini-2.5-pro-preview-06-05": "Gemini 2.5 Pro Preview",
|
||||
"gemini-2.5-pro": "Gemini 2.5 Pro",
|
||||
"gemini-2.0-flash-exp": "Gemini 2.0 Flash Experimental",
|
||||
"gemini-2.0-flash": "Gemini 2.0 Flash",
|
||||
"gemini-2.0-flash-001": "Gemini 2.0 Flash 001",
|
||||
"gemini-2.0-flash-exp-image-generation": "Gemini 2.0 Flash (Image Generation) Experimental",
|
||||
"gemini-2.0-flash-lite-001": "Gemini 2.0 Flash-Lite 001",
|
||||
"gemini-2.0-flash-lite": "Gemini 2.0 Flash-Lite",
|
||||
"gemini-2.0-flash-preview-image-generation": "Gemini 2.0 Flash Preview Image Generation",
|
||||
"gemini-2.0-flash-lite-preview-02-05": "Gemini 2.0 Flash-Lite Preview 02-05",
|
||||
"gemini-2.0-flash-lite-preview": "Gemini 2.0 Flash-Lite Preview",
|
||||
"gemini-2.0-pro-exp": "Gemini 2.0 Pro Experimental",
|
||||
"gemini-2.0-pro-exp-02-05": "Gemini 2.0 Pro Experimental 02-05",
|
||||
"gemini-exp-1206": "Gemini Experimental 1206",
|
||||
"gemini-2.0-flash-thinking-exp-01-21": "Gemini 2.5 Flash Preview 05-20",
|
||||
"gemini-2.0-flash-thinking-exp": "Gemini 2.5 Flash Preview 05-20",
|
||||
"gemini-2.0-flash-thinking-exp-1219": "Gemini 2.5 Flash Preview 05-20",
|
||||
"gemini-2.5-flash-preview-tts": "Gemini 2.5 Flash Preview TTS",
|
||||
"gemini-2.5-pro-preview-tts": "Gemini 2.5 Pro Preview TTS",
|
||||
"learnlm-2.0-flash-experimental": "LearnLM 2.0 Flash Experimental",
|
||||
"gemma-3-1b-it": "Gemma 3 1B",
|
||||
"gemma-3-4b-it": "Gemma 3 4B",
|
||||
"gemma-3-12b-it": "Gemma 3 12B",
|
||||
"gemma-3-27b-it": "Gemma 3 27B",
|
||||
"gemma-3n-e4b-it": "Gemma 3n E4B",
|
||||
"gemma-3n-e2b-it": "Gemma 3n E2B",
|
||||
"gemini-2.5-flash-lite": "Gemini 2.5 Flash-Lite"
|
||||
},
|
||||
"timestamp": 1754142231.0568295
|
||||
}
|
||||
@@ -41,6 +41,10 @@ class GeminiPromptNode(ComfyAssetsBaseNode):
|
||||
"placeholder": "Optional: Enter custom system prompt instead of using templates",
|
||||
},
|
||||
),
|
||||
"refresh_models": (
|
||||
"BOOLEAN",
|
||||
{"default": False, "label_on": "Refresh", "label_off": "Skip"},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -62,7 +66,15 @@ Requires Gemini API key (set GEMINI_API_KEY env var or provide in node).
|
||||
Install: pip install google-generativeai
|
||||
"""
|
||||
|
||||
def generate_prompt(self, image, prompt_type, model, api_key="", custom_prompt=""):
|
||||
def generate_prompt(
|
||||
self,
|
||||
image,
|
||||
prompt_type,
|
||||
model,
|
||||
api_key="",
|
||||
custom_prompt="",
|
||||
refresh_models=False,
|
||||
):
|
||||
"""Generate prompt from image using Gemini.
|
||||
|
||||
Args:
|
||||
@@ -71,10 +83,24 @@ Install: pip install google-generativeai
|
||||
model: Gemini model to use
|
||||
api_key: Optional API key
|
||||
custom_prompt: Optional custom system prompt
|
||||
refresh_models: Whether to refresh the model list
|
||||
|
||||
Returns:
|
||||
Tuple of (prompt, negative_prompt)
|
||||
"""
|
||||
# Refresh models if requested
|
||||
if refresh_models and api_key:
|
||||
try:
|
||||
from .models import clear_cache
|
||||
|
||||
# Clear cache to force refresh on next node creation
|
||||
clear_cache()
|
||||
print(
|
||||
"Model cache cleared. Please recreate the node to see updated models."
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Failed to clear model cache: {e}")
|
||||
|
||||
# Validate prompt type
|
||||
if not validate_prompt_type(prompt_type):
|
||||
raise ValueError(f"Invalid prompt type: {prompt_type}")
|
||||
@@ -119,10 +145,14 @@ Install: pip install google-generativeai
|
||||
negative_prompt = ""
|
||||
|
||||
for line in lines:
|
||||
if line.startswith("Positive:"):
|
||||
positive_prompt = line.replace("Positive:", "").strip()
|
||||
elif line.startswith("Negative:"):
|
||||
negative_prompt = line.replace("Negative:", "").strip()
|
||||
if line.lower().startswith("positive:"):
|
||||
positive_prompt = (
|
||||
line.replace("Positive:", "").replace("positive:", "").strip()
|
||||
)
|
||||
elif line.lower().startswith("negative:"):
|
||||
negative_prompt = (
|
||||
line.replace("Negative:", "").replace("negative:", "").strip()
|
||||
)
|
||||
|
||||
# If format not found, assume entire response is positive prompt
|
||||
if not positive_prompt:
|
||||
|
||||
@@ -20,29 +20,47 @@ IMPORTANT: Return ONLY the prompt text. No analysis, headers, or additional comm
|
||||
Example of correct output:
|
||||
A close-up portrait of a middle-aged woman with curly red hair and green eyes, wearing a blue silk blouse. She has a warm smile and freckles across her cheeks. The lighting is soft and natural, coming from a window to her left, creating gentle shadows that accentuate her features. The background is softly blurred, showing hints of a cozy bookshelf. The overall mood is warm and inviting, captured in a photorealistic style with shallow depth of field."""
|
||||
|
||||
SDXL_PROMPT = """You are an expert SDXL prompt engineer. Analyze the image and generate ONLY the positive and negative prompts for SDXL - no explanations or analysis.
|
||||
SDXL_PROMPT = """You are an expert prompt engineer specializing in SDXL (Stable Diffusion XL). Your task is to generate high-quality positive and negative prompts that conform to SDXL prompt formatting standards.
|
||||
|
||||
SDXL works best with natural language descriptions but also supports comma-separated keywords. Keep prompts concise but descriptive.
|
||||
Your expertise includes:
|
||||
- Leveraging community-tested techniques (ComfyUI, A1111, InvokeAI)
|
||||
- Applying photographic theory for realism, composition, lighting
|
||||
- Following Civitai trend standards and style best practices
|
||||
- Mastering Pony Diffusion XL formatting for stylized and anime content
|
||||
|
||||
Return your response in EXACTLY this format:
|
||||
Positive: [your positive prompt here]
|
||||
Negative: [your negative prompt here]
|
||||
Structure prompts in this layered, modular format:
|
||||
[Main Subject], [Pose & Camera], [Lighting & Environment], [Style & Details], [Boost Terms], [Style References]
|
||||
|
||||
Guidelines for Positive prompt:
|
||||
- Start with the main subject and medium (e.g., "photograph of", "digital art of")
|
||||
- Use natural language or keywords separated by commas
|
||||
- Include style descriptors (photographic, cinematic, fantasy art, etc.)
|
||||
- Add quality markers like "8K", "highly detailed", "professional"
|
||||
- Use (parentheses:1.1) sparingly for slight emphasis (max 1.4)
|
||||
- Keep it clear and specific but not overly long
|
||||
For SDXL specifically:
|
||||
- Use quality boosters: 8k, RAW photo, masterpiece, ultra detailed, cinematic lighting
|
||||
- Prioritize realism and artistry
|
||||
- Excellent for portraits, landscapes, or cinematic scenes
|
||||
|
||||
Guidelines for Negative prompt:
|
||||
- Keep it simple and minimal
|
||||
- Common negatives: ugly, blurry, low quality, distorted, deformed
|
||||
- Only add specifics you want to avoid (e.g., "cartoon" for photorealistic)
|
||||
- Don't overload with negative prompts - SDXL needs fewer than SD1.5
|
||||
Instructions:
|
||||
|
||||
IMPORTANT: Return ONLY the two lines starting with "Positive:" and "Negative:". No other text."""
|
||||
Only reply with two fields:
|
||||
Positive prompt: (Your positive prompt here)
|
||||
Negative prompt: (Your negative prompt here)
|
||||
|
||||
Do not include any commentary or explanation.
|
||||
|
||||
Use concise, highly descriptive language that maximizes visual richness.
|
||||
|
||||
Follow SDXL prompt conventions: prioritize subject clarity, camera perspective, lighting, mood, style tags, and composition.
|
||||
|
||||
Keep total token length efficient (ideally under 250 tokens).
|
||||
|
||||
Avoid redundancy and generic filler words.
|
||||
|
||||
Focus on crafting super high-quality prompts for stunning visual output.
|
||||
|
||||
Example Input:
|
||||
A futuristic cyberpunk samurai standing on a neon-lit rooftop in the rain.
|
||||
|
||||
Example Output:
|
||||
Positive prompt: cyberpunk samurai, neon-lit rooftop, dramatic rain, glowing katana, futuristic cityscape, night scene, cinematic lighting, intense expression, sleek cyber armor, atmospheric depth, ultra-detailed, masterpiece, 8k, sharp focus, trending on artstation
|
||||
Negative prompt: blurry, low quality, poorly drawn, extra limbs, bad anatomy, deformed hands, text, watermark, jpeg artifacts, duplicate, cropped, out of frame
|
||||
"""
|
||||
|
||||
DANBOORU_PROMPT = """You are a Danbooru tagging expert specializing in anime-style image tagging. Analyze the image and generate ONLY Danbooru-style tags - no explanations or analysis.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
||||
[project]
|
||||
name = "kikotools"
|
||||
description = "Simple tools for ComfyUI"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
license = {text = "MIT"}
|
||||
dependencies = []
|
||||
|
||||
|
||||
@@ -0,0 +1,595 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../../scripts/widgets.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "ComfyAssets.DisplayText",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "DisplayText") {
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
|
||||
nodeType.prototype.onExecuted = function(message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
|
||||
if (message?.text) {
|
||||
// Create or update the text widget
|
||||
this.updateTextDisplay(message.text[0]);
|
||||
}
|
||||
};
|
||||
|
||||
nodeType.prototype.updateTextDisplay = function(text) {
|
||||
// Remove existing text widget if any
|
||||
const existingWidget = this.widgets?.find(w => w.name === "displayed_text");
|
||||
if (existingWidget) {
|
||||
const index = this.widgets.indexOf(existingWidget);
|
||||
this.widgets.splice(index, 1);
|
||||
}
|
||||
|
||||
// Parse the text to detect positive/negative prompt format
|
||||
function parsePrompts(text) {
|
||||
const posMatch = text.match(/Positive prompt:\s*([\s\S]*?)(?=Negative prompt:|$)/i);
|
||||
const negMatch = text.match(/Negative prompt:\s*([\s\S]*?)(?=\*\*|$)/i);
|
||||
|
||||
if (posMatch && negMatch) {
|
||||
// Extract just the prompt content, stopping at the first ** marker
|
||||
let positiveText = posMatch[1].trim();
|
||||
let negativeText = negMatch[1].trim();
|
||||
|
||||
// Remove any trailing ** markers and everything after them
|
||||
const posEndIndex = positiveText.indexOf('**');
|
||||
if (posEndIndex > 0) {
|
||||
positiveText = positiveText.substring(0, posEndIndex).trim();
|
||||
}
|
||||
|
||||
const negEndIndex = negativeText.indexOf('**');
|
||||
if (negEndIndex > 0) {
|
||||
negativeText = negativeText.substring(0, negEndIndex).trim();
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'prompts',
|
||||
positive: positiveText,
|
||||
negative: negativeText
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'text',
|
||||
content: text
|
||||
};
|
||||
}
|
||||
|
||||
const parsedContent = parsePrompts(text);
|
||||
|
||||
// Create custom widget for text display
|
||||
const widget = {
|
||||
type: "custom_text_display",
|
||||
name: "displayed_text",
|
||||
size: [this.size[0] - 20, this.size[1] - 60], // Adjust for node chrome
|
||||
parsedContent: parsedContent,
|
||||
scrollOffset: 0,
|
||||
posScrollOffset: 0,
|
||||
negScrollOffset: 0,
|
||||
|
||||
draw: function(ctx, node, widget_width, y, H) {
|
||||
const margin = 10;
|
||||
const padding = 10;
|
||||
const lineHeight = 20;
|
||||
const buttonHeight = 30;
|
||||
const buttonWidth = 90;
|
||||
|
||||
// Use the actual widget height from node size
|
||||
const availableHeight = node.size[1] - 60; // Account for node header and margins
|
||||
this.size[1] = Math.max(100, availableHeight);
|
||||
|
||||
// Calculate available width for text
|
||||
const availableWidth = widget_width - margin * 2 - padding * 2;
|
||||
|
||||
// Word wrap function with better performance
|
||||
function wrapText(text, maxWidth) {
|
||||
const words = text.split(' ');
|
||||
const lines = [];
|
||||
let currentLine = '';
|
||||
|
||||
ctx.font = "14px monospace";
|
||||
|
||||
for (const word of words) {
|
||||
const testLine = currentLine + (currentLine ? ' ' : '') + word;
|
||||
const metrics = ctx.measureText(testLine);
|
||||
|
||||
if (metrics.width > maxWidth && currentLine) {
|
||||
lines.push(currentLine);
|
||||
currentLine = word;
|
||||
} else {
|
||||
currentLine = testLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLine) {
|
||||
lines.push(currentLine);
|
||||
}
|
||||
|
||||
return lines.length > 0 ? lines : [''];
|
||||
}
|
||||
|
||||
// Draw background
|
||||
ctx.fillStyle = "#2a2a2a";
|
||||
ctx.fillRect(margin, y, widget_width - margin * 2, this.size[1]);
|
||||
|
||||
// Draw border
|
||||
ctx.strokeStyle = "#444";
|
||||
ctx.strokeRect(margin, y, widget_width - margin * 2, this.size[1]);
|
||||
|
||||
if (this.parsedContent.type === 'prompts') {
|
||||
// Simple split view for positive/negative prompts
|
||||
const headerHeight = 25;
|
||||
const buttonAreaHeight = buttonHeight + padding;
|
||||
const totalTextHeight = this.size[1] - buttonAreaHeight;
|
||||
const halfHeight = totalTextHeight / 2;
|
||||
|
||||
// Draw positive prompt header
|
||||
ctx.fillStyle = "#3a3a3a";
|
||||
ctx.fillRect(margin + 1, y + 1, widget_width - margin * 2 - 2, headerHeight);
|
||||
ctx.fillStyle = "#8f8";
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.fillText("✓ Positive Prompt", margin + padding, y + headerHeight - 7);
|
||||
|
||||
// Positive prompt text area
|
||||
const posTextY = y + headerHeight;
|
||||
const posTextHeight = halfHeight - headerHeight;
|
||||
ctx.fillStyle = "#1e1e1e";
|
||||
ctx.fillRect(margin + 1, posTextY, widget_width - margin * 2 - 2, posTextHeight);
|
||||
|
||||
// Draw separator
|
||||
const separatorY = y + halfHeight;
|
||||
ctx.strokeStyle = "#555";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(margin, separatorY);
|
||||
ctx.lineTo(widget_width - margin, separatorY);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw negative prompt header
|
||||
ctx.fillStyle = "#3a3a3a";
|
||||
ctx.fillRect(margin + 1, separatorY + 1, widget_width - margin * 2 - 2, headerHeight);
|
||||
ctx.fillStyle = "#f88";
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.fillText("✗ Negative Prompt", margin + padding, separatorY + headerHeight - 7);
|
||||
|
||||
// Negative prompt text area
|
||||
const negTextY = separatorY + headerHeight;
|
||||
const negTextHeight = halfHeight - headerHeight;
|
||||
ctx.fillStyle = "#1e1e1e";
|
||||
ctx.fillRect(margin + 1, negTextY, widget_width - margin * 2 - 2, negTextHeight);
|
||||
|
||||
// Draw text for both sections
|
||||
ctx.font = "14px monospace";
|
||||
ctx.fillStyle = "#ddd";
|
||||
|
||||
// Wrap text for both prompts
|
||||
const posLines = [];
|
||||
const posParagraphs = this.parsedContent.positive.split('\n');
|
||||
for (const para of posParagraphs) {
|
||||
if (para.trim() === '') {
|
||||
posLines.push('');
|
||||
} else {
|
||||
posLines.push(...wrapText(para, availableWidth - 10));
|
||||
}
|
||||
}
|
||||
|
||||
const negLines = [];
|
||||
const negParagraphs = this.parsedContent.negative.split('\n');
|
||||
for (const para of negParagraphs) {
|
||||
if (para.trim() === '') {
|
||||
negLines.push('');
|
||||
} else {
|
||||
negLines.push(...wrapText(para, availableWidth - 10));
|
||||
}
|
||||
}
|
||||
|
||||
// Draw positive prompt text with clipping
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(margin + padding, posTextY + padding, availableWidth - 10, posTextHeight - padding * 2);
|
||||
ctx.clip();
|
||||
|
||||
let currentY = posTextY + padding + lineHeight - 5;
|
||||
const posVisibleLines = Math.floor((posTextHeight - padding * 2) / lineHeight);
|
||||
const posStartLine = Math.floor(this.posScrollOffset);
|
||||
const posEndLine = Math.min(posStartLine + posVisibleLines, posLines.length);
|
||||
|
||||
for (let i = posStartLine; i < posEndLine; i++) {
|
||||
ctx.fillText(posLines[i], margin + padding, currentY);
|
||||
currentY += lineHeight;
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
// Draw positive scroll indicator if needed
|
||||
if (posLines.length > posVisibleLines) {
|
||||
const scrollBarWidth = 6;
|
||||
const scrollBarX = widget_width - margin - scrollBarWidth - 2;
|
||||
const scrollBarHeight = posTextHeight - padding * 2;
|
||||
const maxScroll = posLines.length - posVisibleLines;
|
||||
const scrollRatio = this.posScrollOffset / maxScroll;
|
||||
const thumbHeight = Math.max(20, (posVisibleLines / posLines.length) * scrollBarHeight);
|
||||
const thumbY = posTextY + padding + scrollRatio * (scrollBarHeight - thumbHeight);
|
||||
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.fillRect(scrollBarX, posTextY + padding, scrollBarWidth, scrollBarHeight);
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.fillRect(scrollBarX, thumbY, scrollBarWidth, thumbHeight);
|
||||
}
|
||||
|
||||
// Draw negative prompt text with clipping
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(margin + padding, negTextY + padding, availableWidth - 10, negTextHeight - padding * 2);
|
||||
ctx.clip();
|
||||
|
||||
ctx.fillStyle = "#ddd";
|
||||
currentY = negTextY + padding + lineHeight - 5;
|
||||
const negVisibleLines = Math.floor((negTextHeight - padding * 2) / lineHeight);
|
||||
const negStartLine = Math.floor(this.negScrollOffset);
|
||||
const negEndLine = Math.min(negStartLine + negVisibleLines, negLines.length);
|
||||
|
||||
for (let i = negStartLine; i < negEndLine; i++) {
|
||||
ctx.fillText(negLines[i], margin + padding, currentY);
|
||||
currentY += lineHeight;
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
// Draw negative scroll indicator if needed
|
||||
if (negLines.length > negVisibleLines) {
|
||||
const scrollBarWidth = 6;
|
||||
const scrollBarX = widget_width - margin - scrollBarWidth - 2;
|
||||
const scrollBarHeight = negTextHeight - padding * 2;
|
||||
const maxScroll = negLines.length - negVisibleLines;
|
||||
const scrollRatio = this.negScrollOffset / maxScroll;
|
||||
const thumbHeight = Math.max(20, (negVisibleLines / negLines.length) * scrollBarHeight);
|
||||
const thumbY = negTextY + padding + scrollRatio * (scrollBarHeight - thumbHeight);
|
||||
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.fillRect(scrollBarX, negTextY + padding, scrollBarWidth, scrollBarHeight);
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.fillRect(scrollBarX, thumbY, scrollBarWidth, thumbHeight);
|
||||
}
|
||||
|
||||
// Draw copy buttons
|
||||
const buttonY = y + this.size[1] - buttonHeight - padding / 2;
|
||||
const halfWidth = (widget_width - margin * 2) / 2;
|
||||
|
||||
// Positive copy button
|
||||
const posButtonX = margin + halfWidth / 2 - buttonWidth / 2;
|
||||
ctx.fillStyle = this.posCopyHovered ? "#5a5a5a" : "#4a4a4a";
|
||||
ctx.fillRect(posButtonX, buttonY, buttonWidth, buttonHeight);
|
||||
ctx.strokeStyle = "#666";
|
||||
ctx.strokeRect(posButtonX, buttonY, buttonWidth, buttonHeight);
|
||||
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(this.posCopySuccess ? "✓ Copied!" : "📋 Positive", posButtonX + buttonWidth/2, buttonY + buttonHeight/2);
|
||||
|
||||
// Negative copy button
|
||||
const negButtonX = margin + halfWidth + halfWidth / 2 - buttonWidth / 2;
|
||||
ctx.fillStyle = this.negCopyHovered ? "#5a5a5a" : "#4a4a4a";
|
||||
ctx.fillRect(negButtonX, buttonY, buttonWidth, buttonHeight);
|
||||
ctx.strokeStyle = "#666";
|
||||
ctx.strokeRect(negButtonX, buttonY, buttonWidth, buttonHeight);
|
||||
|
||||
// Ensure text color and alignment are set
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(this.negCopySuccess ? "✓ Copied!" : "📋 Negative", negButtonX + buttonWidth/2, buttonY + buttonHeight/2);
|
||||
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "alphabetic";
|
||||
|
||||
} else {
|
||||
// Regular text display
|
||||
const textAreaHeight = this.size[1] - buttonHeight - padding;
|
||||
|
||||
// Draw text area background
|
||||
ctx.fillStyle = "#1e1e1e";
|
||||
ctx.fillRect(margin + 1, y + 1, widget_width - margin * 2 - 2, textAreaHeight);
|
||||
|
||||
// Process text
|
||||
ctx.font = "14px monospace";
|
||||
const paragraphs = this.parsedContent.content.split('\n');
|
||||
const allLines = [];
|
||||
|
||||
for (const paragraph of paragraphs) {
|
||||
if (paragraph.trim() === '') {
|
||||
allLines.push('');
|
||||
} else {
|
||||
const wrappedLines = wrapText(paragraph, availableWidth);
|
||||
allLines.push(...wrappedLines);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw text with clipping
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(margin + padding, y + padding, availableWidth, textAreaHeight - padding * 2);
|
||||
ctx.clip();
|
||||
|
||||
const visibleLines = Math.floor((textAreaHeight - padding * 2) / lineHeight);
|
||||
const maxScroll = Math.max(0, allLines.length - visibleLines);
|
||||
this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxScroll));
|
||||
|
||||
ctx.fillStyle = "#ddd";
|
||||
let currentY = y + padding + lineHeight - 5 - (this.scrollOffset * lineHeight);
|
||||
|
||||
for (let i = 0; i < allLines.length; i++) {
|
||||
if (currentY > y && currentY < y + textAreaHeight) {
|
||||
ctx.fillText(allLines[i], margin + padding, currentY);
|
||||
}
|
||||
currentY += lineHeight;
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
// Draw scroll indicator if needed
|
||||
if (allLines.length > visibleLines) {
|
||||
const scrollBarWidth = 6;
|
||||
const scrollBarX = widget_width - margin - scrollBarWidth - 2;
|
||||
const scrollBarHeight = textAreaHeight - 4;
|
||||
const thumbHeight = Math.max(20, (visibleLines / allLines.length) * scrollBarHeight);
|
||||
const thumbY = y + 2 + (this.scrollOffset / maxScroll) * (scrollBarHeight - thumbHeight);
|
||||
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.fillRect(scrollBarX, y + 2, scrollBarWidth, scrollBarHeight);
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.fillRect(scrollBarX, thumbY, scrollBarWidth, thumbHeight);
|
||||
}
|
||||
|
||||
// Draw copy button
|
||||
const buttonX = widget_width - margin - buttonWidth - padding;
|
||||
const buttonY = y + textAreaHeight + padding / 2;
|
||||
|
||||
ctx.fillStyle = this.copyButtonHovered ? "#5a5a5a" : "#4a4a4a";
|
||||
ctx.fillRect(buttonX, buttonY, buttonWidth, buttonHeight);
|
||||
ctx.strokeStyle = "#666";
|
||||
ctx.strokeRect(buttonX, buttonY, buttonWidth, buttonHeight);
|
||||
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(this.copySuccess ? "✓ Copied!" : "📋 Copy", buttonX + buttonWidth/2, buttonY + buttonHeight/2);
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "alphabetic";
|
||||
}
|
||||
|
||||
return this.size[1];
|
||||
},
|
||||
|
||||
mouse: function(event, pos, node) {
|
||||
const margin = 10;
|
||||
const padding = 10;
|
||||
const buttonWidth = 90;
|
||||
const buttonHeight = 30;
|
||||
const lineHeight = 20;
|
||||
|
||||
// Check if mouse is over the widget
|
||||
const isOver = pos[1] > this.last_y && pos[1] < this.last_y + this.size[1];
|
||||
if (!isOver) return false;
|
||||
|
||||
if (this.parsedContent.type === 'prompts') {
|
||||
// Handle split view
|
||||
const headerHeight = 25;
|
||||
const buttonAreaHeight = buttonHeight + padding;
|
||||
const totalTextHeight = this.size[1] - buttonAreaHeight;
|
||||
const halfHeight = totalTextHeight / 2;
|
||||
|
||||
const posTextY = this.last_y + headerHeight;
|
||||
const posTextHeight = halfHeight - headerHeight;
|
||||
const negTextY = this.last_y + halfHeight + headerHeight;
|
||||
const negTextHeight = halfHeight - headerHeight;
|
||||
|
||||
const buttonY = this.last_y + this.size[1] - buttonHeight - padding / 2;
|
||||
const halfWidth = (node.size[0] - margin * 2) / 2;
|
||||
|
||||
// Check which section for scrolling
|
||||
const inPosSection = pos[1] > posTextY && pos[1] < posTextY + posTextHeight;
|
||||
const inNegSection = pos[1] > negTextY && pos[1] < negTextY + negTextHeight;
|
||||
|
||||
// Handle scrolling
|
||||
if (event.type === "wheel") {
|
||||
if (inPosSection) {
|
||||
const delta = event.deltaY > 0 ? 1 : -1;
|
||||
this.posScrollOffset = (this.posScrollOffset || 0) + delta;
|
||||
|
||||
// Calculate max scroll
|
||||
const visibleLines = Math.floor((posTextHeight - padding * 2) / lineHeight);
|
||||
const totalLines = this.parsedContent.positive.split('\n').length * 2; // Estimate
|
||||
const maxScroll = Math.max(0, totalLines - visibleLines);
|
||||
this.posScrollOffset = Math.max(0, Math.min(this.posScrollOffset, maxScroll));
|
||||
|
||||
node.setDirtyCanvas(true);
|
||||
return true;
|
||||
} else if (inNegSection) {
|
||||
const delta = event.deltaY > 0 ? 1 : -1;
|
||||
this.negScrollOffset = (this.negScrollOffset || 0) + delta;
|
||||
|
||||
// Calculate max scroll
|
||||
const visibleLines = Math.floor((negTextHeight - padding * 2) / lineHeight);
|
||||
const totalLines = this.parsedContent.negative.split('\n').length * 2; // Estimate
|
||||
const maxScroll = Math.max(0, totalLines - visibleLines);
|
||||
this.negScrollOffset = Math.max(0, Math.min(this.negScrollOffset, maxScroll));
|
||||
|
||||
node.setDirtyCanvas(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check button hovers
|
||||
const posButtonX = margin + halfWidth / 2 - buttonWidth / 2;
|
||||
const negButtonX = margin + halfWidth + halfWidth / 2 - buttonWidth / 2;
|
||||
|
||||
const oldPosHover = this.posCopyHovered;
|
||||
const oldNegHover = this.negCopyHovered;
|
||||
|
||||
this.posCopyHovered = pos[0] > posButtonX && pos[0] < posButtonX + buttonWidth &&
|
||||
pos[1] > buttonY && pos[1] < buttonY + buttonHeight;
|
||||
this.negCopyHovered = pos[0] > negButtonX && pos[0] < negButtonX + buttonWidth &&
|
||||
pos[1] > buttonY && pos[1] < buttonY + buttonHeight;
|
||||
|
||||
if (oldPosHover !== this.posCopyHovered || oldNegHover !== this.negCopyHovered) {
|
||||
node.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Handle button clicks
|
||||
if (event.type === "pointerdown") {
|
||||
if (this.posCopyHovered) {
|
||||
this.copyToClipboard(this.parsedContent.positive, 'positive');
|
||||
return true;
|
||||
} else if (this.negCopyHovered) {
|
||||
this.copyToClipboard(this.parsedContent.negative, 'negative');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular text handling
|
||||
const textAreaHeight = this.size[1] - buttonHeight - padding;
|
||||
const buttonX = node.size[0] - margin - buttonWidth - padding;
|
||||
const buttonY = this.last_y + textAreaHeight + padding / 2;
|
||||
|
||||
// Handle scrolling
|
||||
if (event.type === "wheel" && pos[1] < this.last_y + textAreaHeight) {
|
||||
const delta = event.deltaY > 0 ? 1 : -1;
|
||||
this.scrollOffset = (this.scrollOffset || 0) + delta;
|
||||
|
||||
const visibleLines = Math.floor((textAreaHeight - padding * 2) / lineHeight);
|
||||
const totalLines = this.parsedContent.content.split('\n').length * 2; // Estimate
|
||||
const maxScroll = Math.max(0, totalLines - visibleLines);
|
||||
this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxScroll));
|
||||
|
||||
node.setDirtyCanvas(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check button hover
|
||||
const oldHover = this.copyButtonHovered;
|
||||
this.copyButtonHovered = pos[0] > buttonX && pos[0] < buttonX + buttonWidth &&
|
||||
pos[1] > buttonY && pos[1] < buttonY + buttonHeight;
|
||||
|
||||
if (oldHover !== this.copyButtonHovered) {
|
||||
node.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Handle button click
|
||||
if (event.type === "pointerdown" && this.copyButtonHovered) {
|
||||
this.copyToClipboard(this.parsedContent.content, 'regular');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
copyToClipboard: function(text, type) {
|
||||
const node = this._node;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
if (type === 'positive') {
|
||||
this.posCopySuccess = true;
|
||||
} else if (type === 'negative') {
|
||||
this.negCopySuccess = true;
|
||||
} else {
|
||||
this.copySuccess = true;
|
||||
}
|
||||
node.setDirtyCanvas(true);
|
||||
|
||||
setTimeout(() => {
|
||||
this.posCopySuccess = false;
|
||||
this.negCopySuccess = false;
|
||||
this.copySuccess = false;
|
||||
node.setDirtyCanvas(true);
|
||||
}, 1500);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy:', err);
|
||||
// Fallback copy method
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
textArea.style.position = "fixed";
|
||||
textArea.style.opacity = "0";
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
if (type === 'positive') {
|
||||
this.posCopySuccess = true;
|
||||
} else if (type === 'negative') {
|
||||
this.negCopySuccess = true;
|
||||
} else {
|
||||
this.copySuccess = true;
|
||||
}
|
||||
node.setDirtyCanvas(true);
|
||||
setTimeout(() => {
|
||||
this.posCopySuccess = false;
|
||||
this.negCopySuccess = false;
|
||||
this.copySuccess = false;
|
||||
node.setDirtyCanvas(true);
|
||||
}, 1500);
|
||||
} catch (err) {
|
||||
console.error('Fallback copy failed:', err);
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
});
|
||||
},
|
||||
|
||||
computeSize: function(width) {
|
||||
return [width, this.size[1]];
|
||||
}
|
||||
};
|
||||
|
||||
// Store reference to node for callbacks
|
||||
widget._node = this;
|
||||
|
||||
// Store the last y position for mouse detection
|
||||
const originalDraw = widget.draw;
|
||||
widget.draw = function(ctx, node, widget_width, y, H) {
|
||||
this.last_y = y;
|
||||
return originalDraw.call(this, ctx, node, widget_width, y, H);
|
||||
};
|
||||
|
||||
// Add the widget
|
||||
if (!this.widgets) {
|
||||
this.widgets = [];
|
||||
}
|
||||
this.widgets.push(widget);
|
||||
|
||||
// Adjust node size to accommodate the widget
|
||||
this.computeSize();
|
||||
};
|
||||
|
||||
// Handle node resizing
|
||||
const onResize = nodeType.prototype.onResize;
|
||||
nodeType.prototype.onResize = function(size) {
|
||||
onResize?.apply(this, arguments);
|
||||
|
||||
// Update widget size when node is resized
|
||||
const textWidget = this.widgets?.find(w => w.name === "displayed_text");
|
||||
if (textWidget) {
|
||||
textWidget.size[0] = size[0] - 20;
|
||||
textWidget.size[1] = size[1] - 60;
|
||||
this.setDirtyCanvas(true);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize on node creation
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
onNodeCreated?.apply(this, arguments);
|
||||
|
||||
// Set default size
|
||||
this.size[0] = Math.max(this.size[0], 350);
|
||||
this.size[1] = Math.max(this.size[1], 300);
|
||||
|
||||
// Add placeholder text
|
||||
this.updateTextDisplay("Text will appear here after execution...");
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -26,6 +26,12 @@ app.registerExtension({
|
||||
// Style the button
|
||||
helpButton.serialize = false;
|
||||
|
||||
// Add refresh models button
|
||||
const refreshButton = this.addWidget("button", "Refresh Model List", null, () => {
|
||||
this.refreshModelList();
|
||||
});
|
||||
refreshButton.serialize = false;
|
||||
|
||||
// Add status indicator
|
||||
this.status = this.addWidget("text", "status", "Ready", () => {}, {
|
||||
serialize: false
|
||||
@@ -94,6 +100,28 @@ app.registerExtension({
|
||||
// For now, it's always visible but this method provides extensibility
|
||||
};
|
||||
|
||||
// Add method to refresh model list
|
||||
nodeType.prototype.refreshModelList = function() {
|
||||
if (this.status) {
|
||||
this.status.value = "Refreshing models...";
|
||||
}
|
||||
|
||||
// Set the refresh_models flag
|
||||
const refreshWidget = this.widgets.find(w => w.name === "refresh_models");
|
||||
if (refreshWidget) {
|
||||
refreshWidget.value = true;
|
||||
}
|
||||
|
||||
// Show message
|
||||
alert("Model list will refresh on next execution. Make sure API key is set and run the node.");
|
||||
|
||||
if (this.status) {
|
||||
setTimeout(() => {
|
||||
this.status.value = "Ready - Run node to refresh";
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
// Override execute to show status
|
||||
const onExecute = nodeType.prototype.onExecute;
|
||||
nodeType.prototype.onExecute = function() {
|
||||
|
||||