新增圖片拖曳與剪貼簿上傳功能,優化檔案處理邏輯

This commit is contained in:
NineKey
2026-01-28 03:43:38 +08:00
parent 2280e33dab
commit 905faab0d8
2 changed files with 143 additions and 9 deletions
+38 -9
View File
@@ -3,6 +3,7 @@ import json
import torch
import numpy as np
from PIL import Image, ImageOps
import folder_paths # 新增:用於獲取 ComfyUI 的標準路徑
class ImageWorkflowExtractor:
@classmethod
@@ -16,7 +17,6 @@ class ImageWorkflowExtractor:
},
}
# 修改輸出類型:將原本的 raw_info (STRING) 改為 image (IMAGE)
RETURN_TYPES = ("JSON", "STRING", "STRING", "IMAGE")
RETURN_NAMES = ("workflow_json", "clean_text", "selected_path", "image")
FUNCTION = "extract_info"
@@ -24,29 +24,56 @@ class ImageWorkflowExtractor:
def extract_info(self, image_or_dir, search_by, search_query, seed):
target_path = image_or_dir.strip()
# ==========================================
# 0. 智慧路徑解析 (支援絕對路徑與上傳路徑)
# ==========================================
# 邏輯:先檢查是否為電腦上的絕對路徑 -> 再檢查 ComfyUI input 資料夾 -> 最後檢查 output 資料夾
final_path = target_path
if os.path.exists(target_path):
final_path = os.path.abspath(target_path)
else:
# 嘗試從 input 資料夾尋找 (這是 Drag & Drop 上傳的預設位置)
input_dir = folder_paths.get_input_directory()
input_path = os.path.join(input_dir, target_path)
# 嘗試從 output 資料夾尋找
output_dir = folder_paths.get_output_directory()
output_path = os.path.join(output_dir, target_path)
if os.path.exists(input_path):
final_path = input_path
elif os.path.exists(output_path):
final_path = output_path
else:
# 如果都找不到,保持原樣,讓後面的邏輯去報錯
final_path = os.path.abspath(target_path)
selected_file = ""
# ==========================================
# 1. 檔案選取邏輯
# 1. 檔案選取邏輯 (單檔或資料夾隨機)
# ==========================================
if os.path.isfile(target_path):
selected_file = target_path
elif os.path.isdir(target_path):
if os.path.isfile(final_path):
selected_file = final_path
elif os.path.isdir(final_path):
valid_extensions = ('.png', '.webp', '.jpg', '.jpeg')
files = sorted([
os.path.join(target_path, f)
for f in os.listdir(target_path)
os.path.join(final_path, f)
for f in os.listdir(final_path)
if f.lower().endswith(valid_extensions)
])
if not files:
# 若無檔案,回傳一個空的黑色張量避免系統崩潰
return ({}, "No images found", target_path, torch.zeros((1, 64, 64, 3)))
return ({}, "No images found", final_path, torch.zeros((1, 64, 64, 3)))
index = seed % len(files)
selected_file = files[index]
else:
return ({}, "Invalid path", target_path, torch.zeros((1, 64, 64, 3)))
return ({}, "Invalid path or file not found", final_path, torch.zeros((1, 64, 64, 3)))
# ==========================================
# 2. 資訊提取與圖像轉換輸出
@@ -63,9 +90,11 @@ class ImageWorkflowExtractor:
for node in nodes:
match = False
if search_by == "ID":
# ID 轉字串比對
if str(node.get("id")) == search_query.strip():
match = True
else:
# Type 轉字串比對
if node.get("type") == search_query.strip():
match = True
+105
View File
@@ -0,0 +1,105 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
app.registerExtension({
name: "DynamicTags.ImageDrop",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "WorkflowMetadataReader") {
// =============================================================================
// Helper: 圖片上傳處理
// =============================================================================
async function uploadFile(file, node) {
try {
const body = new FormData();
body.append("image", file);
body.append("overwrite", "true");
const resp = await api.fetchApi("/upload/image", {
method: "POST",
body,
});
if (resp.status === 200) {
const data = await resp.json();
const filename = data.name;
const pathWidget = node.widgets.find(w => w.name === "image_or_dir");
if (pathWidget) {
pathWidget.value = filename;
node.setDirtyCanvas(true, true);
}
} else {
alert(`Upload failed: ${resp.statusText}`);
}
} catch (error) {
console.error("Upload error:", error);
}
}
// =============================================================================
// Logic 1: 拖曳 (Drag & Drop) 事件攔截
// =============================================================================
const onDragOverOriginal = nodeType.prototype.onDragOver;
nodeType.prototype.onDragOver = function(e) {
if (onDragOverOriginal) onDragOverOriginal.apply(this, arguments);
return true;
};
const onDragDropOriginal = nodeType.prototype.onDragDrop;
nodeType.prototype.onDragDrop = function(e) {
if (onDragDropOriginal) onDragDropOriginal.apply(this, arguments);
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
const file = e.dataTransfer.files[0];
if (file.type.startsWith("image/")) {
e.preventDefault();
e.stopPropagation();
uploadFile(file, this);
return true;
}
}
return false;
};
// =============================================================================
// Logic 2: 剪貼簿 (Paste) 事件攔截
// =============================================================================
const onSelectedOriginal = nodeType.prototype.onSelected;
nodeType.prototype.onSelected = function() {
if (onSelectedOriginal) onSelectedOriginal.apply(this, arguments);
this._pasteHandler = (e) => {
if (!app.canvas.selected_nodes || !app.canvas.selected_nodes[this.id]) return;
if (e.clipboardData && e.clipboardData.files && e.clipboardData.files.length > 0) {
const file = e.clipboardData.files[0];
if (file.type.startsWith("image/")) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
uploadFile(file, this);
}
}
};
window.addEventListener("paste", this._pasteHandler, true);
};
const onDeselectedOriginal = nodeType.prototype.onDeselected;
nodeType.prototype.onDeselected = function() {
if (onDeselectedOriginal) onDeselectedOriginal.apply(this, arguments);
if (this._pasteHandler) {
window.removeEventListener("paste", this._pasteHandler, true);
this._pasteHandler = null;
}
};
}
}
});