From 9f571be3e778170e4eaca14c9ddd64a5bd4ee2bd Mon Sep 17 00:00:00 2001 From: iSuneast Date: Thu, 3 Apr 2025 22:32:48 +0800 Subject: [PATCH] remove SavedImageWebhookNode --- README.md | 88 +++--------------- __init__.py | 7 +- webhook_notifier.py | 217 ++++++-------------------------------------- 3 files changed, 42 insertions(+), 270 deletions(-) diff --git a/README.md b/README.md index f588dca..e5b3c9e 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ # ComfyUI-WebhookNotifier -ComfyUI的Webhook通知插件,可以在工作流程执行完成时,将生成图片的信息发送到指定的Webhook URL。 +ComfyUI的Webhook通知插件,可以在工作流程执行完成时,发送任务完成的通知。 ## 功能特点 -- 支持通知生成图像的基本信息 -- 支持通知已保存图像的具体路径 -- **支持提供图片的下载链接** -- **支持在通知前自动保存图片** -- 支持自定义通知载荷(payload) +- 在生成图像后发送完成通知 +- 通知中包含当前工作流的prompt ID +- 支持自定义通知内容 - 可配置默认Webhook URL ## 安装方法 @@ -34,93 +32,31 @@ pip install requests - `default_webhook_url`: 默认的Webhook URL - `enable_debug_logs`: 是否启用调试日志 -- `base_url`: ComfyUI服务器的基础URL,默认为`http://localhost:8188` -- `output_dir`: 默认的图片输出目录,默认为`output` ## 使用方法 -插件提供了两种节点: - -### 1. Webhook Notifier - -直接连接到图像输出,可以自动保存图片并发送图像信息和下载链接。 +插件提供了 Webhook Notifier 节点,可以连接到图像输出,当生成完成时发送通知。 参数说明: - `images`: 输入图像(必填) - `webhook_url`: Webhook URL地址(必填) -- `include_metadata`: 是否包含元数据(可选) -- `custom_payload`: 自定义JSON载荷(可选) -- `base_url`: ComfyUI服务器的基础URL(可选,默认使用配置文件中的值) -- `save_images`: 是否保存图片(可选,默认为true) -- `filename_prefix`: 保存文件名前缀(可选,默认为"webhook_") -- `output_dir`: 输出目录(可选,默认使用配置文件中的值) - -此节点有两个输出: -1. `images`: 原始图像数据,可以连接到其他节点 -2. `saved_paths`: 保存的图片路径,可以连接到其他需要文件路径的节点 - -### 2. Saved Image Webhook - -连接到SaveImage节点的输出,发送已保存图像的路径信息和下载链接。 - -参数说明: -- `saved_images`: SaveImage节点的输出(必填) -- `webhook_url`: Webhook URL地址(必填) -- `custom_payload`: 自定义JSON载荷(可选) -- `base_url`: ComfyUI服务器的基础URL(可选,默认使用配置文件中的值) - -## 示例工作流 - -1. 基本使用示例(不需要SaveImage节点): - - 将模型输出的图像连接到Webhook Notifier节点 - - 设置webhook_url为你的接收端点 - - 设置save_images为true - - 运行工作流,图片会自动保存并发送通知 - -2. 与SaveImage节点配合使用: - - 将SaveImage节点的输出连接到Saved Image Webhook节点 - - 设置webhook_url为你的接收端点 - - 运行工作流 - -3. 带自定义数据的示例: - - 将自定义JSON设置为如下格式: - ```json - { - "workflow_name": "我的测试工作流", - "user_id": "user_123", - "additional_info": "任何你想要的附加信息" - } - ``` +- `workflow_name`: 工作流名称(可选) +- `additional_info`: 自定义JSON载荷(可选) ## Webhook响应格式 -WebhookNotifier节点发送的数据格式示例(当save_images=true时): +WebhookNotifier节点发送的数据格式示例: ```json { + "status": "completed", + "prompt_id": "12345678", + "workflow_name": "我的测试工作流", + "timestamp": "2023-04-03 15:30:45", "images": { "image_count": 1, "dimensions": "512x512" }, - "image_paths": ["/path/to/output/webhook_20230403-153045_abcd1234.png"], - "download_links": ["http://localhost:8188/view?filename=output/webhook_20230403-153045_abcd1234.png"], - "metadata": { - "timestamp": "2023-04-03 15:30:45", - "shape": [1, 512, 512, 3] - }, - "custom_field1": "自定义值1", - "custom_field2": "自定义值2" -} -``` - -SavedImageWebhook节点发送的数据格式示例: - -```json -{ - "image_paths": ["/path/to/image1.png", "/path/to/image2.png"], - "download_links": ["http://localhost:8188/view?filename=image1.png", "http://localhost:8188/view?filename=image2.png"], - "timestamp": "2023-04-03 15:30:45", - "total_images": 2, "custom_field1": "自定义值1", "custom_field2": "自定义值2" } diff --git a/__init__.py b/__init__.py index 5fdbace..e033b2a 100644 --- a/__init__.py +++ b/__init__.py @@ -4,10 +4,7 @@ import os NODE_CLASS_MAPPINGS = {} NODE_DISPLAY_NAME_MAPPINGS = {} -from .webhook_notifier import WebhookNotifierNode, SavedImageWebhookNode +from .webhook_notifier import WebhookNotifierNode NODE_CLASS_MAPPINGS["WebhookNotifierNode"] = WebhookNotifierNode -NODE_DISPLAY_NAME_MAPPINGS["WebhookNotifierNode"] = "Webhook Notifier" - -NODE_CLASS_MAPPINGS["SavedImageWebhookNode"] = SavedImageWebhookNode -NODE_DISPLAY_NAME_MAPPINGS["SavedImageWebhookNode"] = "Saved Image Webhook" \ No newline at end of file +NODE_DISPLAY_NAME_MAPPINGS["WebhookNotifierNode"] = "Webhook Notifier" \ No newline at end of file diff --git a/webhook_notifier.py b/webhook_notifier.py index 672aa17..fff8b3a 100644 --- a/webhook_notifier.py +++ b/webhook_notifier.py @@ -1,21 +1,14 @@ import requests import json import os -from pathlib import Path -import urllib.parse -import uuid import time -import numpy as np -from PIL import Image # 加载配置文件 def load_config(): config_path = os.path.join(os.path.dirname(__file__), "config.json") default_config = { "default_webhook_url": "https://example.com/webhook", - "enable_debug_logs": True, - "base_url": "http://localhost:8188", - "output_dir": "output" # 添加默认输出目录 + "enable_debug_logs": True } try: @@ -45,34 +38,28 @@ class WebhookNotifierNode: return { "required": { "images": ("IMAGE",), - "webhook_url": ("STRING", {"default": CONFIG.get("default_webhook_url", "https://example.com/webhook")}), + "webhook_url": ("STRING", {"default": CONFIG.get("default_webhook_url", "https://example.com/webhook")}) }, "optional": { - "include_metadata": ("BOOLEAN", {"default": True}), - "custom_payload": ("STRING", {"default": "{}", "multiline": True}), - "base_url": ("STRING", {"default": CONFIG.get("base_url", "http://localhost:8188")}), - "save_images": ("BOOLEAN", {"default": True}), - "filename_prefix": ("STRING", {"default": "webhook_"}), - "output_dir": ("STRING", {"default": CONFIG.get("output_dir", "output")}), + "workflow_name": ("STRING", {"default": "默认工作流"}), + "additional_info": ("STRING", {"default": "{}", "multiline": True}) } } - RETURN_TYPES = ("IMAGE", "STRING") - RETURN_NAMES = ("images", "saved_paths") + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("images",) FUNCTION = "notify" CATEGORY = "utils" - def notify(self, images, webhook_url, include_metadata=True, custom_payload="{}", - base_url=None, save_images=True, filename_prefix="webhook_", output_dir=None): + def notify(self, images, webhook_url, workflow_name="默认工作流", additional_info="{}", prompt=None): try: - # 如果未提供base_url和output_dir,则使用配置中的默认值 - if not base_url: - base_url = CONFIG.get("base_url", "http://localhost:8188") - if not output_dir: - output_dir = CONFIG.get("output_dir", "output") - - # 确保base_url没有尾部斜杠 - base_url = base_url.rstrip('/') + # 从ComfyUI获取prompt_id (execution_id) + prompt_id = "unknown" + if prompt is not None and "extra_pnginfo" in prompt and "workflow" in prompt["extra_pnginfo"]: + if "execution_id" in prompt["extra_pnginfo"]["workflow"]: + prompt_id = prompt["extra_pnginfo"]["workflow"]["execution_id"] + elif "client_id" in prompt["extra_pnginfo"]["workflow"]: + prompt_id = prompt["extra_pnginfo"]["workflow"]["client_id"] # 准备基本信息 image_info = { @@ -80,67 +67,22 @@ class WebhookNotifierNode: "dimensions": f"{images.shape[1]}x{images.shape[2]}" } - saved_paths = [] - download_links = [] - - # 如果需要保存图片 - if save_images: - try: - # 确保输出目录存在 - os.makedirs(output_dir, exist_ok=True) - - # 为每张图片生成唯一文件名并保存 - timestamp = time.strftime("%Y%m%d-%H%M%S") - - for i in range(len(images)): - # 生成文件名 - unique_id = str(uuid.uuid4())[:8] - filename = f"{filename_prefix}{timestamp}_{unique_id}.png" - filepath = os.path.join(output_dir, filename) - - # 将numpy数组转换为PIL图像并保存 - img = Image.fromarray((images[i] * 255).astype(np.uint8)) - img.save(filepath) - - # 记录保存的路径 - saved_paths.append(filepath) - - # 生成下载链接 - encoded_path = urllib.parse.quote(os.path.join(os.path.basename(output_dir), filename)) - download_url = f"{base_url}/view?filename={encoded_path}" - download_links.append(download_url) - - if CONFIG.get("enable_debug_logs", False): - print(f"图片已保存到: {saved_paths}") - print(f"下载链接: {download_links}") - - except Exception as e: - print(f"保存图片时出错: {str(e)}") + # 尝试解析额外信息 + try: + extra_info = json.loads(additional_info) if additional_info else {} + except json.JSONDecodeError: + extra_info = {} # 构造payload - try: - base_payload = json.loads(custom_payload) if custom_payload else {} - except json.JSONDecodeError: - base_payload = {} - - # 合并信息 payload = { - **base_payload, - "images": image_info + "status": "completed", + "prompt_id": prompt_id, + "workflow_name": workflow_name, + "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), + "images": image_info, + **extra_info } - # 如果已保存图片,添加路径和下载链接 - if save_images and saved_paths: - payload["image_paths"] = saved_paths - payload["download_links"] = download_links - - if include_metadata: - # 添加更多可能的元数据 - payload["metadata"] = { - "timestamp": import_time_module(), - "shape": images.shape, - } - # 发送webhook response = requests.post( webhook_url, @@ -151,113 +93,10 @@ class WebhookNotifierNode: if response.status_code >= 400: print(f"Webhook通知失败: {response.status_code} - {response.text}") else: - print(f"Webhook通知成功: {response.status_code}") + print(f"Webhook通知成功: {response.status_code}, prompt_id: {prompt_id}") except Exception as e: print(f"发送webhook时出错: {str(e)}") - # 返回原始图像和保存的路径(逗号分隔的字符串) - return (images, ",".join(saved_paths)) - - -class SavedImageWebhookNode: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "saved_images": ("STRING", {"forceInput": True}), # 接收SaveImage节点的输出 - "webhook_url": ("STRING", {"default": CONFIG.get("default_webhook_url", "https://example.com/webhook")}), - }, - "optional": { - "custom_payload": ("STRING", {"default": "{}", "multiline": True}), - "base_url": ("STRING", {"default": CONFIG.get("base_url", "http://localhost:8188")}), - } - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "notify_saved" - CATEGORY = "utils" - - def notify_saved(self, saved_images, webhook_url, custom_payload="{}", base_url=None): - try: - # 如果未提供base_url,则使用配置中的默认值 - if not base_url: - base_url = CONFIG.get("base_url", "http://localhost:8188") - - # 确保base_url没有尾部斜杠 - base_url = base_url.rstrip('/') - - # 解析保存的图片路径 - image_paths = saved_images.split(",") if isinstance(saved_images, str) else [] - image_paths = [p.strip() for p in image_paths if p.strip()] - - # 准备下载链接 - download_links = [] - for path in image_paths: - # 获取相对于ComfyUI输出文件夹的路径 - try: - # 假设路径格式为绝对路径,需要提取相对于output目录的部分 - relative_path = path - if os.path.isabs(path): - # 尝试找到output目录的位置 - output_dir = None - parts = Path(path).parts - for i, part in enumerate(parts): - if part == "output": - output_dir = os.path.join(*parts[:i+1]) - relative_path = os.path.join(*parts[i+1:]) - break - - # 使用URL编码处理文件路径 - encoded_path = urllib.parse.quote(relative_path) - # 构建完整的下载URL - download_url = f"{base_url}/view?filename={encoded_path}" - download_links.append(download_url) - except Exception as e: - print(f"处理图片路径时出错: {str(e)}") - # 如果出错,至少提供原始路径 - download_links.append(path) - - # 准备发送的信息 - try: - base_payload = json.loads(custom_payload) if custom_payload else {} - except json.JSONDecodeError: - base_payload = {} - - # 构建包含图片路径和下载链接的payload - payload = { - **base_payload, - "image_paths": image_paths, - "download_links": download_links, # 添加下载链接 - "timestamp": import_time_module(), - "total_images": len(image_paths) - } - - # 记录调试信息 - if CONFIG.get("enable_debug_logs", False): - print(f"正在发送webhook通知,图片路径: {image_paths}") - print(f"下载链接: {download_links}") - - # 发送webhook - response = requests.post( - webhook_url, - json=payload, - headers={"Content-Type": "application/json"} - ) - - if response.status_code >= 400: - print(f"保存图片Webhook通知失败: {response.status_code} - {response.text}") - else: - print(f"保存图片Webhook通知成功: {response.status_code}") - - except Exception as e: - print(f"发送保存图片webhook时出错: {str(e)}") - - # 返回原始的保存路径信息 - return (saved_images,) - - -# 导入time模块的辅助函数 - 避免全局导入 -def import_time_module(): - import time - return time.strftime("%Y-%m-%d %H:%M:%S") \ No newline at end of file + # 返回原始图像 + return (images,) \ No newline at end of file