让我先搜索一下项目中是否有ComfyUI相关的代码或文档。
toolName: search_codebase
status: success
query: ComfyUI相关代码、接口、API调用
ComfyUI接口详解与集成方案
1. ComfyUI核心接口
1.1 主要API端点
ComfyUI提供以下主要HTTP接口:
| 端点 | 方法 | 功能 | 用途 |
|---|---|---|---|
/prompt |
POST | 提交工作流执行请求 | 触发图片/视频生成 |
/history |
GET | 获取执行历史 | 查询任务状态 |
/view |
GET | 获取生成的图片/视频 | 下载生成的媒体文件 |
/queue |
GET | 获取队列状态 | 查看任务队列 |
/object_info |
GET | 获取节点信息 | 了解可用节点和参数 |
/embeddings |
GET | 获取嵌入模型列表 | 查看可用模型 |
/upload/image |
POST | 上传图片 | 上传输入图片 |
1.2 详细接口说明
1.2.1 提交工作流 (/prompt)
请求示例:
python
import requests
comfyui_url = "http://localhost:8188"
# 提交工作流
payload = {
"prompt": {
"1": {
"class_type": "CheckpointLoaderSimple",
"inputs": {
"ckpt_name": "v1-5-pruned-emaonly.safetensors"
}
},
"2": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "a beautiful sunset",
"clip": ["1", 1]
}
},
"3": {
"class_type": "KSampler",
"inputs": {
"seed": 123456789,
"steps": 20,
"cfg": 7,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"model": ["1", 0],
"positive": ["2", 0],
"negative": ["2", 1]
}
},
"4": {
"class_type": "VAEDecode",
"inputs": {
"samples": ["3", 0],
"vae": ["1", 2]
}
},
"5": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "ComfyUI",
"images": ["4", 0]
}
}
},
"client_id": "client_001"
}
response = requests.post(f"{comfyui_url}/prompt", json=payload)
result = response.json()
# 返回: {"prompt_id": "uuid", "number": 1}
1.2.2 获取执行历史 (/history)
请求示例:
python
# 获取所有历史
response = requests.get(f"{comfyui_url}/history")
history = response.json()
# 获取特定prompt的历史
prompt_id = "your_prompt_id"
response = requests.get(f"{comfyui_url}/history/{prompt_id}")
prompt_history = response.json()
响应格式:
json
{
"prompt_id": {
"prompt": {},
"outputs": {
"5": {
"images": [
{
"filename": "ComfyUI_00001.png",
"subfolder": "",
"type": "output"
}
]
}
}
}
}
1.2.3 获取生成的图片 (/view)
请求示例:
python
# 获取图片
filename = "ComfyUI_00001.png"
subfolder = ""
image_type = "output"
response = requests.get(
f"{comfyui_url}/view",
params={
"filename": filename,
"subfolder": subfolder,
"type": image_type
}
)
# 保存图片
with open("output.png", "wb") as f:
f.write(response.content)
1.2.4 获取队列状态 (/queue)
请求示例:
python
response = requests.get(f"{comfyui_url}/queue")
queue_status = response.json()
# 响应格式
{
"queue_running": [
[1, ["prompt_id", {}], {}]
],
"queue_pending": [
[2, ["prompt_id", {}], {}]
]
}
1.2.5 获取节点信息 (/object_info)
请求示例:
python
response = requests.get(f"{comfyui_url}/object_info")
object_info = response.json()
# 响应包含所有可用节点及其参数
{
"CheckpointLoaderSimple": {
"input": {
"required": {
"ckpt_name": ["STRING", {"default": "model.safetensors"}]
}
},
"output": ["MODEL", "VAE", "CLIP"]
}
}
1.2.6 上传图片 (/upload/image)
请求示例:
python
files = {
"image": ("input.png", open("input.png", "rb"), "image/png"),
"overwrite": "true"
}
response = requests.post(
f"{comfyui_url}/upload/image",
files=files
)
upload_result = response.json()
# 返回: {"name": "input.png", "subfolder": "", "type": "input"}
2. Python Web框架集成方案
2.1 Flask集成示例
python
from flask import Flask, request, jsonify, send_file
import requests
import os
import tempfile
app = Flask(__name__)
COMFYUI_URL = "http://localhost:8188"
@app.route('/api/generate-image', methods=['POST'])
def generate_image():
"""
生成图片接口
"""
data = request.json
# 构建ComfyUI工作流
workflow = build_comfyui_workflow(
prompt=data.get('prompt', ''),
negative_prompt=data.get('negative_prompt', ''),
steps=data.get('steps', 20),
cfg_scale=data.get('cfg_scale', 7),
seed=data.get('seed', -1)
)
# 提交到ComfyUI
try:
comfy_response = requests.post(
f"{COMFYUI_URL}/prompt",
json={"prompt": workflow, "client_id": data.get('client_id', 'default')},
timeout=30
)
if comfy_response.status_code != 200:
return jsonify({"error": "ComfyUI请求失败"}), 500
prompt_result = comfy_response.json()
prompt_id = prompt_result.get('prompt_id')
# 等待生成完成
result = wait_for_completion(prompt_id)
if result.get('status') == 'completed':
# 获取生成的图片
image_info = result['outputs']['images'][0]
image_url = get_image_url(image_info)
# 下载图片
image_response = requests.get(image_url)
# 返回图片
return send_file(
tempfile.NamedTemporaryFile(delete=False),
mimetype='image/png',
as_attachment=True,
download_name=image_info['filename']
)
else:
return jsonify({"error": "生成失败"}), 500
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/generate-video', methods=['POST'])
def generate_video():
"""
生成视频接口
"""
data = request.json
# 构建视频生成工作流
workflow = build_video_workflow(data)
# 提交到ComfyUI
try:
comfy_response = requests.post(
f"{COMFYUI_URL}/prompt",
json={"prompt": workflow, "client_id": data.get('client_id', 'default')},
timeout=60
)
if comfy_response.status_code != 200:
return jsonify({"error": "ComfyUI请求失败"}), 500
prompt_result = comfy_response.json()
prompt_id = prompt_result.get('prompt_id')
# 等待生成完成
result = wait_for_completion(prompt_id)
if result.get('status') == 'completed':
# 获取生成的视频
video_info = result['outputs']['videos'][0]
video_url = get_video_url(video_info)
# 下载视频
video_response = requests.get(video_url)
# 返回视频
return send_file(
tempfile.NamedTemporaryFile(delete=False),
mimetype='video/mp4',
as_attachment=True,
download_name=video_info['filename']
)
else:
return jsonify({"error": "生成失败"}), 500
except Exception as e:
return jsonify({"error": str(e)}), 500
def build_comfyui_workflow(prompt, negative_prompt, steps, cfg_scale, seed):
"""
构建ComfyUI工作流
"""
return {
"1": {
"class_type": "CheckpointLoaderSimple",
"inputs": {
"ckpt_name": "v1-5-pruned-emaonly.safetensors"
}
},
"2": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": prompt,
"clip": ["1", 1]
}
},
"3": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": negative_prompt,
"clip": ["1", 1]
}
},
"4": {
"class_type": "KSampler",
"inputs": {
"seed": seed,
"steps": steps,
"cfg": cfg_scale,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"model": ["1", 0],
"positive": ["2", 0],
"negative": ["3", 0]
}
},
"5": {
"class_type": "VAEDecode",
"inputs": {
"samples": ["4", 0],
"vae": ["1", 2]
}
},
"6": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "generated",
"images": ["5", 0]
}
}
}
def wait_for_completion(prompt_id, max_wait=300):
"""
等待生成完成
"""
import time
start_time = time.time()
while time.time() - start_time < max_wait:
response = requests.get(f"{COMFYUI_URL}/history/{prompt_id}")
history = response.json()
if prompt_id in history:
result = history[prompt_id]
return {
"status": "completed",
"outputs": result.get('outputs', {})
}
time.sleep(2)
return {"status": "timeout"}
def get_image_url(image_info):
"""
获取图片URL
"""
filename = image_info['filename']
subfolder = image_info.get('subfolder', '')
image_type = image_info.get('type', 'output')
return f"{COMFYUI_URL}/view?filename={filename}&subfolder={subfolder}&type={image_type}"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
2.2 FastAPI集成示例
python
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.responses import StreamingResponse
import requests
import asyncio
from typing import Optional
app = FastAPI()
COMFYUI_URL = "http://localhost:8188"
@app.post("/api/generate-image")
async def generate_image(
prompt: str,
negative_prompt: str = "",
steps: int = 20,
cfg_scale: float = 7.0,
seed: int = -1,
client_id: str = "default"
):
"""
生成图片接口(异步)
"""
workflow = build_comfyui_workflow(prompt, negative_prompt, steps, cfg_scale, seed)
try:
# 提交到ComfyUI
comfy_response = await asyncio.to_thread(
requests.post,
f"{COMFYUI_URL}/prompt",
json={"prompt": workflow, "client_id": client_id},
timeout=30
)
if comfy_response.status_code != 200:
raise HTTPException(status_code=500, detail="ComfyUI请求失败")
prompt_result = comfy_response.json()
prompt_id = prompt_result.get('prompt_id')
# 等待生成完成
result = await wait_for_completion_async(prompt_id)
if result['status'] == 'completed':
# 获取图片
image_info = result['outputs']['images'][0]
image_url = get_image_url(image_info)
# 下载图片
image_response = await asyncio.to_thread(requests.get, image_url)
# 返回图片流
return StreamingResponse(
image_response.iter_content(chunk_size=8192),
media_type="image/png"
)
else:
raise HTTPException(status_code=500, detail="生成失败")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/generate-video")
async def generate_video(
prompt: str,
negative_prompt: str = "",
steps: int = 20,
cfg_scale: float = 7.0,
seed: int = -1,
client_id: str = "default"
):
"""
生成视频接口(异步)
"""
workflow = build_video_workflow(prompt, negative_prompt, steps, cfg_scale, seed)
try:
# 提交到ComfyUI
comfy_response = await asyncio.to_thread(
requests.post,
f"{COMFYUI_URL}/prompt",
json={"prompt": workflow, "client_id": client_id},
timeout=60
)
if comfy_response.status_code != 200:
raise HTTPException(status_code=500, detail="ComfyUI请求失败")
prompt_result = comfy_response.json()
prompt_id = prompt_result.get('prompt_id')
# 等待生成完成
result = await wait_for_completion_async(prompt_id)
if result['status'] == 'completed':
# 获取视频
video_info = result['outputs']['videos'][0]
video_url = get_video_url(video_info)
# 下载视频
video_response = await asyncio.to_thread(requests.get, video_url)
# 返回视频流
return StreamingResponse(
video_response.iter_content(chunk_size=8192),
media_type="video/mp4"
)
else:
raise HTTPException(status_code=500, detail="生成失败")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
async def wait_for_completion_async(prompt_id: str, max_wait: int = 300):
"""
异步等待生成完成
"""
import time
start_time = time.time()
while time.time() - start_time < max_wait:
response = await asyncio.to_thread(
requests.get,
f"{COMFYUI_URL}/history/{prompt_id}"
)
history = response.json()
if prompt_id in history:
result = history[prompt_id]
return {
"status": "completed",
"outputs": result.get('outputs', {})
}
await asyncio.sleep(2)
return {"status": "timeout"}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=5000)
3. Dify集成方案
3.1 创建ComfyUI工具
python
# api/core/tools/builtin_tool/providers/comfyui.py
from core.tools.__base.tool import Tool
from core.tools.entities.tool_entities import ToolInvokeMessage, ToolProviderType
from typing import Any, Generator
import requests
import time
class ComfyUITool(Tool):
def tool_provider_type(self) -> ToolProviderType:
return ToolProviderType.BUILTIN
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
conversation_id: str | None = None,
app_id: str | None = None,
message_id: str | None = None,
) -> ToolInvokeMessage | list[ToolInvokeMessage] | Generator[ToolInvokeMessage, None, None]:
# 获取参数
comfyui_url = tool_parameters.get('comfyui_url', 'http://localhost:8188')
prompt = tool_parameters.get('prompt', '')
negative_prompt = tool_parameters.get('negative_prompt', '')
steps = tool_parameters.get('steps', 20)
cfg_scale = tool_parameters.get('cfg_scale', 7.0)
seed = tool_parameters.get('seed', -1)
client_id = tool_parameters.get('client_id', f'dify_{user_id}')
# 构建工作流
workflow = self._build_workflow(prompt, negative_prompt, steps, cfg_scale, seed)
try:
# 提交到ComfyUI
response = requests.post(
f"{comfyui_url}/prompt",
json={"prompt": workflow, "client_id": client_id},
timeout=30
)
if response.status_code != 200:
return self.create_text_message(f"ComfyUI请求失败: {response.text}")
prompt_result = response.json()
prompt_id = prompt_result.get('prompt_id')
# 等待生成完成
result = self._wait_for_completion(comfyui_url, prompt_id)
if result['status'] == 'completed':
# 获取生成的图片
outputs = result.get('outputs', {})
for node_id, node_output in outputs.items():
if 'images' in node_output:
for image_info in node_output['images']:
# 构建图片URL
image_url = self._get_image_url(
comfyui_url,
image_info['filename'],
image_info.get('subfolder', ''),
image_info.get('type', 'output')
)
# 返回图片消息
yield self.create_image_message(image_url)
if 'videos' in node_output:
for video_info in node_output['videos']:
# 构建视频URL
video_url = self._get_video_url(
comfyui_url,
video_info['filename'],
video_info.get('subfolder', ''),
video_info.get('type', 'output')
)
# 返回视频URL
yield self.create_link_message(video_url)
else:
return self.create_text_message("生成失败")
except Exception as e:
return self.create_text_message(f"错误: {str(e)}")
def _build_workflow(self, prompt: str, negative_prompt: str, steps: int, cfg_scale: float, seed: int) -> dict:
"""
构建ComfyUI工作流
"""
return {
"1": {
"class_type": "CheckpointLoaderSimple",
"inputs": {
"ckpt_name": "v1-5-pruned-emaonly.safetensors"
}
},
"2": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": prompt,
"clip": ["1", 1]
}
},
"3": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": negative_prompt,
"clip": ["1", 1]
}
},
"4": {
"class_type": "KSampler",
"inputs": {
"seed": seed,
"steps": steps,
"cfg": cfg_scale,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"model": ["1", 0],
"positive": ["2", 0],
"negative": ["3", 0]
}
},
"5": {
"class_type": "VAEDecode",
"inputs": {
"samples": ["4", 0],
"vae": ["1", 2]
}
},
"6": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "dify_generated",
"images": ["5", 0]
}
}
}
def _wait_for_completion(self, comfyui_url: str, prompt_id: str, max_wait: int = 300) -> dict:
"""
等待生成完成
"""
start_time = time.time()
while time.time() - start_time < max_wait:
response = requests.get(f"{comfyui_url}/history/{prompt_id}")
history = response.json()
if prompt_id in history:
result = history[prompt_id]
return {
"status": "completed",
"outputs": result.get('outputs', {})
}
time.sleep(2)
return {"status": "timeout"}
def _get_image_url(self, comfyui_url: str, filename: str, subfolder: str, image_type: str) -> str:
"""
获取图片URL
"""
return f"{comfyui_url}/view?filename={filename}&subfolder={subfolder}&type={image_type}"
def _get_video_url(self, comfyui_url: str, filename: str, subfolder: str, video_type: str) -> str:
"""
获取视频URL
"""
return f"{comfyui_url}/view?filename={filename}&subfolder={subfolder}&type={video_type}"
3.2 工具提供者配置
python
# api/core/tools/builtin_tool/providers/comfyui/__init__.py
from core.tools.builtin_tool.provider import BuiltinToolProviderController
from core.tools.entities.tool_entities import ToolEntity, ToolParameter, ToolDescription
from core.tools.entities.common_entities import I18nObject
from .comfyui_tool import ComfyUITool
class ComfyUIProvider(BuiltinToolProviderController):
def get_provider_name(self) -> str:
return "comfyui"
def get_provider_description(self) -> I18nObject:
return I18nObject(
en_US="ComfyUI integration for image and video generation",
zh_Hans="ComfyUI集成,用于图片和视频生成"
)
def get_tools(self) -> list[ToolEntity]:
return [
ToolEntity(
identity=ToolIdentity(
name="generate_image",
author="dify",
label=I18nObject(
en_US="Generate Image",
zh_Hans="生成图片"
),
description=ToolDescription(
human=I18nObject(
en_US="Generate images using ComfyUI",
zh_Hans="使用ComfyUI生成图片"
),
llm="Generate images using ComfyUI"
),
parameters=[
ToolParameter(
name="prompt",
label=I18nObject(
en_US="Prompt",
zh_Hans="提示词"
),
human_description=I18nObject(
en_US="Text prompt for image generation",
zh_Hans="图片生成的文本提示"
),
type=ToolParameter.ToolParameterType.STRING,
required=True,
form=ToolParameter.ToolParameterForm.LLM
),
ToolParameter(
name="negative_prompt",
label=I18nObject(
en_US="Negative Prompt",
zh_Hans="负面提示词"
),
human_description=I18nObject(
en_US="Negative prompt for image generation",
zh_Hans="图片生成的负面提示"
),
type=ToolParameter.ToolParameterType.STRING,
required=False,
form=ToolParameter.ToolParameterForm.FORM
),
ToolParameter(
name="steps",
label=I18nObject(
en_US="Steps",
zh_Hans="步数"
),
human_description=I18nObject(
en_US="Number of sampling steps",
zh_Hans="采样步数"
),
type=ToolParameter.ToolParameterType.NUMBER,
required=False,
form=ToolParameter.ToolParameterForm.FORM,
default=20
),
ToolParameter(
name="cfg_scale",
label=I18nObject(
en_US="CFG Scale",
zh_Hans="CFG缩放"
),
human_description=I18nObject(
en_US="Classifier free guidance scale",
zh_Hans="无分类器引导缩放"
),
type=ToolParameter.ToolParameterType.NUMBER,
required=False,
form=ToolParameter.ToolParameterForm.FORM,
default=7.0
),
ToolParameter(
name="seed",
label=I18nObject(
en_US="Seed",
zh_Hans="种子"
),
human_description=I18nObject(
en_US="Random seed for generation",
zh_Hans="生成的随机种子"
),
type=ToolParameter.ToolParameterType.NUMBER,
required=False,
form=ToolParameter.ToolParameterForm.FORM,
default=-1
)
]
)
]
def get_tool(self, tool_name: str) -> ToolEntity | None:
for tool in self.get_tools():
if tool.identity.name == tool_name:
return tool
return None
def get_tool_instance(self, tool_name: str) -> ComfyUITool | None:
tool_entity = self.get_tool(tool_name)
if tool_entity:
return ComfyUITool(entity=tool_entity, runtime=None)
return None
4. 前端集成方案
4.1 前端调用示例
typescript
// web/app/components/comfyui/generate.tsx
import React, { useState } from 'react';
interface GenerateParams {
prompt: string;
negative_prompt?: string;
steps?: number;
cfg_scale?: number;
seed?: number;
}
export const ComfyUIGenerate: React.FC = () => {
const [loading, setLoading] = useState(false);
const [imageUrl, setImageUrl] = useState<string>('');
const [videoUrl, setVideoUrl] = useState<string>('');
const generateImage = async (params: GenerateParams) => {
setLoading(true);
try {
const response = await fetch('/api/generate-image', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
if (response.ok) {
const blob = await response.blob();
const url = URL.createObjectURL(blob);
setImageUrl(url);
} else {
console.error('生成失败');
}
} catch (error) {
console.error('错误:', error);
} finally {
setLoading(false);
}
};
const generateVideo = async (params: GenerateParams) => {
setLoading(true);
try {
const response = await fetch('/api/generate-video', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
if (response.ok) {
const blob = await response.blob();
const url = URL.createObjectURL(blob);
setVideoUrl(url);
} else {
console.error('生成失败');
}
} catch (error) {
console.error('错误:', error);
} finally {
setLoading(false);
}
};
return (
<div>
<h2>ComfyUI生成器</h2>
<div>
<input
type="text"
placeholder="输入提示词"
onChange={(e) => setPrompt(e.target.value)}
/>
<input
type="text"
placeholder="负面提示词"
onChange={(e) => setNegativePrompt(e.target.value)}
/>
<input
type="number"
placeholder="步数"
defaultValue={20}
onChange={(e) => setSteps(Number(e.target.value))}
/>
<input
type="number"
placeholder="CFG缩放"
defaultValue={7.0}
step={0.1}
onChange={(e) => setCfgScale(Number(e.target.value))}
/>
<input
type="number"
placeholder="种子"
defaultValue={-1}
onChange={(e) => setSeed(Number(e.target.value))}
/>
</div>
<button onClick={() => generateImage({ prompt, negative_prompt, steps, cfg_scale, seed })}>
生成图片
</button>
<button onClick={() => generateVideo({ prompt, negative_prompt, steps, cfg_scale, seed })}>
生成视频
</button>
{loading && <div>生成中...</div>}
{imageUrl && (
<div>
<h3>生成的图片</h3>
<img src={imageUrl} alt="生成的图片" />
</div>
)}
{videoUrl && (
<div>
<h3>生成的视频</h3>
<video src={videoUrl} controls />
</div>
)}
</div>
);
};
5. 总结
ComfyUI提供了丰富的HTTP API接口,可以方便地集成到各种应用中:
- 核心接口 :
/prompt、/history、/view、/queue等 - 集成方式:可以通过Flask、FastAPI等Python Web框架封装
- Dify集成:通过创建自定义工具实现
- 前端调用:通过REST API调用,直接获取生成的图片和视频
通过以上方案,你可以灵活地将ComfyUI集成到你的应用中,实现图片和视频的生成功能。