ComfyUI-WorkflowPublisher/__init__.py

235 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import json
import re
import urllib
import aiohttp
from aiohttp import web
from server import PromptServer
# 获取当前插件的目录
NODE_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_FILE = os.path.join(NODE_DIR, "config.json")
# 默认配置
DEFAULT_CONFIG = {
"api_url": ""
}
def load_config():
"""加载配置文件,如果文件不存在则创建"""
if not os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, 'w') as f:
json.dump(DEFAULT_CONFIG, f)
return DEFAULT_CONFIG
with open(CONFIG_FILE, 'r') as f:
return json.load(f)
def save_config(config_data):
"""保存配置到文件"""
with open(CONFIG_FILE, 'w') as f:
json.dump(config_data, f, indent=4)
# 创建一个虚拟节点类虽然它不会出现在图表中但这是ComfyUI加载自定义节点的标准方式
class WorkflowPublisherNode:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
# 这个节点实际上是UI扩展不处理任何输入输出但需要定义这些方法
return {
"required": {},
}
RETURN_TYPES = ()
FUNCTION = "do_nothing"
OUTPUT_NODE = True
CATEGORY = "utilities"
def do_nothing(self):
return ()
# -----------------
# API 端点定义
# -----------------
# 添加自定义API路由
@PromptServer.instance.routes.get("/publisher/settings")
async def get_publisher_settings(request):
"""获取发布器设置"""
config = load_config()
return web.json_response(config)
@PromptServer.instance.routes.post("/publisher/settings")
async def save_publisher_settings(request):
"""保存发布器设置"""
try:
data = await request.json()
api_url = data.get("api_url", "")
config = load_config()
config["api_url"] = api_url
save_config(config)
return web.json_response({"status": "success", "message": "Settings saved"})
except Exception as e:
return web.json_response({"status": "error", "message": str(e)}, status=500)
@PromptServer.instance.routes.post("/publisher/publish")
async def publish_workflow_handler(request):
"""处理工作流发布请求"""
try:
data = await request.json()
workflow = data.get("workflow")
workflow_name = data.get("name")
if not workflow or not workflow_name:
return web.json_response({"status": "error", "message": "Missing workflow data or name"}, status=400)
config = load_config()
target_api_url = config.get("api_url")
if not target_api_url:
return web.json_response({"status": "error", "message": "API URL not configured"}, status=400)
# 准备要发送到目标API的数据
payload = {
"name": workflow_name,
"workflow": workflow
}
headers = {'Content-Type': 'application/json'}
# 使用 aiohttp 发送异步请求
async with aiohttp.ClientSession() as session:
async with session.post(target_api_url, json=payload, headers=headers) as response:
response_text = await response.text()
if response.status == 200:
return web.json_response(
{"status": "success", "message": "Workflow published successfully!", "details": response_text})
else:
return web.json_response({
"status": "error",
"message": f"Failed to publish workflow. Target API returned status {response.status}",
"details": response_text
}, status=500)
except Exception as e:
import traceback
traceback.print_exc()
return web.json_response({"status": "error", "message": str(e)}, status=500)
@PromptServer.instance.routes.get("/publisher/workflows")
async def get_workflows_from_server(request):
"""从目标服务器获取工作流列表,并按基础名称进行分组"""
try:
config = load_config()
target_api_url = config.get("api_url")
if not target_api_url:
return web.json_response({"status": "error", "message": "API URL not configured"}, status=400)
async with aiohttp.ClientSession() as session:
async with session.get(target_api_url) as response:
if response.status != 200:
response_text = await response.text()
return web.json_response({
"status": "error",
"message": f"Failed to fetch workflows. Target API returned status {response.status}",
"details": response_text
}, status=500)
workflows = await response.json()
# --- [核心逻辑] 对工作流进行分组和版本化 ---
grouped_workflows = {}
# 正则表达式匹配 '任意字符 [YYYYMMDDHHMMSS]' 格式
version_pattern = re.compile(r"^(.*) \[(20\d{12})\]$")
for wf in workflows:
match = version_pattern.match(wf.get("name", ""))
if match:
base_name = match.group(1).strip()
version = match.group(2)
else:
# 对于没有版本号的旧工作流,将整个名称作为基础名称
base_name = wf.get("name", "Unnamed Workflow")
version = "N/A" # 无版本信息
if base_name not in grouped_workflows:
grouped_workflows[base_name] = []
grouped_workflows[base_name].append({
"version": version,
"workflow": wf.get("workflow")
})
# 对每个工作流的版本进行降序排序(最新版本在前)
for base_name in grouped_workflows:
grouped_workflows[base_name].sort(key=lambda x: x['version'], reverse=True)
return web.json_response(grouped_workflows)
except Exception as e:
import traceback
traceback.print_exc()
return web.json_response({"status": "error", "message": str(e)}, status=500)
@PromptServer.instance.routes.post("/publisher/workflow/delete")
async def delete_workflow_version(request):
"""接收前端的删除请求并将其转发到目标API服务器"""
try:
data = await request.json()
workflow_name = data.get("name")
if not workflow_name:
return web.json_response({"status": "error", "message": "Missing workflow name"}, status=400)
config = load_config()
target_api_url = config.get("api_url")
if not target_api_url:
return web.json_response({"status": "error", "message": "API URL not configured"}, status=400)
# 构建目标URL需要对工作流名称进行URL编码
delete_url = f"{target_api_url}/{urllib.parse.quote(workflow_name)}"
async with aiohttp.ClientSession() as session:
async with session.delete(delete_url) as response:
if response.status == 200:
return web.json_response({"status": "success", "message": "Workflow version deleted"})
else:
details = await response.text()
return web.json_response({
"status": "error",
"message": f"Target API failed to delete. Status: {response.status}",
"details": details
}, status=response.status)
except Exception as e:
return web.json_response({"status": "error", "message": str(e)}, status=500)
# -----------------
# ComfyUI 注册
# -----------------
# 告诉 ComfyUI 我们有一个包含JS文件的web目录
WEB_DIRECTORY = "js"
# 节点映射
NODE_CLASS_MAPPINGS = {
# "WorkflowPublisher": WorkflowPublisherNode
}
# 节点显示名称映射
NODE_DISPLAY_NAME_MAPPINGS = {
# "WorkflowPublisher": "Workflow Publisher (UI)"
}
print("✅ Loaded Workflow Publisher Node")