update v3.1.0

This commit is contained in:
billwuhao
2025-03-01 04:01:54 +08:00
parent ce0bc5b8f8
commit 1fe08b940e
13 changed files with 97925 additions and 246741 deletions
-157
View File
@@ -1,157 +0,0 @@
import random
import re
import os
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed
class DeepseekRun:
node_dir = os.path.dirname(os.path.abspath(__file__))
comfy_path = os.path.dirname(os.path.dirname(node_dir))
model_path = os.path.join(comfy_path, "models", "LLM")
ds_model_path = os.path.join(model_path, "DeepScaleR-1.5B-Preview")
model_paths = {
"DeepScaleR-1.5B-Preview": ds_model_path, # 可以添加更多模型和路径
}
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": (list(cls.model_paths.keys()), {"tooltip": "models are expected to be in Comfyui/models/LLM folder"}),
"user_prompt": ("STRING", {"default": "", "multiline": True}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"max_tokens": ("INT", {"default": 1000, "min": 0, "max": 0xffffffffffffffff}),
"temperature": ("FLOAT", {"default": 1, "min": 0, "max": 2}),
"top_k": ("INT", {"default": 50, "min": 0, "max": 101}),
"top_p": ("FLOAT", {"default": 1, "min": 0, "max": 1}),
"unload_model": ("BOOLEAN", {
"default": False,
"tooltip": "If True, unload the model from memory after execution. Next execution will reload the model."}), # Added unload_model input
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("STRING",)
FUNCTION = "dsgen"
CATEGORY = "MW-OneButtonPrompt"
_model_cache = {}
def dsgen(self, model, user_prompt, seed=0, temperature=1.0, max_tokens=1000, top_k=25, top_p=1.0, unload_model=False):
if seed:
set_seed(self.hash_seed(seed))
match = re.search(r'{(.*?)}', user_prompt)
if match:
content_in_brackets = match.group(1)
# 按 "|" 拆分
options = content_in_brackets.split('|')
# 随机选择一个
chosen_option = random.choice(options).strip() # 使用 strip() 去除可能存在的首尾空格
# 替换原字符串 "{}" 中的内容
user_prompt = user_prompt.replace(match.group(0), chosen_option)
else:
user_prompt = user_prompt
messages = [
{"role": "user", "content": user_prompt},
]
model_dict = self.load_model(model)
if model_dict is None: # Check if model loading failed
return ("Error loading model. Check console.", )
device = model_dict["device"] # 获取设备信息
dsmodel = model_dict["dsmodel"]
tokenizer = model_dict["tokenizer"]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
# 构造输入时直接使用 device
model_inputs = tokenizer([text], return_tensors="pt").to(device)
generated_ids = dsmodel.generate(
**model_inputs,
max_new_tokens=max_tokens,
temperature=temperature,
top_k=top_k,
top_p=top_p,
do_sample=True
)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
response = re.sub(r'<think>[\s\S]*?</think>', '', response).strip()
if unload_model: # Check if unload_model is True
self.unload_model_from_cache(model) # Unload the model if requested
print(f"DeepseekRun: Model '{model}' unloaded from cache.") # Inform user model is unloaded
return (response, )
def load_model(self, model):
model_path = self.model_paths.get(model)
if model in self._model_cache:
return self._model_cache[model]
try:
dsmodel = AutoModelForCausalLM.from_pretrained(
model_path,
device_map="auto", # 自动分配
torch_dtype="auto",
low_cpu_mem_usage=True,
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained(model_path)
# 获取实际使用的设备
device = next(dsmodel.parameters()).device
model_info = {
"dsmodel": dsmodel,
"tokenizer": tokenizer,
"device": device
}
self._model_cache[model] = model_info
print(f"DeepseekRun: Model '{model}' loaded to cache.") # Inform user model is loaded
return model_info
except Exception as e:
print(f"DeepseekRun: Error loading model {model} from {model_path}: {e}")
return None
def unload_model_from_cache(self, model): # New method to unload model
if model in self._model_cache:
model_info = self._model_cache.pop(model) # Remove model from cache
del model_info # Optionally delete model_info dictionary to release references (may not be strictly needed in Python)
torch.cuda.empty_cache() # Clear CUDA cache to try and free VRAM
print(f"DeepseekRun: Model '{model}' removed from cache and CUDA cache cleared.") # Inform user of unload action
else:
print(f"DeepseekRun: Model '{model}' not found in cache, cannot unload.") # Inform user if model was not in cache
def hash_seed(self, seed):
import hashlib
# Convert the seed to a string and then to bytes
seed_bytes = str(seed).encode('utf-8')
# Create a SHA-256 hash of the seed bytes
hash_object = hashlib.sha256(seed_bytes)
# Convert the hash to an integer
hashed_seed = int(hash_object.hexdigest(), 16)
# Ensure the hashed seed is within the acceptable range for set_seed
return hashed_seed % (2**32)
NODE_CLASS_MAPPINGS = {
"DeepseekRun": DeepseekRun
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DeepseekRun": "Deepseek Run"
}
+484
View File
@@ -0,0 +1,484 @@
import random
import re
import os
import torch
from PIL import Image
import numpy as np
from qwen_vl_utils import process_vision_info
from transformers import (
Qwen2_5_VLForConditionalGeneration,
AutoModelForCausalLM,
AutoTokenizer,
AutoProcessor,
BitsAndBytesConfig,
set_seed,
)
node_dir = os.path.dirname(os.path.abspath(__file__))
comfy_path = os.path.dirname(os.path.dirname(node_dir))
llm_model_path = os.path.join(comfy_path, "models", "LLM")
def hash_seed(seed):
import hashlib
# Convert the seed to a string and then to bytes
seed_bytes = str(seed).encode('utf-8')
# Create a SHA-256 hash of the seed bytes
hash_object = hashlib.sha256(seed_bytes)
# Convert the hash to an integer
hashed_seed = int(hash_object.hexdigest(), 16)
# Ensure the hashed seed is within the acceptable range for set_seed
return hashed_seed % (2**32)
################################### Qwen VLM ###################################
def tensor_to_pil(image_tensor, batch_index=0) -> Image:
# Convert tensor of shape [batch, height, width, channels] at the batch_index to PIL Image
image_tensor = image_tensor[batch_index].unsqueeze(0)
i = 255.0 * image_tensor.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8).squeeze())
return img
class QwenVLRun:
def __init__(self):
self.model_checkpoint = None
self.processor = None
self.model = None
# self.device = (
# torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
# )
# self.bf16_support = (
# torch.cuda.is_available()
# and torch.cuda.get_device_capability(self.device)[0] >= 8
# )
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"default": "", "multiline": True}),
"model": (
[
"Qwen2.5-VL-3B-Instruct",
# "Qwen2.5-VL-7B-Instruct",
# "Qwen2.5-VL-7B-Instruct-bnb-4bit"
],
{"default": "Qwen2.5-VL-3B-Instruct"},
),
# "quantization": (
# ["none", "4bit", "8bit"],
# {"default": "none"},
# ),
"keep_model_loaded": ("BOOLEAN", {"default": False}),
"temperature": (
"FLOAT",
{"default": 0.7, "min": 0, "max": 1, "step": 0.1},
),
"max_new_tokens": (
"INT",
{"default": 512, "min": 128, "max": 2048, "step": 1},
),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
},
"optional": {
"image": ("IMAGE",),
"next_image": ("IMAGE",),
"video": ("IMAGE",),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "inference"
CATEGORY = "MW-OneButtonPrompt"
def inference(
self,
text,
model,
# quantization,
keep_model_loaded,
temperature,
max_new_tokens,
seed,
image=None,
next_image =None,
video=None,
):
set_seed(hash_seed(seed))
self.model_checkpoint = os.path.join(llm_model_path, model)
if self.processor is None:
# Define min_pixels and max_pixels:
# Images will be resized to maintain their aspect ratio
# within the range of min_pixels and max_pixels.
min_pixels = 256*28*28
max_pixels = 1024*28*28
self.processor = AutoProcessor.from_pretrained(
self.model_checkpoint,
min_pixels=min_pixels,
max_pixels=max_pixels,
)
if self.model is None:
# Load the model on the available device(s)
# if quantization == "4bit":
# quantization_config = BitsAndBytesConfig(
# load_in_4bit=True,
# bnb_4bit_quant_type="nf4", # 与 Unsloth 默认量化类型一致
# bnb_4bit_compute_dtype=torch.bfloat16
# )
# elif quantization == "8bit":
# quantization_config = BitsAndBytesConfig(
# load_in_8bit=True,
# )
# else:
# quantization_config = None
self.model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
self.model_checkpoint,
torch_dtype="auto", # torch.bfloat16 if self.bf16_support else torch.float16,
device_map="auto",
# quantization_config=quantization_config,
)
with torch.no_grad():
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": text},
],
}
]
if video is not None:
video_imgs = [tensor_to_pil(video, i) for i in range(video.size(0))]
messages[0]["content"].insert(0, {"type": "video", "video": video_imgs})
# 处理图像输入
elif image is not None:
if next_image is not None:
pil_next_image = tensor_to_pil(next_image)
messages[0]["content"].insert(0, {
"type": "image",
"image": pil_next_image,
})
pil_image = tensor_to_pil(image)
messages[0]["content"].insert(0, {
"type": "image",
"image": pil_image,
})
# 准备输入
text = self.processor.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
image_inputs, video_inputs, video_kwargs = process_vision_info(messages, return_video_kwargs=True)
inputs = self.processor(
text=[text],
images=image_inputs,
videos=video_inputs,
padding=True,
return_tensors="pt",
**video_kwargs,
).to("cuda")
# 推理
try:
generated_ids = self.model.generate(**inputs, max_new_tokens=max_new_tokens)
generated_ids_trimmed = [
out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
]
result = self.processor.batch_decode(
generated_ids_trimmed,
skip_special_tokens=True,
clean_up_tokenization_spaces=False,
temperature=temperature,
)
except Exception as e:
print(f"Error during model inference: {str(e)}")
raise
if not keep_model_loaded:
del self.processor
del self.model
self.processor = None
self.model = None
torch.cuda.empty_cache()
torch.cuda.ipc_collect()
return result
################################### Qwen LLM ###################################
class QwenLLMRun:
def __init__(self):
self.model_checkpoint = None
self.tokenizer = None
self.model = None
# self.device = (
# torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
# )
# self.bf16_support = (
# torch.cuda.is_available()
# and torch.cuda.get_device_capability(self.device)[0] >= 8
# )
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": (
[
"Qwen2.5-3B-Instruct",
"Qwen2.5-3B-Instruct-Flux",
# "Qwen2.5-7B-Instruct",
# "Qwen2.5-7B-Instruct-Uncensored-Flux",
],
{"default": "Qwen2.5-3B-Instruct-Flux"},
),
"system": ("STRING", {
"multiline": True,
"default": "Act like a prompt engineer for Stable Diffusion. You need to give me the most accturate prompt for my input. Don't introduce your message, give ONLY the prompt. Prompt must be around 150 words."}),
"text": ("STRING", {"multiline": True, "default": "Give me a prompt for Stable Diffusion"}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"max_new_tokens": ("INT", {"default": 512, "min": 128, "max": 2048, "step": 1}),
"temperature": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01}),
"top_k": ("INT", {"default": 50, "min": 0}),
"top_p": ("FLOAT", {"default": 0.95, "min": 0.0, "max": 1.0, "step": 0.01}),
"keep_model_loaded": ("BOOLEAN", {"default": False}),
# "attention": (['flash_attention_2', 'sdpa', 'eager'], {"default": 'sdpa'}),
# "quantization": (
# ["none", "4bit", "8bit"],
# {"default": "none"},
# ),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate"
CATEGORY = "MW-OneButtonPrompt"
def generate(self,
model,
# attention,
# quantization,
system,
text,
seed,
max_new_tokens,
temperature,
top_k,
top_p,
keep_model_loaded
):
set_seed(hash_seed(seed))
self.model_checkpoint = os.path.join(llm_model_path, model)
if self.model is None:
# Load the model on the available device(s)
# if quantization == "4bit":
# quantization_config = BitsAndBytesConfig(
# load_in_4bit=True,
# bnb_4bit_quant_type="nf4", # 与 Unsloth 默认量化类型一致
# bnb_4bit_compute_dtype=torch.bfloat16
# )
# elif quantization == "8bit":
# quantization_config = BitsAndBytesConfig(
# load_in_8bit=True,
# )
# else:
# quantization_config = None
self.model = AutoModelForCausalLM.from_pretrained(
self.model_checkpoint,
torch_dtype="auto", # torch.bfloat16 if self.bf16_support else torch.float16,
device_map="auto",
# attn_implementation=attention,
# quantization_config = quantization_config,
)
if self.tokenizer is None:
self.tokenizer = AutoTokenizer.from_pretrained(self.model_checkpoint)
messages = [
{
"role": "system",
"content": system
},
{
"role": "user",
"content": text
}
]
text = self.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
model_inputs = self.tokenizer([text], return_tensors="pt").to(self.model.device)
generated_ids = self.model.generate(
model_inputs.input_ids,
max_new_tokens=max_new_tokens,
do_sample=True,
temperature=temperature,
top_k=top_k,
top_p=top_p,
attention_mask=model_inputs.attention_mask,
pad_token_id=self.tokenizer.eos_token_id
)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = self.tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
if not keep_model_loaded:
del self.model
del self.tokenizer
self.model = None
self.tokenizer = None
torch.cuda.empty_cache()
torch.cuda.ipc_collect()
return (response, )
################################## DeepSeekRone ##################################
class DeepseekRun:
ds_model_path = os.path.join(llm_model_path, "DeepScaleR-1.5B-Preview")
model_paths = {
"DeepScaleR-1.5B-Preview": ds_model_path, # 可以添加更多模型和路径
}
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": (list(cls.model_paths.keys()), {"tooltip": "models are expected to be in Comfyui/models/LLM folder"}),
"user_prompt": ("STRING", {"default": "", "multiline": True}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"max_tokens": ("INT", {"default": 1000, "min": 0, "max": 0xffffffffffffffff}),
"temperature": ("FLOAT", {"default": 1, "min": 0, "max": 2}),
"top_k": ("INT", {"default": 50, "min": 0, "max": 101}),
"top_p": ("FLOAT", {"default": 1, "min": 0, "max": 1}),
"unload_model": ("BOOLEAN", {
"default": False,
"tooltip": "If True, unload the model from memory after execution. Next execution will reload the model."}), # Added unload_model input
}
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("Prompt", "Think")
FUNCTION = "dsgen"
CATEGORY = "MW-OneButtonPrompt"
_model_cache = {}
def dsgen(self, model, user_prompt, seed=0, temperature=1.0, max_tokens=1000, top_k=25, top_p=1.0, unload_model=False):
if seed:
set_seed(hash_seed(seed))
match = re.search(r'{(.*?)}', user_prompt)
if match:
content_in_brackets = match.group(1)
# 按 "|" 拆分
options = content_in_brackets.split('|')
# 随机选择一个
chosen_option = random.choice(options).strip() # 使用 strip() 去除可能存在的首尾空格
# 替换原字符串 "{}" 中的内容
user_prompt = user_prompt.replace(match.group(0), chosen_option)
else:
user_prompt = user_prompt
messages = [
{"role": "user", "content": user_prompt},
]
model_dict = self.load_model(model)
if model_dict is None: # Check if model loading failed
return ("Error loading model. Check console.", )
device = model_dict["device"] # 获取设备信息
dsmodel = model_dict["dsmodel"]
tokenizer = model_dict["tokenizer"]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
# 构造输入时直接使用 device
model_inputs = tokenizer([text], return_tensors="pt").to(device)
generated_ids = dsmodel.generate(
**model_inputs,
max_new_tokens=max_tokens,
temperature=temperature,
top_k=top_k,
top_p=top_p,
do_sample=True
)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
responses = response.split("</think>")
if len(responses) < 2:
think = response.replace("<think>", "").strip()
answer = ""
else:
think = responses[0].replace("<think>", "").strip()
answer = responses[1].strip()
if unload_model: # Check if unload_model is True
self.unload_model_from_cache(model) # Unload the model if requested
print(f"DeepseekRun: Model '{model}' unloaded from cache.") # Inform user model is unloaded
return (answer, think,)
def load_model(self, model):
model_path = self.model_paths.get(model)
if model in self._model_cache:
return self._model_cache[model]
try:
dsmodel = AutoModelForCausalLM.from_pretrained(
model_path,
device_map="auto", # 自动分配
torch_dtype="auto",
low_cpu_mem_usage=True,
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained(model_path)
# 获取实际使用的设备
device = next(dsmodel.parameters()).device
model_info = {
"dsmodel": dsmodel,
"tokenizer": tokenizer,
"device": device
}
self._model_cache[model] = model_info
print(f"DeepseekRun: Model '{model}' loaded to cache.") # Inform user model is loaded
return model_info
except Exception as e:
print(f"DeepseekRun: Error loading model {model} from {model_path}: {e}")
return None
def unload_model_from_cache(self, model): # New method to unload model
if model in self._model_cache:
model_info = self._model_cache.pop(model) # Remove model from cache
del model_info # Optionally delete model_info dictionary to release references (may not be strictly needed in Python)
torch.cuda.empty_cache() # Clear CUDA cache to try and free VRAM
print(f"DeepseekRun: Model '{model}' removed from cache and CUDA cache cleared.") # Inform user of unload action
else:
print(f"DeepseekRun: Model '{model}' not found in cache, cannot unload.") # Inform user if model was not in cache
+40 -49
View File
@@ -3,6 +3,7 @@ import torch
import requests
from io import BytesIO
import os
import re
import numpy as np
import json
import random
@@ -37,6 +38,7 @@ def get_image_data_from_url(url, proxies=None):
except requests.exceptions.RequestException as e:
print(f"Error fetching URL: {url}. Error: {e}")
return None # Error during request
def pil2tensor(img):
output_images = []
output_masks = []
@@ -64,6 +66,12 @@ def pil2tensor(img):
return (output_image, output_mask)
def process_prompt(prompt):
result = re.sub(r'[sS]core_\w+\s*,?\s*|<.+?>\s*,?\s*', '', prompt, flags=re.DOTALL)
# 清理多余的空格和换行
result = re.sub(r'\s+', ' ', result).strip()
return result
def get_random_imginfo(data: dict, img_type: str, proxies=None):
"""
Selects and returns a random key-value pair from a dictionary.
@@ -76,23 +84,24 @@ def get_random_imginfo(data: dict, img_type: str, proxies=None):
selected item from the dictionary. Returns None if
the dictionary is empty.
"""
if not data:
return None # Return None if the dictionary is empty
if img_type == "Prompt":
if img_type == "Img+Prompt" or img_type == "OnlyPrompt":
data = data["Prompt"]
else:
data = {**data["Prompt"], **data["NoPrompt"]}
keys = list(data)
for i in range(6):
random_url = random.choice(keys)
imageinfo = get_image_data_from_url(random_url, proxies=proxies)
if imageinfo:
return imageinfo, data[random_url][1]
if img_type == "OnlyPrompt":
prompt = process_prompt(data[random.choice(keys)][1])
return None, prompt
else:
raise ValueError("Failed to find a valid image URL after 6 attempts.")
for i in range(6):
random_url = random.choice(keys)
imageinfo = get_image_data_from_url(random_url, proxies=proxies)
prompt = process_prompt(data[random_url][1])
if imageinfo:
return imageinfo, prompt
else:
raise ValueError("Failed to find a valid image URL after 6 attempts. Or the proxy settings are incorrect.")
class LoadImageInfoFromCivitai:
@@ -102,7 +111,7 @@ class LoadImageInfoFromCivitai:
def INPUT_TYPES(cls):
return {
"required": {
"output_type": (["Img", "Img+Prompt"], {"default": "Prompt"}),
"output_type": (["Img", "Img+Prompt", "OnlyPrompt"], {"default": "OnlyPrompt"}),
"nsfw": ("BOOLEAN", {
"default": False,
"tooltip": "When True, need `civit_nsfw.json` in the `txtfiles` folder, otherwise it is invalid"}),
@@ -134,49 +143,31 @@ class LoadImageInfoFromCivitai:
set_seed(self.hash_seed(seed))
proxy = None if proxy == "http://127.0.0.1:None" else proxy
img, prompt = self.load_json_file(output_type, nsfw, proxies={"https": proxy,"http": proxy,})
print(f"LoadImageInfoFromCivitai.load: load_json_file returned img type: {type(img)}") # Debug print
data = self.load_data_form_json(nsfw)
if img is None: # Check if get_image_data_from_url failed
print("LoadImageInfoFromCivitai.load: get_image_data_from_url returned None. Image download failed.") # Debug print
return (None, None, prompt)
img, prompt = get_random_imginfo(data, output_type, proxies={"https": proxy,"http": proxy,})
if img is None:
img = Image.new('RGB', (512, 512), color=(0, 0, 0))
# return (None, None, prompt)
img_out, mask_out = pil2tensor(img)
print(f"LoadImageInfoFromCivitai.load: pil2tensor returned img_out type: {type(img_out)}, mask_out type: {type(mask_out)}") # Debug print
if img_out is None or mask_out is None: # Check if pil2tensor failed
print("LoadImageInfoFromCivitai.load: pil2tensor returned None outputs. Image processing failed.") # Debug print
return (None, None, prompt) # Return None for IMAGE and MASK, and error message
return (img_out, mask_out, prompt)
def load_json_file(self, output_type, nsfw, proxies=None):
def load_data_form_json(self,nsfw):
if nsfw:
file_path = self.jsonfile_path + "/civit_nsfw.json"
if not os.path.exists(self.jsonfile_path + "/civit_nsfw.json"):
if not os.path.exists(file_path):
file_path = self.jsonfile_path + "/civit_sfw.json"
try:
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
print(f"Error loading JSON file: {file_path}. Error: {e}")
return None, None
if output_type == "Img+Prompt":
img, prompt = get_random_imginfo(data, "Prompt", proxies=proxies)
else:
img, prompt = get_random_imginfo(data, "Img", proxies=proxies)
if not nsfw:
try:
with open(self.jsonfile_path + "/civit_sfw.json", "r", encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
print(f"Error loading JSON file: {self.jsonfile_path + '/civit_sfw.json'}. Error: {e}")
return None, None
if output_type == "Img+Prompt":
img, prompt = get_random_imginfo(data, "Prompt", proxies=proxies)
else:
img, prompt = get_random_imginfo(data, "Img", proxies=proxies)
return img, prompt
else:
file_path = self.jsonfile_path + "/civit_sfw.json"
try:
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
return data
except Exception as e:
print(f"Error loading JSON file: {file_path}. Error: {e}")
raise
+5 -1
View File
@@ -126,18 +126,22 @@ class OneButtonPromptFlux:
return (generate_prompt(subject, pose, style, lora_trigger_or_prefix, refresh, test, seed),)
from .DeepSeekRone import DeepseekRun
from .DeepSeekRone_Qwen import DeepseekRun, QwenLLMRun, QwenVLRun
from .LoadCivitai import LoadImageInfoFromCivitai
NODE_CLASS_MAPPINGS = {
"DeepseekRun": DeepseekRun,
"QwenLLMRun": QwenLLMRun,
"QwenVLRun": QwenVLRun,
"OneButtonPromptFlux": OneButtonPromptFlux,
"LoadImageInfoFromCivitai": LoadImageInfoFromCivitai
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DeepseekRun": "Deepseek Run",
"QwenLLMRun": "Qwen LLM Run",
"QwenVLRun": "Qwen VL Run",
"OneButtonPromptFlux": "One Button Prompt Flux",
"LoadImageInfoFromCivitai": "Load Image Info From Civitai"
}
+19 -1
View File
@@ -4,7 +4,7 @@
---
# One Button Prompt for Flux in ComfyUI
# A node for one click generation of prompts (for image and video generation, etc.) in Comfyui
## Summary
@@ -12,6 +12,24 @@ This is a node for generating Flux prompts with one click in ComfyUI.
## 📣 Updates
[2025-03-01]⚒️:
- Supports Qwen2.5 language and visual models, can generate prompts, and can describe images and videos.
1. Generate prompts:
![](https://github.com/billwuhao/ComfyUI_OneButtonPrompt_Flux/blob/master/images/3bflux.png)
2. Two images are used to generate the video prompt:
![](https://github.com/billwuhao/ComfyUI_OneButtonPrompt_Flux/blob/master/images/nextimage.png)
3. Describe video (high memory usage, please load fewer frames):
![](https://github.com/billwuhao/ComfyUI_OneButtonPrompt_Flux/blob/master/images/video3b.png)
- Now it is possible to load `onlyprompt` from JSON files on C site without the need for internet connection.
![](https://github.com/billwuhao/ComfyUI_OneButtonPrompt_Flux/blob/master/images/onlyprompt.png)
Download the following 3 models to `models/LLM`:
- [Qwen2.5-VL-3B-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-3B-Instruct)
- [Qwen2.5-3B-Instruct](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct)
- [Qwen2.5-3B-Instruct-Flux](https://huggingface.co/mrkrak3n/Qwen2.5-3B-Instruct-Flux)
[2025-02-20]⚒️: Support [C](https://civitai.com/images) Station images and prompts.
![](https://github.com/billwuhao/ComfyUI_OneButtonPrompt_Flux/blob/master/images/2025-02-23_00-40-23.png)
+19 -1
View File
@@ -4,10 +4,28 @@
---
## 一个在 comfyui 中一键生成 flux 提示的节点.
## 一个在 comfyui 中一键生成提示 (用于图像和视频生成等) 的节点.
## 📣 更新
[2025-03-01]⚒️:
- 支持 Qwen2.5 语言和视觉模型, 可生成提示, 可反推图像和视频.
1. 生成提示:
![](https://github.com/billwuhao/ComfyUI_OneButtonPrompt_Flux/blob/master/images/3bflux.png)
2. 两张图片反推视频生成提示:
![](https://github.com/billwuhao/ComfyUI_OneButtonPrompt_Flux/blob/master/images/nextimage.png)
3. 反推视频(显存占用较大, 请加载较少的帧数):
![](https://github.com/billwuhao/ComfyUI_OneButtonPrompt_Flux/blob/master/images/video3b.png)
- 现在可以从 C 站 json 文件仅加载 prompt, 而无需联网.
![](https://github.com/billwuhao/ComfyUI_OneButtonPrompt_Flux/blob/master/images/onlyprompt.png)
下载以下 3 个模型到 `models/LLM`:
- [Qwen2.5-VL-3B-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-3B-Instruct)
- [Qwen2.5-3B-Instruct](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct)
- [Qwen2.5-3B-Instruct-Flux](https://huggingface.co/mrkrak3n/Qwen2.5-3B-Instruct-Flux)
[2025-02-20]⚒️: 支持 [C](https://civitai.com/images) 站图片及提示词.
![](https://github.com/billwuhao/ComfyUI_OneButtonPrompt_Flux/blob/master/images/2025-02-23_00-40-23.png)
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

+2 -2
View File
@@ -1,7 +1,7 @@
[project]
name = "onebuttonprompt_flux"
description = "ComfyUI_OneButtonPrompt_Flux is a Flux prompt generation node. The subject can be \"human\", \"other\" or a combination of both. For human, pose settings can be enabled. Additionally, various styles can be applied. Finally, combine it with \"Prompt Enhancement\" to seamlessly automate image generation, eliminating the hassle of designing prompts."
version = "3.0.0"
description = "A node for one click generation of prompts (for image and video generation, etc.) in Comfyui. 一个在 comfyui 中一键生成提示 (用于图像和视频生成等) 的节点"
version = "3.5.0"
license = {file = "LICENSE"}
[project.urls]
+6
View File
@@ -0,0 +1,6 @@
bitsandbytes
accelerate
qwen-vl-utils
optimum
transformers>=4.49
# git+https://github.com/huggingface/transformers
+97350 -246530
View File
File diff suppressed because one or more lines are too long