Added SaveImage node output marking feature, supporting variable naming through node titles to distinguish multiple outputs, optimized response format and provided more user-friendly documentation.
This commit is contained in:
@@ -1,54 +1,119 @@
|
||||
# ComfyUI-OneAPI
|
||||
# ComfyUI-OneAPI ✨
|
||||
|
||||
ComfyUI-OneAPI is a plugin that provides simple REST API interfaces for ComfyUI, allowing you to execute ComfyUI workflows through a single API request.
|
||||
ComfyUI-OneAPI is a plugin that provides simple REST API interfaces for ComfyUI, allowing you to execute complex ComfyUI workflows through a single API request.
|
||||
|
||||
[中文文档](README_CN.md)
|
||||
|
||||
## Features
|
||||
## ⚡️ Quick Start
|
||||
|
||||
- **Simple Interface**: Execute complex ComfyUI workflows through a single API endpoint
|
||||
- **Parameter Mapping**: Support for dynamically replacing parameters in workflows
|
||||
- **Asynchronous Execution**: Support for both synchronous and asynchronous execution modes
|
||||
- **No WebSocket Dependency**: Uses HTTP polling to get results, no need to handle WebSocket connections
|
||||
### 🚀 Execute Workflow with Just One Request
|
||||
|
||||
## Installation
|
||||
```bash
|
||||
curl -X POST "http://localhost:8188/oneapi/v1/execute" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workflow": {...} # Replace with your workflow JSON
|
||||
}'
|
||||
```
|
||||
|
||||
### 📝 Simplest Request Format
|
||||
|
||||
```json
|
||||
{
|
||||
"workflow": {...} // API version of workflow JSON
|
||||
}
|
||||
```
|
||||
|
||||
### 📤 Common Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "completed",
|
||||
"images": ["http://server/image1.png", "http://server/image2.png"]
|
||||
}
|
||||
```
|
||||
|
||||
## 🔥 Advanced Usage
|
||||
|
||||
### 1️⃣ Dynamic Parameter Replacement - No More Workflow Edits 🔄
|
||||
|
||||
Add markers in node titles to easily replace parameters:
|
||||
|
||||
```json
|
||||
// Request
|
||||
{
|
||||
"workflow": {...},
|
||||
"params": {
|
||||
"prompt": "cute cat",
|
||||
"input_image": "https://example.com/image.jpg"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**✨ How to Mark Nodes:**
|
||||
- 📝 Text Prompt: Add `$prompt.text` to CLIPTextEncode node title
|
||||
- 🖼️ Input Image: Add `$input_image` to LoadImage node title
|
||||
|
||||
### 2️⃣ Distinguish Multiple Outputs - Handle Complex Workflows 🧩
|
||||
|
||||
When your workflow has multiple SaveImage nodes, easily distinguish different outputs:
|
||||
|
||||
```json
|
||||
// Response
|
||||
{
|
||||
"status": "completed",
|
||||
"images": ["http://server/image1.png", "http://server/image2.png"],
|
||||
"images_by_var": {
|
||||
"background": ["http://server/image1.png"],
|
||||
"character": ["http://server/image2.png"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**✨ How to Mark Output Nodes:**
|
||||
- 💾 Add `$output.background` or `$output.character` to SaveImage node titles
|
||||
|
||||
## 📋 Advanced Features
|
||||
|
||||
### 📦 Installation
|
||||
|
||||
1. Download or clone this repository to the `custom_nodes` directory of ComfyUI
|
||||
2. Restart ComfyUI
|
||||
|
||||
## API Usage
|
||||
|
||||
### Execute Workflow
|
||||
### 🔌 API Parameters
|
||||
|
||||
```
|
||||
POST /oneapi/v1/execute
|
||||
|
||||
Request Body:
|
||||
{
|
||||
"workflow": {Workflow JSON},
|
||||
"params": {
|
||||
"paramName": "paramValue"
|
||||
}
|
||||
"workflow": {...}, // API version of workflow JSON
|
||||
"params": {...}, // Optional: Parameter mapping
|
||||
"wait_for_result": true/false, // Optional: Wait for results (default true)
|
||||
"timeout": 300 // Optional: Timeout in seconds
|
||||
}
|
||||
```
|
||||
|
||||
## Workflow Parameter Mapping
|
||||
### 🏷️ Node Title Marker Rules
|
||||
|
||||
To use dynamic parameters in your workflow, you need to use special markers in the node titles:
|
||||
#### ⬇️ Input Parameter Markers
|
||||
|
||||
1. LoadImage node: Use `$image_param` format
|
||||
2. Other nodes: Use `$param.field_name` format
|
||||
1. 🖼️ LoadImage node: Use `$image_param` format
|
||||
2. 🔄 Other nodes: Use `$param.field_name` format
|
||||
|
||||
Examples:
|
||||
- `$input_image` - LoadImage node uses params.input_image as the image
|
||||
- `$prompt.text` - Replaces text field with params.prompt
|
||||
|
||||
- A LoadImage node marked with `$input_image` will use the input_image parameter from params as the image param
|
||||
- A CLIPTextEncode node marked with `$prompt.text` will replace the text field with the prompt parameter from params
|
||||
#### ⬆️ Output Markers
|
||||
|
||||
## Examples
|
||||
Add markers to SaveImage node titles:
|
||||
- Format: `$output.name` (e.g., `$output.background`)
|
||||
- Without markers, node ID is used as the variable name
|
||||
|
||||
### curl Examples
|
||||
## 🔍 Examples
|
||||
|
||||
#### Text-to-Image Example
|
||||
### 📝 Text-to-Image Example
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8188/oneapi/v1/execute" \
|
||||
@@ -61,10 +126,9 @@ curl -X POST "http://localhost:8188/oneapi/v1/execute" \
|
||||
}'
|
||||
```
|
||||
|
||||
#### Image-to-Image Example
|
||||
### 🖼️ Image-to-Image Example
|
||||
|
||||
```bash
|
||||
# Execute image-to-image workflow
|
||||
curl -X POST "http://localhost:8188/oneapi/v1/execute" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -76,8 +140,8 @@ curl -X POST "http://localhost:8188/oneapi/v1/execute" \
|
||||
}'
|
||||
```
|
||||
|
||||
## Notes
|
||||
## ⚠️ Notes
|
||||
|
||||
- This plugin is designed to simplify API calls, and does not provide WebSocket real-time progress information
|
||||
- Long-running workflows may cause request timeouts, consider setting appropriate timeout values
|
||||
- Parameter mapping in workflows depends on special markers in node titles
|
||||
- 🔄 This plugin uses HTTP polling to get results, does not provide WebSocket real-time progress
|
||||
- ⏱️ Long-running workflows may cause request timeouts, consider setting appropriate timeout values
|
||||
- 🏷️ Parameter mapping and output marking depend on special markers in node titles
|
||||
+95
-31
@@ -1,54 +1,119 @@
|
||||
# ComfyUI-OneAPI
|
||||
# ComfyUI-OneAPI ✨
|
||||
|
||||
ComfyUI-OneAPI 是一个为 ComfyUI 提供简单REST API接口的插件,使您能够通过一个简单的API请求执行ComfyUI工作流。
|
||||
ComfyUI-OneAPI 是一个为 ComfyUI 提供简单REST API接口的插件,只需一个API请求即可执行复杂的ComfyUI工作流。
|
||||
|
||||
[English Documentation](README.md)
|
||||
|
||||
## 特点
|
||||
## ⚡️ 快速开始
|
||||
|
||||
- **简单接口**:通过单个API接口执行复杂的ComfyUI工作流
|
||||
- **参数映射**:支持动态替换工作流中的参数
|
||||
- **异步执行**:支持同步或异步执行模式
|
||||
- **无WebSocket依赖**:使用HTTP轮询获取结果,不需要处理WebSocket连接
|
||||
### 🚀 仅需一个请求即可执行工作流
|
||||
|
||||
## 安装
|
||||
```bash
|
||||
curl -X POST "http://localhost:8188/oneapi/v1/execute" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workflow": {...} # 替换为您的工作流JSON
|
||||
}'
|
||||
```
|
||||
|
||||
### 📝 最简单的请求格式
|
||||
|
||||
```json
|
||||
{
|
||||
"workflow": {...} // 工作流的API版JSON
|
||||
}
|
||||
```
|
||||
|
||||
### 📤 常见响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "completed",
|
||||
"images": ["http://server/image1.png", "http://server/image2.png"]
|
||||
}
|
||||
```
|
||||
|
||||
## 🔥 进阶用法
|
||||
|
||||
### 1️⃣ 动态替换参数 - 告别工作流修改 🔄
|
||||
|
||||
在节点标题中添加标记,轻松替换参数:
|
||||
|
||||
```json
|
||||
// 请求
|
||||
{
|
||||
"workflow": {...},
|
||||
"params": {
|
||||
"prompt": "cute cat",
|
||||
"input_image": "https://example.com/image.jpg"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**✨ 如何标记节点:**
|
||||
- 📝 文本提示:在CLIPTextEncode节点标题添加 `$prompt.text`
|
||||
- 🖼️ 输入图像:在LoadImage节点标题添加 `$input_image`
|
||||
|
||||
### 2️⃣ 区分多个输出 - 处理复杂工作流 🧩
|
||||
|
||||
当工作流有多个SaveImage节点时,轻松区分不同输出:
|
||||
|
||||
```json
|
||||
// 响应
|
||||
{
|
||||
"status": "completed",
|
||||
"images": ["http://server/image1.png", "http://server/image2.png"],
|
||||
"images_by_var": {
|
||||
"background": ["http://server/image1.png"],
|
||||
"character": ["http://server/image2.png"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**✨ 如何标记输出节点:**
|
||||
- 💾 在SaveImage节点标题添加 `$output.background` 或 `$output.character`
|
||||
|
||||
## 📋 高级功能
|
||||
|
||||
### 📦 安装
|
||||
|
||||
1. 下载或克隆此仓库到ComfyUI的`custom_nodes`目录
|
||||
2. 重启ComfyUI
|
||||
|
||||
## API使用
|
||||
|
||||
### 执行工作流
|
||||
### 🔌 API参数详解
|
||||
|
||||
```
|
||||
POST /oneapi/v1/execute
|
||||
|
||||
请求体:
|
||||
{
|
||||
"workflow": {工作流JSON},
|
||||
"params": {
|
||||
"参数名": "参数值"
|
||||
}
|
||||
"workflow": {...}, // 工作流的API版JSON
|
||||
"params": {...}, // 可选:参数映射
|
||||
"wait_for_result": true/false, // 可选:是否等待结果(默认true)
|
||||
"timeout": 300 // 可选:超时时间(秒)
|
||||
}
|
||||
```
|
||||
|
||||
## 工作流参数映射
|
||||
### 🏷️ 节点标题标记规则
|
||||
|
||||
要在工作流中使用动态参数,需要在节点标题中使用特殊标记:
|
||||
#### ⬇️ 输入参数标记
|
||||
|
||||
1. LoadImage节点:使用 `$image_param` 格式
|
||||
2. 其他节点:使用 `$param.field_name` 格式
|
||||
1. 🖼️ LoadImage节点:使用 `$image_param` 格式
|
||||
2. 🔄 其他节点:使用 `$param.field_name` 格式
|
||||
|
||||
例如:
|
||||
示例:
|
||||
- `$input_image` - LoadImage节点使用params.input_image作为图片
|
||||
- `$prompt.text` - 使用params.prompt替换text字段
|
||||
|
||||
- 标记为 `$input_image` 的LoadImage节点,将使用params中的input_image参数作为图片参数
|
||||
- 标记为 `$prompt.text` 的CLIPTextEncode节点,将使用params中的prompt参数替换text字段
|
||||
#### ⬆️ 输出标记
|
||||
|
||||
## 示例
|
||||
在SaveImage节点标题中添加标记:
|
||||
- 格式:`$output.name`(如:`$output.background`)
|
||||
- 无标记时使用节点ID作为标识
|
||||
|
||||
### curl示例
|
||||
## 🔍 示例
|
||||
|
||||
#### 文生图示例
|
||||
### 📝 文生图示例
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8188/oneapi/v1/execute" \
|
||||
@@ -61,10 +126,9 @@ curl -X POST "http://localhost:8188/oneapi/v1/execute" \
|
||||
}'
|
||||
```
|
||||
|
||||
#### 图生图示例
|
||||
### 🖼️ 图生图示例
|
||||
|
||||
```bash
|
||||
# 执行图生图工作流
|
||||
curl -X POST "http://localhost:8188/oneapi/v1/execute" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -76,8 +140,8 @@ curl -X POST "http://localhost:8188/oneapi/v1/execute" \
|
||||
}'
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
## ⚠️ 注意事项
|
||||
|
||||
- 此插件设计为简化API调用,不提供WebSocket实时进度信息
|
||||
- 超长时间的工作流可能导致请求超时,建议设置合理的超时时间
|
||||
- 工作流中的参数映射依赖于节点标题中的特殊标记
|
||||
- 🔄 此插件使用HTTP轮询获取结果,不提供WebSocket实时进度
|
||||
- ⏱️ 长时间运行的工作流可能导致超时,请设置合适的timeout值
|
||||
- 🏷️ 参数映射和输出标记依赖于节点标题中的特殊标记
|
||||
@@ -27,7 +27,19 @@ async def execute_workflow(request):
|
||||
- timeout: Timeout in seconds (default 300)
|
||||
|
||||
Returns:
|
||||
- Workflow execution results
|
||||
- Return values:
|
||||
- status: Status (queued, processing, completed, timeout)
|
||||
- prompt_id: Prompt ID
|
||||
- images: List of image URLs [string, ...]
|
||||
- images_by_var: Mapped image URLs by variable name {var_name: [string, ...], ...}
|
||||
|
||||
Node title markers:
|
||||
- Input: Use "$param.field" in node title to map parameter values
|
||||
- Output: Use "$output" or "$output.name" in SaveImage node title to specify outputs
|
||||
- "$output.name" - Marks a SaveImage node with a custom output name (added to "images_by_var[name]")
|
||||
- If no explicit output marker is set, the node_id is used as the variable name
|
||||
- Only SaveImage nodes are considered for output
|
||||
- Both images and images_by_var fields are always included in the response
|
||||
"""
|
||||
try:
|
||||
# Get request data
|
||||
@@ -46,6 +58,9 @@ async def execute_workflow(request):
|
||||
if params:
|
||||
workflow = await _apply_params_to_workflow(workflow, params)
|
||||
|
||||
# Extract and save output node information
|
||||
output_id_2_var = await _extract_output_nodes(workflow)
|
||||
|
||||
# Generate client ID
|
||||
client_id = str(uuid.uuid4())
|
||||
|
||||
@@ -68,7 +83,7 @@ async def execute_workflow(request):
|
||||
return web.json_response(result)
|
||||
|
||||
# Poll for results
|
||||
result = await _wait_for_results(prompt_id, timeout, request)
|
||||
result = await _wait_for_results(prompt_id, timeout, request, output_id_2_var)
|
||||
return web.json_response(result)
|
||||
|
||||
except Exception as e:
|
||||
@@ -115,6 +130,52 @@ async def _process_node_params(node_data, params):
|
||||
# Process parameter marker
|
||||
await _process_param_marker(node_data, part[1:], params)
|
||||
|
||||
async def _extract_output_nodes(workflow):
|
||||
"""
|
||||
Extract SaveImage nodes and their output variable names from workflow
|
||||
|
||||
Args:
|
||||
workflow: Workflow JSON object
|
||||
|
||||
Returns:
|
||||
Dictionary mapping node_id to output variable name
|
||||
"""
|
||||
output_id_2_var = {}
|
||||
|
||||
for node_id, node_data in workflow.items():
|
||||
# Skip nodes that don't meet criteria
|
||||
if not _is_valid_node(node_data):
|
||||
continue
|
||||
|
||||
# Only process SaveImage nodes
|
||||
if node_data.get('class_type') != 'SaveImage':
|
||||
continue
|
||||
|
||||
# Get node title
|
||||
title = node_data["_meta"]["title"]
|
||||
|
||||
# Check for $output marker in the title
|
||||
output_var = None
|
||||
parts = title.split(',')
|
||||
for part in parts:
|
||||
part = part.strip()
|
||||
if part.startswith('$output'):
|
||||
# Parse output marker
|
||||
if '.' in part:
|
||||
# Format: $output.name - Specify output name
|
||||
output_var = part.split('.', 1)[1]
|
||||
if not output_var:
|
||||
raise Exception(f"Invalid output marker format (empty name): {part}")
|
||||
else:
|
||||
# Simple $output without variable name is not valid
|
||||
raise Exception(f"Invalid output marker format (missing name): {part}. Use $output.name format.")
|
||||
|
||||
# For SaveImage nodes, always register in output_id_2_var
|
||||
# If no explicit marker, use node_id as the variable name
|
||||
output_id_2_var[node_id] = output_var if output_var else str(node_id)
|
||||
|
||||
return output_id_2_var
|
||||
|
||||
async def _process_param_marker(node_data, var_spec, params):
|
||||
"""
|
||||
Process individual parameter marker
|
||||
@@ -322,13 +383,14 @@ async def _get_base_url(request):
|
||||
|
||||
return base_url
|
||||
|
||||
async def _wait_for_results(prompt_id, timeout=None, request=None):
|
||||
async def _wait_for_results(prompt_id, timeout=None, request=None, output_id_2_var=None):
|
||||
"""Wait for workflow execution results, get history using HTTP API"""
|
||||
start_time = time.time()
|
||||
result = {
|
||||
"status": "processing",
|
||||
"prompt_id": prompt_id,
|
||||
"images": []
|
||||
"images": [],
|
||||
"images_by_var": {}
|
||||
}
|
||||
|
||||
# Get base URL for image URLs
|
||||
@@ -365,13 +427,14 @@ async def _wait_for_results(prompt_id, timeout=None, request=None):
|
||||
if "outputs" in prompt_history:
|
||||
result["status"] = "completed"
|
||||
|
||||
# Store original outputs
|
||||
if 'outputs' in result:
|
||||
del result['outputs'] # Remove outputs field
|
||||
# Store all SaveImage node outputs
|
||||
output_id_2_images = {}
|
||||
|
||||
# Process outputs, especially focusing on SaveImage nodes
|
||||
# Process outputs, focusing on nodes with images
|
||||
for node_id, node_output in prompt_history["outputs"].items():
|
||||
if "images" in node_output:
|
||||
# Store image URLs for this node
|
||||
node_images = []
|
||||
for img_data in node_output["images"]:
|
||||
filename = img_data.get("filename")
|
||||
subfolder = img_data.get("subfolder", "")
|
||||
@@ -384,8 +447,25 @@ async def _wait_for_results(prompt_id, timeout=None, request=None):
|
||||
if img_type:
|
||||
img_url += f"&type={img_type}"
|
||||
|
||||
# Simplified: directly add URL string to results
|
||||
result["images"].append(img_url)
|
||||
node_images.append(img_url)
|
||||
|
||||
output_id_2_images[node_id] = node_images
|
||||
|
||||
# Process according to output mapping
|
||||
if output_id_2_var and output_id_2_images:
|
||||
# First pass: handle nodes with explicit mapping
|
||||
for node_id, output_var in output_id_2_var.items():
|
||||
if node_id in output_id_2_images:
|
||||
# Add to images_by_var using the variable name
|
||||
result["images_by_var"][output_var] = output_id_2_images[node_id]
|
||||
|
||||
# For backward compatibility: add all output images to the images array as well
|
||||
for images in output_id_2_images.values():
|
||||
result["images"].extend(images)
|
||||
else:
|
||||
# No mapping or no image outputs, fallback to including all images in the images array
|
||||
for node_images in output_id_2_images.values():
|
||||
result["images"].extend(node_images)
|
||||
|
||||
# Return complete results
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user