Initial release of ComfyUI Reve API Integration Node

- Unified node supporting Create, Edit, and Remix operations
- Dynamic input system based on operation type
- Comprehensive error handling and credit tracking
- Verbose logging for debugging and monitoring
- Clean public release with documentation and examples
This commit is contained in:
lum3on
2025-09-16 14:37:52 +02:00
commit 70efdb04ed
15 changed files with 2564 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
# Reve API Configuration
# Copy this file to .env and add your actual API key
# Your Reve API key (required)
REVE_API_KEY=your_reve_api_key_here
# Optional: API timeout in seconds (default: 60)
# REVE_API_TIMEOUT=60
# Optional: Enable debug logging (default: false)
# REVE_DEBUG=true
+170
View File
@@ -0,0 +1,170 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# OS files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Development and test files
test_*.py
debug_*.py
check_*.py
monitor_*.py
*_test.py
*_debug.py
# Temporary files
*.tmp
*.temp
*.bak
*.backup
# API keys and secrets
.env
*.key
*.secret
api_keys.txt
# ComfyUI specific
output/
temp/
models/
+212
View File
@@ -0,0 +1,212 @@
# ComfyUI Reve API Integration Node
A unified ComfyUI custom node that integrates all Reve API endpoints (Create, Edit, Remix) into a single, dynamic node with operation-specific inputs and seamless operation switching.
## ✅ Current Status
**The Reve API is now working!** This integration has been updated with the correct API endpoints and is fully functional.
The API uses the endpoints `/v1/image/create`, `/v1/image/edit`, and `/v1/image/remix`. Authentication works with API keys in the format `papi.xxxxx`.
## Features
- **Unified Interface**: Single node handles all three operations (Create, Edit, Remix)
- **Dynamic Inputs**: Show/hide parameters based on selected operation type
- **Consistent Outputs**: Standardized output format across all operations
- **Error Handling**: Comprehensive error management with operation-specific context
- **Credit Tracking**: Integrated credit monitoring and usage tracking
- **Verbose Logging**: Optional detailed logging for debugging and monitoring API interactions
## Operations
### Create
Generate images from text prompts.
- **Cost**: 18 credits per generation
- **Inputs**: prompt, aspect_ratio, version, output_format
### Edit
Modify existing images with text instructions.
- **Cost**: 30 credits per edit
- **Inputs**: prompt, reference_image, version, output_format
### Remix
Combine multiple images with text guidance.
- **Cost**: 30 credits per remix
- **Inputs**: prompt, reference_images (1-4), aspect_ratio, version, output_format
## Installation
1. Clone or download this repository to your ComfyUI custom_nodes directory:
```bash
cd ComfyUI/custom_nodes
git clone <repository_url> ComfyUI_Reve-API
```
2. Install dependencies:
```bash
cd ComfyUI_Reve-API
pip install -r requirements.txt
```
3. Restart ComfyUI
## Usage
1. Add the "Reve API (Create/Edit/Remix)" node to your workflow
2. Enter your Reve API key in the api_key field
3. Select the desired operation from the dropdown
4. Configure parameters based on the operation:
- **Create**: Enter prompt and select aspect ratio
- **Edit**: Connect reference image and enter editing instructions
- **Remix**: Connect reference images (1-4) and enter combination prompt
5. Execute the workflow
## Outputs
The node provides five outputs:
- **image**: Generated/edited image tensor
- **metadata**: JSON string with operation details
- **request_id**: Unique identifier for the API request
- **credits_used**: Number of credits consumed
- **credits_remaining**: Remaining credit balance
## API Key Management
Your Reve API key is entered directly in the node's api_key field. The key is required for all operations and should be in the format `papi.xxxxx`.
## Verbose Logging
The node includes an optional verbose logging feature that provides detailed information about API interactions, performance metrics, and debugging information.
### Enabling Verbose Logging
1. In the node interface, set the `verbose_logging` parameter to `True`
2. Check the ComfyUI console for detailed log output
### What Gets Logged
When verbose logging is enabled, the following information is logged to the console:
- **Operation Start**: Details about the operation being performed
- **Request Parameters**: Information about the API request (without sensitive data)
- **API Response**: Request ID, credits used/remaining, processing time
- **Performance Metrics**: Total processing time, API call time, image processing time
- **Error Details**: Detailed error information when operations fail
### Example Verbose Output
```
2025-09-16 12:38:26,317 - nodes.reve_api_node.verbose - INFO - 🚀 Starting Reve API CREATE operation
{
"prompt_length": 21,
"operation": "create",
"version": "latest",
"aspect_ratio": "16:9",
"output_format": "png"
}
2025-09-16 12:38:28,842 - nodes.reve_api_node.verbose - INFO - 🔍 Reve API CREATE Operation Complete
{
"operation": "create",
"request_id": "req-abc123",
"credits_used": 18,
"credits_remaining": 482,
"processing_time_seconds": 2.5,
"success": true
}
2025-09-16 12:38:28,845 - nodes.reve_api_node.verbose - INFO - ✅ Reve API CREATE operation completed successfully
{
"total_processing_time_seconds": 2.528,
"api_call_time_seconds": 2.5,
"image_processing_time_seconds": 0.028
}
```
### Benefits of Verbose Logging
- **Debugging**: Identify issues with API calls or image processing
- **Performance Monitoring**: Track processing times and optimize workflows
- **Credit Tracking**: Monitor credit usage across operations
- **Audit Trail**: Keep detailed records of API interactions
- **Troubleshooting**: Get detailed error information for support
### Note on Performance
Verbose logging has minimal performance impact when disabled (default). When enabled, it adds negligible overhead for logging operations.
## Error Handling
The node provides detailed error messages for common issues:
- Invalid API key
- Insufficient credits
- Content policy violations
- Rate limiting
- Network errors
## Requirements
- ComfyUI
- Python 3.8+
- PyTorch (included with ComfyUI)
- Pillow (included with ComfyUI)
- requests
- numpy (included with ComfyUI)
## Troubleshooting
### Node doesn't appear in ComfyUI
- Check that all dependencies are installed
- Verify the node is in the correct custom_nodes directory
- Check ComfyUI console for error messages
### API errors
- Verify your API key is correct
- Check your credit balance
- Ensure images meet size requirements (max 10MB for edit, 5MB each for remix)
### Image conversion errors
- Ensure input images are valid ComfyUI image tensors
- Check image dimensions are reasonable (512x512 to 2048x2048 recommended)
## Support
For issues related to:
- **Node functionality**: Check the GitHub repository issues
- **Reve API**: Contact Reve API support
- **ComfyUI integration**: Check ComfyUI documentation
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## Changelog
### v1.2.0
- **CHANGED**: API key is now required in the node interface - no more environment variable setup needed
- **REMOVED**: Environment variable fallback for API key (REVE_API_KEY)
- **IMPROVED**: Simplified setup process - users just paste their API key directly in the node
- **ENHANCED**: Better API key validation with clear error messages
### v1.1.1
- **FIXED**: HTTP 400 error in edit operation by using correct `edit_instruction` parameter
- **REMOVED**: Unsupported `aspect_ratio` parameter from edit operation
- **IMPROVED**: Error handling to show detailed API error messages instead of generic HTTP codes
- **ENHANCED**: Debugging capabilities with better error message extraction
### v1.1.0
- **NEW**: Verbose logging functionality for debugging and monitoring
- **CHANGED**: Simplified node outputs to only return image and metadata
- **ENHANCED**: Credit tracking and performance metrics now available via verbose logging
- **IMPROVED**: Better structured metadata output with JSON formatting
### v1.0.0
- Initial release
- Unified node supporting Create, Edit, and Remix operations
- Dynamic input system
- Comprehensive error handling
- Credit tracking and monitoring
+48
View File
@@ -0,0 +1,48 @@
"""
ComfyUI Reve API Integration Node
This package provides a unified ComfyUI custom node for integrating with the Reve API,
supporting Create, Edit, and Remix operations in a single dynamic node.
"""
import os
import sys
# Add current directory to path for imports
current_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, current_dir)
try:
from .nodes.reve_api_node import ReveAPI
# Node class mappings for ComfyUI
NODE_CLASS_MAPPINGS = {
"ReveAPI": ReveAPI
}
# Display name mappings for ComfyUI interface
NODE_DISPLAY_NAME_MAPPINGS = {
"ReveAPI": "Reve API (Create/Edit/Remix)"
}
# Web directory for any custom UI components (if needed in future)
WEB_DIRECTORY = "./web"
print("✅ ComfyUI Reve API Integration loaded successfully")
except ImportError as e:
print(f"❌ Failed to load ComfyUI Reve API Integration: {e}")
print("Please ensure all dependencies are installed:")
print(" pip install requests pillow")
# Create empty mappings to prevent ComfyUI errors
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
except Exception as e:
print(f"❌ Unexpected error loading ComfyUI Reve API Integration: {e}")
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
# Export for ComfyUI
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
+1
View File
@@ -0,0 +1 @@
# ComfyUI Reve API Integration - Core Module
+336
View File
@@ -0,0 +1,336 @@
"""
Image Utilities Module
This module provides utilities for converting between ComfyUI image tensors
and base64 encoded images required by the Reve API, with support for different
formats, size validation, and quality preservation.
"""
import io
import base64
import logging
from typing import List, Tuple, Optional, Union
from PIL import Image, ImageOps
import numpy as np
try:
import torch
except ImportError:
raise ImportError("PyTorch is required. This should be available in ComfyUI environment.")
# Configure logging
logger = logging.getLogger(__name__)
class ImageConversionError(Exception):
"""Exception raised for image conversion errors"""
pass
def tensor_to_pil(tensor: torch.Tensor) -> Image.Image:
"""
Convert ComfyUI image tensor to PIL Image.
Args:
tensor: ComfyUI image tensor (B, H, W, C) with values in [0, 1]
Returns:
PIL Image object
Raises:
ImageConversionError: If tensor format is invalid
"""
try:
# Handle batch dimension - take first image if batch
if tensor.dim() == 4:
tensor = tensor[0]
elif tensor.dim() != 3:
raise ImageConversionError(f"Expected 3D or 4D tensor, got {tensor.dim()}D")
# Ensure tensor is in correct format (H, W, C)
if tensor.shape[-1] not in [1, 3, 4]:
raise ImageConversionError(f"Expected 1, 3, or 4 channels, got {tensor.shape[-1]}")
# Convert to numpy and scale to [0, 255]
numpy_image = tensor.cpu().numpy()
numpy_image = np.clip(numpy_image * 255.0, 0, 255).astype(np.uint8)
# Convert to PIL Image
if numpy_image.shape[-1] == 1:
# Grayscale
pil_image = Image.fromarray(numpy_image.squeeze(-1), mode='L')
elif numpy_image.shape[-1] == 3:
# RGB
pil_image = Image.fromarray(numpy_image, mode='RGB')
elif numpy_image.shape[-1] == 4:
# RGBA
pil_image = Image.fromarray(numpy_image, mode='RGBA')
else:
raise ImageConversionError(f"Unsupported channel count: {numpy_image.shape[-1]}")
return pil_image
except Exception as e:
raise ImageConversionError(f"Failed to convert tensor to PIL: {str(e)}")
def pil_to_tensor(pil_image: Image.Image) -> torch.Tensor:
"""
Convert PIL Image to ComfyUI image tensor.
Args:
pil_image: PIL Image object
Returns:
ComfyUI image tensor (1, H, W, C) with values in [0, 1]
Raises:
ImageConversionError: If image format is invalid
"""
try:
# Convert to RGB if necessary
if pil_image.mode not in ['RGB', 'RGBA', 'L']:
pil_image = pil_image.convert('RGB')
# Convert to numpy array
numpy_image = np.array(pil_image)
# Handle grayscale
if len(numpy_image.shape) == 2:
numpy_image = numpy_image[:, :, np.newaxis]
# Normalize to [0, 1] and convert to float32
numpy_image = numpy_image.astype(np.float32) / 255.0
# Convert to tensor and add batch dimension
tensor = torch.from_numpy(numpy_image).unsqueeze(0)
return tensor
except Exception as e:
raise ImageConversionError(f"Failed to convert PIL to tensor: {str(e)}")
def tensor_to_base64(tensor: torch.Tensor, format: str = "PNG", quality: int = 95) -> str:
"""
Convert ComfyUI image tensor to base64 encoded string.
Args:
tensor: ComfyUI image tensor
format: Output format (PNG, JPEG, WEBP)
quality: JPEG/WEBP quality (1-100)
Returns:
Base64 encoded image string
Raises:
ImageConversionError: If conversion fails
"""
try:
# Convert tensor to PIL
pil_image = tensor_to_pil(tensor)
# Convert to RGB for JPEG (no transparency support)
if format.upper() == "JPEG" and pil_image.mode in ['RGBA', 'LA']:
# Create white background
background = Image.new('RGB', pil_image.size, (255, 255, 255))
if pil_image.mode == 'RGBA':
background.paste(pil_image, mask=pil_image.split()[-1])
else:
background.paste(pil_image)
pil_image = background
# Save to bytes buffer
buffer = io.BytesIO()
save_kwargs = {"format": format.upper()}
if format.upper() in ["JPEG", "WEBP"]:
save_kwargs["quality"] = quality
save_kwargs["optimize"] = True
pil_image.save(buffer, **save_kwargs)
# Encode to base64
buffer.seek(0)
base64_string = base64.b64encode(buffer.getvalue()).decode('utf-8')
return base64_string
except Exception as e:
raise ImageConversionError(f"Failed to convert tensor to base64: {str(e)}")
def base64_to_tensor(base64_string: str) -> torch.Tensor:
"""
Convert base64 encoded image string to ComfyUI image tensor.
Args:
base64_string: Base64 encoded image
Returns:
ComfyUI image tensor
Raises:
ImageConversionError: If conversion fails
"""
try:
# Decode base64
image_bytes = base64.b64decode(base64_string)
# Load as PIL Image
buffer = io.BytesIO(image_bytes)
pil_image = Image.open(buffer)
# Convert to tensor
tensor = pil_to_tensor(pil_image)
return tensor
except Exception as e:
raise ImageConversionError(f"Failed to convert base64 to tensor: {str(e)}")
def validate_image_size(tensor: torch.Tensor, max_size_mb: float = 10.0) -> bool:
"""
Validate image tensor size for API upload.
Args:
tensor: ComfyUI image tensor
max_size_mb: Maximum size in megabytes
Returns:
True if size is acceptable, False otherwise
"""
try:
# Convert to base64 to get actual upload size
base64_string = tensor_to_base64(tensor, format="PNG")
size_bytes = len(base64_string.encode('utf-8'))
size_mb = size_bytes / (1024 * 1024)
return size_mb <= max_size_mb
except Exception:
return False
def resize_image_if_needed(tensor: torch.Tensor, max_dimension: int = 2048) -> torch.Tensor:
"""
Resize image tensor if it exceeds maximum dimensions.
Args:
tensor: ComfyUI image tensor
max_dimension: Maximum width or height
Returns:
Resized tensor if needed, original tensor otherwise
"""
try:
# Get dimensions (handle batch dimension)
if tensor.dim() == 4:
_, height, width, _ = tensor.shape
else:
height, width, _ = tensor.shape
# Check if resize is needed
if height <= max_dimension and width <= max_dimension:
return tensor
# Calculate new dimensions maintaining aspect ratio
if height > width:
new_height = max_dimension
new_width = int(width * (max_dimension / height))
else:
new_width = max_dimension
new_height = int(height * (max_dimension / width))
# Convert to PIL, resize, and convert back
pil_image = tensor_to_pil(tensor)
pil_image = pil_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
resized_tensor = pil_to_tensor(pil_image)
logger.info(f"Resized image from {width}x{height} to {new_width}x{new_height}")
return resized_tensor
except Exception as e:
logger.warning(f"Failed to resize image: {str(e)}")
return tensor
def prepare_image_for_api(tensor: torch.Tensor, format: str = "PNG",
max_size_mb: float = 10.0, max_dimension: int = 2048) -> str:
"""
Prepare ComfyUI image tensor for Reve API upload.
Handles resizing, format conversion, and size validation.
Args:
tensor: ComfyUI image tensor
format: Output format (PNG, JPEG, WEBP)
max_size_mb: Maximum file size in MB
max_dimension: Maximum width/height
Returns:
Base64 encoded image string ready for API
Raises:
ImageConversionError: If preparation fails
"""
try:
# Resize if needed
processed_tensor = resize_image_if_needed(tensor, max_dimension)
# Convert to base64
base64_string = tensor_to_base64(processed_tensor, format)
# Validate size
size_bytes = len(base64_string.encode('utf-8'))
size_mb = size_bytes / (1024 * 1024)
if size_mb > max_size_mb:
# Try JPEG with lower quality if PNG is too large
if format.upper() == "PNG":
logger.warning(f"PNG too large ({size_mb:.1f}MB), trying JPEG")
base64_string = tensor_to_base64(processed_tensor, "JPEG", quality=85)
size_bytes = len(base64_string.encode('utf-8'))
size_mb = size_bytes / (1024 * 1024)
if size_mb > max_size_mb:
raise ImageConversionError(f"Image too large even after compression: {size_mb:.1f}MB > {max_size_mb}MB")
logger.debug(f"Prepared image: {size_mb:.1f}MB, format: {format}")
return base64_string
except Exception as e:
raise ImageConversionError(f"Failed to prepare image for API: {str(e)}")
def prepare_multiple_images_for_api(tensors: List[torch.Tensor], format: str = "PNG",
max_size_mb_each: float = 5.0) -> List[str]:
"""
Prepare multiple ComfyUI image tensors for Reve API (Remix operation).
Args:
tensors: List of ComfyUI image tensors
format: Output format
max_size_mb_each: Maximum size per image in MB
Returns:
List of base64 encoded image strings
Raises:
ImageConversionError: If preparation fails
"""
if not tensors or len(tensors) > 4:
raise ImageConversionError("Must provide 1-4 images for remix operation")
base64_images = []
for i, tensor in enumerate(tensors):
try:
base64_string = prepare_image_for_api(
tensor, format, max_size_mb_each, max_dimension=1536 # Smaller for multiple images
)
base64_images.append(base64_string)
except Exception as e:
raise ImageConversionError(f"Failed to prepare image {i+1}: {str(e)}")
return base64_images
+425
View File
@@ -0,0 +1,425 @@
"""
Reve API Client Module
This module provides a comprehensive HTTP client for interacting with the Reve API,
supporting Create, Edit, and Remix operations with proper authentication, error handling,
and retry logic.
"""
import os
import json
import time
import base64
import logging
from typing import Dict, Any, Optional, Union, List
from dataclasses import dataclass
from enum import Enum
try:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
except ImportError:
raise ImportError("requests library is required. Install with: pip install requests")
# Configure logging
logger = logging.getLogger(__name__)
class ReveOperation(Enum):
"""Supported Reve API operations"""
CREATE = "create"
EDIT = "edit"
REMIX = "remix"
class ReveError(Exception):
"""Base exception for Reve API errors"""
def __init__(self, message: str, error_code: str = None, request_id: str = None):
super().__init__(message)
self.error_code = error_code
self.request_id = request_id
class ReveAuthenticationError(ReveError):
"""Authentication related errors"""
pass
class ReveContentPolicyError(ReveError):
"""Content policy violation errors"""
pass
class ReveRateLimitError(ReveError):
"""Rate limiting errors"""
def __init__(self, message: str, retry_after: int = None, **kwargs):
super().__init__(message, **kwargs)
self.retry_after = retry_after
@dataclass
class ReveResponse:
"""Standardized response from Reve API"""
success: bool
image: Optional[str] = None # Base64 encoded image
metadata: Optional[Dict[str, Any]] = None
request_id: Optional[str] = None
credits_used: Optional[int] = None
credits_remaining: Optional[int] = None
error_code: Optional[str] = None
error_message: Optional[str] = None
class ReveAPIClient:
"""
Core HTTP client for Reve API operations.
Handles authentication, request formatting, response parsing,
error handling, and retry logic for all Reve API endpoints.
"""
BASE_URL = "https://api.reve.com"
DEFAULT_TIMEOUT = 60
MAX_RETRIES = 3
BACKOFF_FACTOR = 1.0
def __init__(self, api_key: str, timeout: int = DEFAULT_TIMEOUT):
"""
Initialize the Reve API client.
Args:
api_key: Reve API key (required)
timeout: Request timeout in seconds
"""
if not api_key or api_key.strip() == "":
raise ReveAuthenticationError("API key is required.")
self.api_key = api_key.strip()
self.timeout = timeout
self.session = self._create_session()
def _create_session(self) -> requests.Session:
"""Create a requests session with retry strategy and authentication."""
session = requests.Session()
# Configure retry strategy
retry_strategy = Retry(
total=self.MAX_RETRIES,
backoff_factor=self.BACKOFF_FACTOR,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["POST"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
# Set default headers
session.headers.update({
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
"User-Agent": "ComfyUI-Reve-API/1.0"
})
return session
def _make_request(self, endpoint: str, payload: Dict[str, Any]) -> Dict[str, Any]:
"""
Make HTTP request to Reve API endpoint.
Args:
endpoint: API endpoint path
payload: Request payload
Returns:
Parsed JSON response
Raises:
ReveError: For various API errors
"""
url = f"{self.BASE_URL}{endpoint}"
try:
logger.debug(f"Making request to {url}")
response = self.session.post(url, json=payload, timeout=self.timeout)
# Handle rate limiting with custom retry
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
raise ReveRateLimitError(
f"Rate limit exceeded. Retry after {retry_after} seconds.",
retry_after=retry_after,
request_id=response.headers.get("X-Request-ID")
)
# Check if we got HTML instead of JSON (API not available)
content_type = response.headers.get("content-type", "").lower()
if "text/html" in content_type:
raise ReveError(
"Reve API endpoints are not available. The API may be in development or requires special access. "
"Please check https://api.reve.com for the latest API status or contact Reve support.",
"API_NOT_AVAILABLE",
None
)
# Parse response
try:
response_data = response.json()
except json.JSONDecodeError:
# Special handling for 404 errors that return HTML
if response.status_code == 404:
raise ReveError(
"Reve API endpoint not found. The API endpoints may have changed or the API is not yet publicly available. "
"Please check https://api.reve.com for the latest API documentation.",
"ENDPOINT_NOT_FOUND",
None
)
response_data = {"error": {"message": "Invalid JSON response"}}
# Handle HTTP errors
if not response.ok:
# Try to get detailed error information from response
error_info = response_data.get("error", {})
# If no error object, check for direct error fields
if not error_info:
error_info = {
"code": response_data.get("code"),
"message": response_data.get("message") or response_data.get("error")
}
error_code = error_info.get("code", f"HTTP_{response.status_code}")
error_message = error_info.get("message", f"HTTP {response.status_code} error")
request_id = response_data.get("request_id") or response.headers.get("X-Request-ID")
# Add raw response data for debugging if error message is generic
if error_message == f"HTTP {response.status_code} error":
logger.debug(f"Raw response data: {response_data}")
# Try to extract any error information from the response
if isinstance(response_data, dict):
# Look for common error field names
for field in ["detail", "details", "msg", "description"]:
if field in response_data:
error_message = f"HTTP {response.status_code}: {response_data[field]}"
break
else:
# If we have response data but no clear error message, include it
if response_data:
error_message = f"HTTP {response.status_code}: {json.dumps(response_data)}"
if response.status_code == 401:
raise ReveAuthenticationError(error_message, error_code, request_id)
elif response.status_code == 404:
raise ReveError(
"Reve API endpoint not found. The API endpoints may have changed or the API is not yet publicly available. "
"Please check https://api.reve.com for the latest API documentation.",
"ENDPOINT_NOT_FOUND",
request_id
)
elif error_code == "CONTENT_POLICY_VIOLATION":
raise ReveContentPolicyError(error_message, error_code, request_id)
else:
raise ReveError(error_message, error_code, request_id)
return response_data
except requests.exceptions.Timeout:
raise ReveError(f"Request timeout after {self.timeout} seconds")
except requests.exceptions.ConnectionError:
raise ReveError("Connection error. Please check your internet connection.")
except requests.exceptions.RequestException as e:
raise ReveError(f"Request failed: {str(e)}")
def create(self, prompt: str, aspect_ratio: str = "3:2", version: str = "latest") -> ReveResponse:
"""
Create image from text prompt.
Args:
prompt: Text description of desired image
aspect_ratio: Image aspect ratio (16:9, 9:16, 3:2, 2:3, 4:3, 3:4, 1:1)
version: Model version (latest, reve-create@20250915)
Returns:
ReveResponse with generated image and metadata (always returns PNG format)
"""
payload = {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"version": version
}
try:
response_data = self._make_request("/v1/image/create", payload)
# Create metadata from response fields
metadata = {
"version": response_data.get("version"),
"content_violation": response_data.get("content_violation"),
"aspect_ratio": aspect_ratio,
"prompt": prompt
}
return ReveResponse(
success=True,
image=response_data.get("image"),
metadata=metadata,
request_id=response_data.get("request_id"),
credits_used=response_data.get("credits_used"),
credits_remaining=response_data.get("credits_remaining")
)
except ReveError as e:
return ReveResponse(
success=False,
error_code=e.error_code,
error_message=str(e),
request_id=e.request_id
)
def edit(self, prompt: str, reference_image: str, version: str = "latest") -> ReveResponse:
"""
Edit existing image with text instructions.
Args:
prompt: Text instruction for editing (sent as edit_instruction to API)
reference_image: Base64 encoded source image
version: Model version (latest, reve-edit@20250915)
Returns:
ReveResponse with edited image and metadata
"""
payload = {
"edit_instruction": prompt, # API expects 'edit_instruction', not 'prompt'
"reference_image": reference_image,
"version": version
}
try:
response_data = self._make_request("/v1/image/edit", payload)
# Create metadata from response fields
metadata = {
"version": response_data.get("version"),
"content_violation": response_data.get("content_violation"),
"prompt": prompt,
"reference_image": reference_image
}
return ReveResponse(
success=True,
image=response_data.get("image"),
metadata=metadata,
request_id=response_data.get("request_id"),
credits_used=response_data.get("credits_used"),
credits_remaining=response_data.get("credits_remaining")
)
except ReveError as e:
return ReveResponse(
success=False,
error_code=e.error_code,
error_message=str(e),
request_id=e.request_id
)
def remix(self, prompt: str, reference_images: List[str], aspect_ratio: str = "3:2",
version: str = "latest") -> ReveResponse:
"""
Remix multiple images with text guidance.
Args:
prompt: Text description with XML tags for image references
reference_images: List of 1-4 base64 encoded images
aspect_ratio: Output image aspect ratio
version: Model version (latest, reve-remix@20250915)
Returns:
ReveResponse with remixed image and metadata
"""
if not reference_images or len(reference_images) > 4:
return ReveResponse(
success=False,
error_code="INVALID_PARAMETERS",
error_message="reference_images must contain 1-4 images"
)
payload = {
"prompt": prompt,
"reference_images": reference_images,
"aspect_ratio": aspect_ratio,
"version": version
}
try:
response_data = self._make_request("/v1/image/remix", payload)
# Create metadata from response fields
metadata = {
"version": response_data.get("version"),
"content_violation": response_data.get("content_violation"),
"aspect_ratio": aspect_ratio,
"prompt": prompt,
"reference_images": reference_images
}
return ReveResponse(
success=True,
image=response_data.get("image"),
metadata=metadata,
request_id=response_data.get("request_id"),
credits_used=response_data.get("credits_used"),
credits_remaining=response_data.get("credits_remaining")
)
except ReveError as e:
return ReveResponse(
success=False,
error_code=e.error_code,
error_message=str(e),
request_id=e.request_id
)
def get_credits(self) -> Dict[str, Any]:
"""
Get current credit balance (if supported by API).
Returns:
Dictionary with credit information
"""
# This endpoint may not exist - implement if available
try:
response_data = self._make_request("/api/credits", {})
return response_data
except ReveError:
# Fallback - credits are returned with each operation
return {"error": "Credits endpoint not available"}
def close(self):
"""Close the HTTP session."""
if self.session:
self.session.close()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
# Utility function for easy client creation
def create_reve_client(api_key: str, timeout: int = 60) -> ReveAPIClient:
"""
Create a Reve API client instance.
Args:
api_key: API key (required)
timeout: Request timeout in seconds
Returns:
Configured ReveAPIClient instance
"""
return ReveAPIClient(api_key=api_key, timeout=timeout)
+208
View File
@@ -0,0 +1,208 @@
# Reve API Research and Specifications
## Overview
Reve API provides image generation, editing, and remixing capabilities through three main endpoints. This document consolidates the available information about the API structure, authentication, and operation parameters.
## Base API Information
- **Base URL**: `https://api.reve.com`
- **Authentication**: Bearer token via Authorization header
- **Content Type**: `application/json`
- **Response Format**: JSON with base64 encoded images
## Authentication
### API Key Management
- **Header Format**: `Authorization: Bearer {api_key}`
- **Environment Variable**: `REVE_API_KEY`
- **Scope**: All endpoints require authentication
### Error Responses
- **401 Unauthorized**: Invalid or missing API key
- **403 Forbidden**: Insufficient permissions
- **429 Too Many Requests**: Rate limiting exceeded
## API Endpoints
### 1. Create Endpoint
**Purpose**: Generate images from text prompts
```
POST /api/create
```
**Parameters**:
- `prompt` (required): Text description of desired image
- `aspect_ratio` (optional): Image dimensions (16:9, 9:16, 3:2, 2:3, 4:3, 3:4, 1:1)
- `version` (optional): Model version (latest, reve-create@20250915)
- `output_format` (optional): Image format (png, jpeg, webp, json)
**Cost**: 18 credits per generation
### 2. Edit Endpoint
**Purpose**: Modify existing images with text instructions
```
POST /api/edit
```
**Parameters**:
- `prompt` (required): Text instruction for editing
- `reference_image` (required): Base64 encoded source image
- `version` (optional): Model version (latest, reve-edit@20250915)
- `output_format` (optional): Image format (png, jpeg, webp, json)
**Cost**: 30 credits per edit
### 3. Remix Endpoint
**Purpose**: Combine multiple images with text guidance
```
POST /api/remix
```
**Parameters**:
- `prompt` (required): Text description with XML tags for image references
- `reference_images` (required): Array of 1-4 base64 encoded images
- `aspect_ratio` (optional): Output image dimensions
- `version` (optional): Model version (latest, reve-remix@20250915)
- `output_format` (optional): Image format (png, jpeg, webp, json)
**Cost**: 30 credits per remix
**XML Tag Format**: Use `<img1>`, `<img2>`, etc. in prompt to reference images
## Response Format
### Success Response
```json
{
"image": "base64_encoded_image_data",
"request_id": "unique_request_identifier",
"credits_used": 18,
"credits_remaining": 982,
"metadata": {
"operation": "create|edit|remix",
"parameters": {...},
"processing_time": 2.5,
"model_version": "reve-create@20250915"
}
}
```
### Error Response
```json
{
"error": {
"code": "CONTENT_POLICY_VIOLATION",
"message": "Content violates policy guidelines",
"details": "Specific violation description"
},
"request_id": "unique_request_identifier"
}
```
## Error Codes
### Client Errors (4xx)
- `400 BAD_REQUEST`: Invalid parameters or malformed request
- `401 UNAUTHORIZED`: Invalid or missing API key
- `403 FORBIDDEN`: Insufficient permissions or credits
- `404 NOT_FOUND`: Endpoint not found
- `429 TOO_MANY_REQUESTS`: Rate limit exceeded
### Server Errors (5xx)
- `500 INTERNAL_SERVER_ERROR`: Server processing error
- `502 BAD_GATEWAY`: Upstream service error
- `503 SERVICE_UNAVAILABLE`: Service temporarily unavailable
## Rate Limiting
- **Default Limits**: Not explicitly documented
- **Headers**: Check `X-RateLimit-*` headers in responses
- **Retry Strategy**: Exponential backoff recommended
## Content Policy
### Prohibited Content
- Violence, gore, or harmful content
- Adult or sexual content
- Copyrighted material without permission
- Hate speech or discriminatory content
### Detection
- Automatic content filtering on all operations
- Returns `CONTENT_POLICY_VIOLATION` error code
- No credits charged for policy violations
## Image Specifications
### Input Requirements
- **Format**: Base64 encoded (PNG, JPEG, WebP)
- **Size Limits**:
- Edit: Single image, max 10MB
- Remix: 1-4 images, max 5MB each
- **Resolution**: Recommended 512x512 to 2048x2048
### Output Specifications
- **Format**: PNG, JPEG, WebP, or JSON metadata
- **Quality**: High resolution, production ready
- **Encoding**: Base64 in JSON response
## Model Versions
### Available Versions
- `latest`: Always points to newest stable version
- `reve-create@20250915`: Specific create model version
- `reve-edit@20250915`: Specific edit model version
- `reve-remix@20250915`: Specific remix model version
### Version Selection
- Default: `latest` for all operations
- Explicit: Specify exact version for reproducibility
- Compatibility: Older versions may be deprecated
## Integration Notes
### Best Practices
1. **Error Handling**: Implement comprehensive error handling for all response codes
2. **Retry Logic**: Use exponential backoff for rate limiting and server errors
3. **Credit Monitoring**: Track credit usage to prevent service interruption
4. **Image Optimization**: Compress images before base64 encoding to reduce payload size
5. **Caching**: Cache successful responses when appropriate
### Performance Considerations
- **Request Size**: Minimize base64 payload size
- **Concurrent Requests**: Respect rate limits
- **Timeout Handling**: Set appropriate timeouts (30-60 seconds)
- **Memory Management**: Handle large image data efficiently
## Security Considerations
### API Key Protection
- Store API keys securely (environment variables, secure storage)
- Never expose keys in client-side code
- Rotate keys regularly
- Monitor for unauthorized usage
### Data Privacy
- Images are processed server-side
- No guarantee of data retention policies
- Consider data sensitivity before uploading
## Testing and Development
### Sandbox Environment
- No explicit sandbox mentioned in documentation
- Use low-cost operations for testing
- Monitor credit usage during development
### Debugging
- Use `request_id` for support requests
- Log API responses for troubleshooting
- Monitor error patterns and rates
---
*Note: This documentation is based on available project specifications and common API patterns. Some details may need verification against the actual Reve API documentation when accessible.*
+193
View File
@@ -0,0 +1,193 @@
# ComfyUI Reve API Integration Node - Unified Architecture Design
## Overview
This document defines the architecture for a unified ComfyUI custom node that integrates all Reve API endpoints (Create, Edit, Remix) into a single, dynamic node with operation-specific inputs and outputs.
## Core Design Principles
1. **Unified Interface**: Single node handles all three operations (Create, Edit, Remix)
2. **Dynamic Inputs**: Show/hide parameters based on selected operation type
3. **Consistent Outputs**: Standardized output format across all operations
4. **Error Handling**: Unified error management with operation-specific context
5. **Credit Tracking**: Integrated credit monitoring and usage tracking
## Node Architecture
### Main Node Class: `ReveAPI`
```python
class ReveAPI:
"""
Unified Reve API node supporting Create, Edit, and Remix operations
with dynamic input system and consistent output handling.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"operation": (["create", "edit", "remix"], {"default": "create"}),
"prompt": ("STRING", {"multiline": True, "default": "A beautiful landscape"})
},
"optional": {
"api_key": ("STRING", {"default": ""}),
"version": (["latest", "reve-create@20250915", "reve-edit@20250915", "reve-remix@20250915"],
{"default": "latest"}),
"aspect_ratio": (["16:9", "9:16", "3:2", "2:3", "4:3", "3:4", "1:1"],
{"default": "3:2"}),
"output_format": (["png", "jpeg", "webp", "json"], {"default": "png"}),
"reference_image": ("IMAGE",),
"reference_images": ("IMAGE",) # Multi-image input for Remix
}
}
RETURN_TYPES = ("IMAGE", "STRING", "STRING", "INT", "INT")
RETURN_NAMES = ("image", "metadata", "request_id", "credits_used", "credits_remaining")
FUNCTION = "process_request"
CATEGORY = "image/ai"
```
### Dynamic Input System
The node implements conditional input visibility based on operation type:
#### Create Mode
- **Show**: prompt, aspect_ratio, version, output_format, api_key
- **Hide**: reference_image, reference_images
- **Validation**: prompt required, aspect_ratio valid, version valid
#### Edit Mode
- **Show**: prompt, reference_image, version, output_format, api_key
- **Hide**: reference_images, aspect_ratio
- **Validation**: prompt required, reference_image required, version valid
#### Remix Mode
- **Show**: prompt, reference_images, aspect_ratio, version, output_format, api_key
- **Hide**: reference_image
- **Validation**: prompt required, reference_images (1-4), aspect_ratio valid, XML tags valid
### Module Structure
```
custom_nodes/ComfyUI_Reve-API/
├── __init__.py # Node registration and ComfyUI integration
├── requirements.txt # Dependencies
├── README.md # Installation and usage guide
├── nodes/
│ ├── __init__.py
│ ├── reve_api_node.py # Main unified ReveAPI node
│ ├── dynamic_inputs.py # Dynamic input system implementation
│ └── operation_handlers.py # Operation-specific logic handlers
├── core/
│ ├── __init__.py
│ ├── reve_api_client.py # HTTP client for Reve API
│ ├── image_utils.py # Image conversion utilities
│ ├── credit_monitor.py # Credit tracking and monitoring
│ └── error_handler.py # Error handling and content policy detection
├── docs/
│ ├── api_research.md # Reve API specifications
│ ├── unified_architecture.md # This document
│ └── examples/ # Usage examples and workflows
└── tests/
├── __init__.py
├── test_api_client.py
├── test_image_utils.py
├── test_unified_node.py
├── test_dynamic_inputs.py
├── test_operation_handlers.py
└── test_error_handling.py
```
### Core Components
#### 1. API Client (`reve_api_client.py`)
- HTTP request handling with authentication
- Request formatting for each operation type
- Response parsing and error handling
- Retry logic for rate limiting
- Credit tracking integration
#### 2. Image Utilities (`image_utils.py`)
- ComfyUI tensor to base64 conversion
- Multi-image handling for Remix operation
- Format validation and size checking
- Quality preservation during conversion
#### 3. Dynamic Input System (`dynamic_inputs.py`)
- Operation-based input visibility control
- Parameter validation per operation type
- Input mapping and transformation
- ComfyUI refresh handling
#### 4. Operation Handlers (`operation_handlers.py`)
- Create operation: Text-to-image generation
- Edit operation: Image editing with instructions
- Remix operation: Multi-image combination with XML parsing
- Unified response processing
### API Integration Specifications
#### Reve API Endpoints
- **Create**: `POST /api/create` - Text-to-image generation (18 credits)
- **Edit**: `POST /api/edit` - Image editing (30 credits)
- **Remix**: `POST /api/remix` - Multi-image combination (30 credits)
#### Authentication
- API key via header: `Authorization: Bearer {api_key}`
- Environment variable fallback: `REVE_API_KEY`
- Per-request override via node input
#### Request/Response Format
All operations return standardized response:
```json
{
"image": "base64_encoded_image",
"request_id": "unique_request_identifier",
"credits_used": 18,
"credits_remaining": 982,
"metadata": {
"operation": "create|edit|remix",
"parameters": {...},
"processing_time": 2.5
}
}
```
### Error Handling Strategy
#### Error Categories
1. **Authentication Errors** (401): Invalid API key
2. **Validation Errors** (400): Invalid parameters
3. **Content Policy** (400): Content policy violations
4. **Rate Limiting** (429): Too many requests
5. **Server Errors** (500): API service issues
#### Error Response Format
```python
{
"success": False,
"error_code": "CONTENT_POLICY_VIOLATION",
"error_message": "Content violates policy guidelines",
"operation": "create",
"retry_after": None # For rate limiting
}
```
### Performance Considerations
1. **API Client Caching**: Reuse HTTP client instances
2. **Image Conversion Optimization**: Efficient tensor/base64 conversion
3. **Memory Management**: Cleanup after operation switching
4. **Parameter Validation**: Early validation to prevent wasted credits
5. **Lazy Loading**: Dynamic input system with minimal overhead
### User Experience Features
1. **Operation Mode Indicator**: Visual feedback in node title
2. **Parameter Tooltips**: Context-sensitive help text
3. **Credit Cost Preview**: Show estimated cost before execution
4. **Progress Indicators**: Real-time processing feedback
5. **Smart Parameter Preservation**: Maintain compatible settings when switching operations
This architecture provides a solid foundation for implementing the unified Reve API integration node with all required functionality while maintaining ComfyUI best practices and user experience standards.
+133
View File
@@ -0,0 +1,133 @@
# Verbose Logging Example
This document demonstrates how to use the verbose logging feature in the ComfyUI Reve API node.
## Basic Usage
1. **Add the Reve API node** to your workflow from the `image/ai` category
2. **Set the operation** to your desired type (create, edit, or remix)
3. **Enable verbose logging** by setting the `verbose_logging` parameter to `True`
4. **Configure your other parameters** as needed
5. **Execute the workflow** and check the ComfyUI console for detailed logs
## Example Workflow Setup
### Create Operation with Verbose Logging
```
[Text Input] → prompt: "A beautiful sunset over mountains"
[Dropdown] → operation: "create"
[Dropdown] → aspect_ratio: "16:9"
[Checkbox] → verbose_logging: True
```
### Expected Console Output
When you run this workflow, you'll see detailed logging in the ComfyUI console:
```
2025-09-16 12:38:26,317 - nodes.reve_api_node.verbose - INFO - 🚀 Starting Reve API CREATE operation
{
"prompt_length": 35,
"operation": "create",
"version": "latest",
"aspect_ratio": "16:9",
"output_format": "png",
"has_reference_image": false,
"reference_images_count": 0
}
2025-09-16 12:38:28,842 - nodes.reve_api_node.verbose - INFO - 🔍 Reve API CREATE Operation Complete
{
"operation": "create",
"request_id": "req-abc123def456",
"credits_used": 18,
"credits_remaining": 482,
"processing_time_seconds": 2.525,
"success": true,
"request_parameters": {
"prompt_length": 35,
"version": "latest",
"aspect_ratio": "16:9",
"has_reference_image": false,
"reference_images_count": 0
}
}
2025-09-16 12:38:28,845 - nodes.reve_api_node.verbose - INFO - ✅ Reve API CREATE operation completed successfully
{
"total_processing_time_seconds": 2.528,
"api_call_time_seconds": 2.525,
"image_processing_time_seconds": 0.003
}
```
## Understanding the Log Output
### Operation Start Log
- **prompt_length**: Number of characters in your prompt
- **operation**: The type of operation being performed
- **version**: API version being used
- **aspect_ratio**: Selected aspect ratio
- **output_format**: Image format
- **has_reference_image**: Whether a reference image is provided
- **reference_images_count**: Number of reference images (for remix)
### Operation Complete Log
- **request_id**: Unique identifier for tracking this API call
- **credits_used**: Number of credits consumed by this operation
- **credits_remaining**: Your remaining credit balance
- **processing_time_seconds**: Time taken by the API to process the request
- **success**: Whether the operation completed successfully
- **request_parameters**: Summary of the parameters sent to the API
### Performance Summary Log
- **total_processing_time_seconds**: Total time from start to finish
- **api_call_time_seconds**: Time spent waiting for the API response
- **image_processing_time_seconds**: Time spent converting and processing the image
## Error Logging Example
If an error occurs, you'll see detailed error information:
```
2025-09-16 12:40:15,123 - nodes.reve_api_node.verbose - INFO - ❌ Reve API CREATE operation failed
{
"error_type": "ReveAuthenticationError",
"error_message": "Invalid API key",
"operation": "create",
"time_to_failure_seconds": 0.245
}
```
## Use Cases for Verbose Logging
### 1. Debugging API Issues
Enable verbose logging when troubleshooting API connectivity or authentication problems.
### 2. Performance Optimization
Monitor processing times to identify bottlenecks in your workflows.
### 3. Credit Management
Track credit usage across different operations to optimize costs.
### 4. Workflow Documentation
Create detailed logs of your workflow executions for documentation or auditing.
### 5. Support and Troubleshooting
Provide detailed logs when seeking support for issues.
## Best Practices
1. **Enable only when needed**: Keep verbose logging disabled for production workflows to reduce console clutter
2. **Monitor credit usage**: Use the credit tracking information to manage your API usage
3. **Performance analysis**: Use timing information to optimize your workflows
4. **Error diagnosis**: Enable verbose logging when experiencing issues for better error information
## Performance Impact
- **Disabled (default)**: No performance impact
- **Enabled**: Minimal overhead (~1-5ms per operation for logging)
- **Log processing**: Structured JSON formatting adds negligible processing time
The verbose logging feature is designed to have minimal impact on your workflow performance while providing maximum insight into the API operations.
+1
View File
@@ -0,0 +1 @@
# ComfyUI Reve API Integration - Nodes Module
+398
View File
@@ -0,0 +1,398 @@
"""
Unified Reve API Node for ComfyUI
This module implements a single unified node that handles all three Reve API operations
(Create, Edit, Remix) with dynamic inputs based on the selected operation type.
"""
import os
import sys
import logging
import time
import json
from typing import Dict, Any, List, Tuple, Optional
# Add the parent directory to the path to import our modules
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
sys.path.insert(0, parent_dir)
try:
from core.reve_api_client import ReveAPIClient, ReveResponse, ReveError
from core.image_utils import (
prepare_image_for_api,
prepare_multiple_images_for_api,
base64_to_tensor,
ImageConversionError
)
except ImportError as e:
print(f"Failed to import Reve API modules: {e}")
raise
import torch
# Configure logging
logger = logging.getLogger(__name__)
# Configure verbose logging for Reve API operations
verbose_logger = logging.getLogger(f"{__name__}.verbose")
verbose_logger.setLevel(logging.INFO)
# Create formatter for verbose logs
verbose_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Only add handler if it doesn't exist to avoid duplicates
if not verbose_logger.handlers:
# Create console handler for verbose logging
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(verbose_formatter)
verbose_logger.addHandler(console_handler)
# Prevent propagation to avoid duplicate logs
verbose_logger.propagate = False
class ReveAPI:
"""
Unified Reve API node supporting Create, Edit, and Remix operations
with dynamic input system and consistent output handling.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"operation": (["create", "edit", "remix"], {
"default": "create",
"tooltip": "Select the Reve API operation to perform"
}),
"prompt": ("STRING", {
"multiline": True,
"default": "A beautiful landscape",
"tooltip": "Text description for the operation"
}),
"api_key": ("STRING", {
"default": "",
"tooltip": "Reve API key - paste your API key here"
})
},
"optional": {
"version": (["latest", "reve-create@20250915", "reve-edit@20250915", "reve-remix@20250915"], {
"default": "latest",
"tooltip": "Model version to use"
}),
"aspect_ratio": (["16:9", "9:16", "3:2", "2:3", "4:3", "3:4", "1:1"], {
"default": "3:2",
"tooltip": "Output image aspect ratio (for Create and Remix operations)"
}),
"output_format": (["png", "jpeg", "webp"], {
"default": "png",
"tooltip": "Output image format"
}),
"reference_image": ("IMAGE", {
"tooltip": "Reference image for Edit operation"
}),
"reference_images": ("IMAGE", {
"tooltip": "Reference images for Remix operation (1-4 images)"
}),
"verbose_logging": ("BOOLEAN", {
"default": False,
"tooltip": "Enable verbose logging for API interactions, performance metrics, and debugging"
})
}
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("image", "metadata")
FUNCTION = "process_request"
CATEGORY = "image/ai"
DESCRIPTION = "Unified Reve API node for Create, Edit, and Remix operations with dynamic inputs"
def __init__(self):
self._client = None
def _log_verbose(self, message: str, data: Dict[str, Any] = None, verbose_enabled: bool = False):
"""Log verbose information if verbose logging is enabled."""
if verbose_enabled:
if data:
formatted_data = json.dumps(data, indent=2, default=str)
verbose_logger.info(f"{message}\n{formatted_data}")
else:
verbose_logger.info(message)
def _log_api_interaction(self, operation: str, request_params: Dict[str, Any],
response: 'ReveResponse', processing_time: float, verbose_enabled: bool = False):
"""Log detailed API interaction information."""
if not verbose_enabled:
return
interaction_data = {
"operation": operation,
"request_id": response.request_id,
"credits_used": response.credits_used,
"credits_remaining": response.credits_remaining,
"processing_time_seconds": round(processing_time, 3),
"success": response.success,
"request_parameters": {
"prompt_length": len(request_params.get("prompt", "")),
"version": request_params.get("version", "unknown"),
"aspect_ratio": request_params.get("aspect_ratio", "unknown"),
"has_reference_image": "reference_image" in request_params,
"reference_images_count": len(request_params.get("reference_images", [])) if "reference_images" in request_params else 0
}
}
if response.error_message:
interaction_data["error"] = {
"code": response.error_code,
"message": response.error_message
}
self._log_verbose(f"🔍 Reve API {operation.upper()} Operation Complete", interaction_data, verbose_enabled)
def _get_client(self, api_key: str = "") -> ReveAPIClient:
"""Get or create API client instance."""
if self._client is None:
self._client = ReveAPIClient(api_key=api_key if api_key else None)
return self._client
def _validate_inputs(self, operation: str, **kwargs) -> Dict[str, Any]:
"""Validate inputs based on operation type."""
errors = []
if operation == "edit":
if "reference_image" not in kwargs or kwargs["reference_image"] is None:
errors.append("Edit operation requires a reference_image")
elif operation == "remix":
if "reference_images" not in kwargs or kwargs["reference_images"] is None:
errors.append("Remix operation requires reference_images")
elif hasattr(kwargs["reference_images"], "shape"):
# Check if it's a batch of images
batch_size = kwargs["reference_images"].shape[0]
if batch_size < 1 or batch_size > 4:
errors.append(f"Remix operation requires 1-4 reference images, got {batch_size}")
if errors:
raise ValueError("; ".join(errors))
return kwargs
def _prepare_create_request(self, prompt: str, aspect_ratio: str, version: str,
output_format: str) -> Dict[str, Any]:
"""Prepare request parameters for Create operation."""
return {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"version": version,
"output_format": output_format
}
def _prepare_edit_request(self, prompt: str, reference_image: torch.Tensor,
version: str, output_format: str) -> Dict[str, Any]:
"""Prepare request parameters for Edit operation."""
try:
# Convert reference image to base64
base64_image = prepare_image_for_api(reference_image, output_format)
return {
"prompt": prompt, # This will be converted to 'edit_instruction' in the API client
"reference_image": base64_image,
"version": version
}
except ImageConversionError as e:
raise ValueError(f"Failed to prepare reference image: {str(e)}")
def _prepare_remix_request(self, prompt: str, reference_images: torch.Tensor,
aspect_ratio: str, version: str, output_format: str) -> Dict[str, Any]:
"""Prepare request parameters for Remix operation."""
try:
# Handle batch of images
if reference_images.dim() == 4:
# Split batch into individual images
image_list = [reference_images[i:i+1] for i in range(reference_images.shape[0])]
else:
# Single image
image_list = [reference_images.unsqueeze(0)]
# Convert all images to base64
base64_images = prepare_multiple_images_for_api(image_list, output_format)
return {
"prompt": prompt,
"reference_images": base64_images,
"aspect_ratio": aspect_ratio,
"version": version
}
except ImageConversionError as e:
raise ValueError(f"Failed to prepare reference images: {str(e)}")
def _process_response(self, response: ReveResponse) -> Tuple[torch.Tensor, str]:
"""Process API response and convert to ComfyUI outputs."""
if not response.success:
error_msg = f"Reve API Error: {response.error_message}"
if response.error_code:
error_msg = f"{response.error_code}: {response.error_message}"
raise RuntimeError(error_msg)
try:
# Convert base64 image to tensor
if response.image:
image_tensor = base64_to_tensor(response.image)
else:
raise ValueError("No image data in response")
# Prepare metadata string (enhanced with additional information)
metadata = {
"operation": response.metadata.get("operation", "unknown") if response.metadata else "unknown",
"model_version": response.metadata.get("model_version", "unknown") if response.metadata else "unknown",
"processing_time": response.metadata.get("processing_time", 0) if response.metadata else 0,
"parameters": response.metadata.get("parameters", {}) if response.metadata else {},
"request_id": response.request_id or "unknown",
"success": response.success
}
metadata_str = json.dumps(metadata, indent=2)
return (
image_tensor,
metadata_str
)
except Exception as e:
raise RuntimeError(f"Failed to process response: {str(e)}")
def process_request(self, operation: str, prompt: str, api_key: str,
version: str = "latest", aspect_ratio: str = "3:2",
output_format: str = "png", reference_image: torch.Tensor = None,
reference_images: torch.Tensor = None, verbose_logging: bool = False) -> Tuple[torch.Tensor, str]:
"""
Process Reve API request based on operation type.
Args:
operation: Operation type (create, edit, remix)
prompt: Text description/instruction
api_key: Reve API key (required)
version: Model version
aspect_ratio: Output aspect ratio
output_format: Output format
reference_image: Single reference image for edit
reference_images: Multiple reference images for remix
verbose_logging: Enable verbose logging for debugging and monitoring
Returns:
Tuple of (image_tensor, metadata_str)
Note:
When verbose_logging is enabled, additional information including request_id,
credits_used, credits_remaining, and performance metrics are logged to the console.
"""
try:
# Validate API key is provided
if not api_key or api_key.strip() == "":
raise ValueError("API key is required. Please paste your Reve API key in the api_key field.")
# Log start of operation if verbose logging is enabled
start_time = time.time()
self._log_verbose(f"🚀 Starting Reve API {operation.upper()} operation", {
"prompt_length": len(prompt),
"operation": operation,
"version": version,
"aspect_ratio": aspect_ratio,
"output_format": output_format,
"has_reference_image": reference_image is not None,
"reference_images_count": reference_images.shape[0] if reference_images is not None else 0
}, verbose_logging)
# Validate inputs based on operation
inputs = {
"reference_image": reference_image,
"reference_images": reference_images
}
self._validate_inputs(operation, **inputs)
# Get API client
client = self._get_client(api_key)
# Prepare request based on operation type
api_start_time = time.time()
request_params = {}
if operation == "create":
request_params = {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"version": version
}
response = client.create(**request_params)
elif operation == "edit":
request_params = self._prepare_edit_request(
prompt, reference_image, version, output_format
)
response = client.edit(**request_params)
elif operation == "remix":
request_params = self._prepare_remix_request(
prompt, reference_images, aspect_ratio, version, output_format
)
response = client.remix(**request_params)
else:
raise ValueError(f"Unsupported operation: {operation}")
# Calculate processing time
processing_time = time.time() - api_start_time
total_time = time.time() - start_time
# Log API interaction details
self._log_api_interaction(operation, request_params, response, processing_time, verbose_logging)
# Log completion summary
self._log_verbose(f"✅ Reve API {operation.upper()} operation completed successfully", {
"total_processing_time_seconds": round(total_time, 3),
"api_call_time_seconds": round(processing_time, 3),
"image_processing_time_seconds": round(total_time - processing_time, 3)
}, verbose_logging)
# Process and return response
return self._process_response(response)
except Exception as e:
# Calculate error time for verbose logging
error_time = time.time() - start_time if 'start_time' in locals() else 0
# Log error details if verbose logging is enabled
self._log_verbose(f"❌ Reve API {operation.upper()} operation failed", {
"error_type": type(e).__name__,
"error_message": str(e),
"operation": operation,
"time_to_failure_seconds": round(error_time, 3)
}, verbose_logging)
logger.error(f"Reve API request failed: {str(e)}")
# Provide user-friendly error messages for common issues
error_str = str(e)
if "API_NOT_AVAILABLE" in error_str or "ENDPOINT_NOT_FOUND" in error_str:
raise RuntimeError(
"❌ Reve API is not currently available.\n\n"
"The Reve API endpoints are not accessible. This could be because:\n"
"• The API is still in development\n"
"• The API requires special access or activation\n"
"• The API endpoints have changed\n\n"
"Please visit https://api.reve.com or contact Reve support for the latest API status.\n"
"You can use the Reve web interface at https://app.reve.com in the meantime."
)
else:
raise RuntimeError(f"Reve API request failed: {str(e)}")
def __del__(self):
"""Cleanup API client on node destruction."""
if self._client:
try:
self._client.close()
except:
pass
+16
View File
@@ -0,0 +1,16 @@
# ComfyUI Reve API Integration Dependencies
# HTTP client for API requests
requests>=2.28.0
# Image processing (should be available in ComfyUI environment)
Pillow>=9.0.0
# NumPy for array operations (should be available in ComfyUI environment)
numpy>=1.21.0
# PyTorch (should be available in ComfyUI environment)
# torch>=1.13.0
# Optional: For enhanced error handling and validation
# pydantic>=1.10.0
+1
View File
@@ -0,0 +1 @@
# ComfyUI Reve API Integration - Tests Module
+411
View File
@@ -0,0 +1,411 @@
{
"id": "a42eb435-91b1-4ff3-b0ed-f6a889436939",
"revision": 0,
"last_node_id": 14,
"last_link_id": 34,
"nodes": [
{
"id": 6,
"type": "LoadImage",
"pos": [
-604.8377075195312,
749.3304443359375
],
"size": [
270,
314
],
"flags": {},
"order": 0,
"mode": 4,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
19
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"enableTabs": false,
"tabWidth": 65,
"tabXOffset": 10,
"hasSecondTab": false,
"secondTabText": "Send Back",
"secondTabOffset": 80,
"secondTabWidth": 65,
"cnr_id": "comfy-core",
"ver": "0.3.59",
"Node name for S&R": "LoadImage",
"ue_properties": {
"version": "7.0.1",
"widget_ue_connectable": {
"image": true,
"upload": true
}
}
},
"widgets_values": [
"nogaruka._Photo_round_wooden_font_in_a_bathhouse_with_a_lot_of__1989a698-e36c-4e11-b885-24c2e3ccfda5.png",
"image"
]
},
{
"id": 8,
"type": "LoadImage",
"pos": [
-314.837890625,
899.3301391601562
],
"size": [
270,
314.00006103515625
],
"flags": {},
"order": 1,
"mode": 4,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
20
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"enableTabs": false,
"tabWidth": 65,
"tabXOffset": 10,
"hasSecondTab": false,
"secondTabText": "Send Back",
"secondTabOffset": 80,
"secondTabWidth": 65,
"cnr_id": "comfy-core",
"ver": "0.3.59",
"Node name for S&R": "LoadImage",
"ue_properties": {
"version": "7.0.1",
"widget_ue_connectable": {
"image": true,
"upload": true
}
}
},
"widgets_values": [
"eclissidilana_65569_a_very_realistic_beautiful_night_flash_film_d631d5c6-f601-4d1e-b61a-af4352aab2de (2).png",
"image"
]
},
{
"id": 13,
"type": "LoadImage",
"pos": [
-309.6622314453125,
333.55999755859375
],
"size": [
270,
314.0000305175781
],
"flags": {},
"order": 2,
"mode": 4,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
32
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"enableTabs": false,
"tabWidth": 65,
"tabXOffset": 10,
"hasSecondTab": false,
"secondTabText": "Send Back",
"secondTabOffset": 80,
"secondTabWidth": 65,
"cnr_id": "comfy-core",
"ver": "0.3.59",
"Node name for S&R": "LoadImage",
"ue_properties": {
"version": "7.0.1"
}
},
"widgets_values": [
"yo9otatara_A_portrait_of_an_bald_man_wearing_a_white_turtlene_5e874a40-107e-4611-b5b5-6edc4873378b_0.png",
"image"
]
},
{
"id": 10,
"type": "ImageConcanate",
"pos": [
-314.837890625,
749.3304443359375
],
"size": [
270,
102
],
"flags": {},
"order": 3,
"mode": 4,
"inputs": [
{
"name": "image1",
"type": "IMAGE",
"link": 19
},
{
"name": "image2",
"type": "IMAGE",
"link": 20
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
33
]
}
],
"properties": {
"enableTabs": false,
"tabWidth": 65,
"tabXOffset": 10,
"hasSecondTab": false,
"secondTabText": "Send Back",
"secondTabOffset": 80,
"secondTabWidth": 65,
"cnr_id": "comfyui-kjnodes",
"ver": "1.1.4",
"Node name for S&R": "ImageConcanate",
"ue_properties": {
"version": "7.0.1"
}
},
"widgets_values": [
"right",
true
]
},
{
"id": 3,
"type": "PreviewImage",
"pos": [
463.3653564453125,
435.236572265625
],
"size": [
498.1344909667969,
372.80816650390625
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 34
}
],
"outputs": [],
"properties": {
"enableTabs": false,
"tabWidth": 65,
"tabXOffset": 10,
"hasSecondTab": false,
"secondTabText": "Send Back",
"secondTabOffset": 80,
"secondTabWidth": 65,
"cnr_id": "comfy-core",
"ver": "0.3.59",
"Node name for S&R": "PreviewImage",
"ue_properties": {
"version": "7.0.1",
"widget_ue_connectable": {}
}
},
"widgets_values": []
},
{
"id": 14,
"type": "ReveAPI",
"pos": [
-5.167802333831787,
457.3710632324219
],
"size": [
408.34271240234375,
544.4044189453125
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "reference_image",
"shape": 7,
"type": "IMAGE",
"link": 32
},
{
"name": "reference_images",
"shape": 7,
"type": "IMAGE",
"link": 33
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
34
]
},
{
"name": "metadata",
"type": "STRING",
"links": null
}
],
"properties": {
"enableTabs": false,
"tabWidth": 65,
"tabXOffset": 10,
"hasSecondTab": false,
"secondTabText": "Send Back",
"secondTabOffset": 80,
"secondTabWidth": 65,
"Node name for S&R": "ReveAPI",
"ue_properties": {
"version": "7.0.1"
}
},
"widgets_values": [
"create",
"macro photograph of a human eye with a green iris, golden flecks, and delicate sunflower petals integrated seamlessly inside the pupil, surrounding eyelid adorned with soft pastel makeup, moist realistic skin detail, cinematic lighting, no text or symbols.",
"papi.2a668e8b-9fa1-472e-b773-816095cea5d7.N5jZ6CUA_0_TmyB-c7a3Dr9fATWuvZtN",
"latest",
"3:2",
"png",
true
]
}
],
"links": [
[
19,
6,
0,
10,
0,
"IMAGE"
],
[
20,
8,
0,
10,
1,
"IMAGE"
],
[
32,
13,
0,
14,
0,
"IMAGE"
],
[
33,
10,
0,
14,
1,
"IMAGE"
],
[
34,
14,
0,
3,
0,
"IMAGE"
]
],
"groups": [
{
"id": 1,
"title": "Remix+",
"bounding": [
-614.8377075195312,
679.3304443359375,
580,
547.5999755859375
],
"color": "#3f789e",
"font_size": 24,
"flags": {}
},
{
"id": 2,
"title": "Edit+",
"bounding": [
-319.662109375,
259.9598693847656,
290,
397.6000061035156
],
"color": "#3f789e",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ue_links": [],
"links_added_by_ue": [],
"ds": {
"scale": 1.0152559799477145,
"offset": [
570.2145821362822,
-28.935737510802966
]
},
"frontendVersion": "1.26.11",
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}