235 lines
8.1 KiB
Python
235 lines
8.1 KiB
Python
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")
|