Files
alFrame-ComfyUI-AF-Pack-Pro…/nodes.py
T
2025-10-12 22:54:53 +02:00

280 lines
11 KiB
Python

# AF Prompt Nodes Pack - Combined Nodes
#
# Creator: Alex Furer - Co-Creator(s): Qwen3, Claude AI, DeepSeek
#
# Description: A collection of prompt management nodes for ComfyUI
#
# Repo: https://github.com/alFrame/ComfyUI-AF-Pack-Prompt-Nodes
#
# LICENSE: MIT License
#
# v1.0.0
import csv
import os
from datetime import datetime
import folder_paths
# =============================================================================
# AF Edit Generated Prompt Node
# =============================================================================
class AF_Edit_Generated_Prompt:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"generated_prompt": ("STRING", {
"multiline": True,
"default": "",
"readonly": True
}),
"manual_or_paste_generated": ("STRING", {
"multiline": True,
"default": ""
}),
},
"optional": {
"input_text": ("STRING", {
"multiline": True,
"default": "",
"forceInput": True
})
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "process"
CATEGORY = "AF - Nodes/AF - Prompt Pack"
OUTPUT_NODE = False
def process(self, generated_prompt="", manual_or_paste_generated="", input_text=""):
# Handle None values by converting to empty strings
generated_prompt = generated_prompt or ""
manual_or_paste_generated = manual_or_paste_generated or ""
input_text = input_text or ""
# Determine output - NO ERROR THROWING, just return the manual text or input
if manual_or_paste_generated.strip():
output = manual_or_paste_generated
elif input_text.strip():
output = input_text
else:
# No prompt provided - return empty string to allow continuation
output = ""
# Send data to JavaScript extension
display_text = input_text if input_text else "No input connected"
return {
"ui": {"generated_prompt": [str(display_text)]},
"result": (output,)
}
# =============================================================================
# AF Save Prompt History Node
# =============================================================================
class AF_Save_Prompt_History:
def __init__(self):
self.type = "AF Save Prompt History"
self.aux_id = "AF-SPH-001"
self.cnr_id = "prompt-save-history"
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"directory": ("STRING", {"default": "AF-PromptHistory", "multiline": False}),
"filename": ("STRING", {"default": "prompt_history_4", "multiline": False}),
"project": ("STRING", {"default": "default", "multiline": False}),
},
"optional": {
"global_positive": ("STRING", {"forceInput": True}),
"global_negative": ("STRING", {"forceInput": True}),
"local_positive": ("STRING", {"forceInput": True}),
# Removed local_negative as requested
},
}
RETURN_TYPES = ("STRING", "STRING", "STRING")
RETURN_NAMES = ("global_positive", "global_negative", "local_positive")
FUNCTION = "save_prompts"
OUTPUT_NODE = True
CATEGORY = "AF - Nodes/AF - Prompt Pack"
def save_prompts(self, directory, filename, project, global_positive="", global_negative="", local_positive=""):
csv_filename = filename.strip() + ".csv"
try:
output_dir = folder_paths.get_output_directory()
except Exception as e:
my_dir = os.path.dirname(os.path.abspath(__file__))
comfyui_root = os.path.dirname(os.path.dirname(my_dir))
output_dir = os.path.join(comfyui_root, "output")
library_path = os.path.join(output_dir, directory.strip())
try:
os.makedirs(library_path, exist_ok=True)
except Exception as e:
return (global_positive, global_negative, local_positive)
csv_file_path = os.path.join(library_path, csv_filename)
file_exists = os.path.exists(csv_file_path)
try:
with open(csv_file_path, "a", newline='', encoding='utf-8') as csvfile:
# Updated fieldnames - removed local_negative
fieldnames = ['timestamp', 'project', 'global_positive', 'global_negative', 'local_positive']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
if not file_exists:
writer.writeheader()
writer.writerow({
'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'project': project.strip(),
'global_positive': global_positive.replace('\n', '\\n') if global_positive else '',
'global_negative': global_negative.replace('\n', '\\n') if global_negative else '',
'local_positive': local_positive.replace('\n', '\\n') if local_positive else '',
})
except Exception as e:
pass
return (global_positive, global_negative, local_positive)
# =============================================================================
# AF Load Prompt History Node (Enhanced with dropdowns)
# =============================================================================
class AF_Load_Prompt_History:
def __init__(self):
self.type = "AF Load Prompt History"
self.aux_id = "AF-LPH-001"
self.cnr_id = "prompt-load-history"
@classmethod
def INPUT_TYPES(s):
# Get initial data for dropdowns
projects = s.get_available_projects()
return {
"required": {
"directory": ("STRING", {"default": "AF-PromptHistory", "multiline": False}),
"filename": ("STRING", {"default": "prompt_history_4", "multiline": False}),
"project": (projects, {"default": projects[0] if projects else ""}),
"global_positive": (["Loading..."], {"default": "Loading..."}),
"global_negative": (["Loading..."], {"default": "Loading..."}),
"local_positive": (["Loading..."], {"default": "Loading..."}),
},
}
RETURN_TYPES = ("STRING", "STRING", "STRING")
RETURN_NAMES = ("global_positive", "global_negative", "local_positive")
FUNCTION = "load_prompts"
CATEGORY = "AF - Nodes/AF - Prompt Pack"
OUTPUT_NODE = True
@classmethod
def get_available_projects(cls, directory="AF-PromptHistory", filename="prompt_history_4"):
"""Get list of unique projects from CSV file"""
try:
output_dir = folder_paths.get_output_directory()
except:
my_dir = os.path.dirname(os.path.abspath(__file__))
comfyui_root = os.path.dirname(os.path.dirname(my_dir))
output_dir = os.path.join(comfyui_root, "output")
library_path = os.path.join(output_dir, directory.strip())
csv_file_path = os.path.join(library_path, filename.strip() + ".csv")
if not os.path.exists(csv_file_path):
return ["No projects found"]
projects = set()
try:
with open(csv_file_path, 'r', newline='', encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
project = row.get('project', '').strip()
if project:
projects.add(project)
except Exception as e:
return [f"Error: {str(e)}"]
project_list = sorted(list(projects))
return project_list if project_list else ["No projects found"]
@classmethod
def get_prompts_by_type(cls, prompt_type, directory="AF-PromptHistory", filename="prompt_history_4", project_filter=""):
"""Get prompts of specific type, optionally filtered by project"""
try:
output_dir = folder_paths.get_output_directory()
except:
my_dir = os.path.dirname(os.path.abspath(__file__))
comfyui_root = os.path.dirname(os.path.dirname(my_dir))
output_dir = os.path.join(comfyui_root, "output")
library_path = os.path.join(output_dir, directory.strip())
csv_file_path = os.path.join(library_path, filename.strip() + ".csv")
if not os.path.exists(csv_file_path):
return ["No prompts found"]
prompts = set()
try:
with open(csv_file_path, 'r', newline='', encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
# Filter by project if specified
if project_filter and row.get('project', '').strip() != project_filter:
continue
prompt = row.get(prompt_type, '').strip()
if prompt:
# Restore newlines from CSV storage
prompt = prompt.replace('\\n', '\n')
# Truncate very long prompts for dropdown display
if len(prompt) > 100:
display_prompt = prompt[:97] + "..."
else:
display_prompt = prompt
prompts.add((prompt, display_prompt))
except Exception as e:
return [f"Error: {str(e)}"]
# Convert to list and sort by display text
prompt_list = sorted(list(prompts), key=lambda x: x[1])
# Return as simple list of display texts for dropdown
return [display for _, display in prompt_list] if prompt_list else ["No prompts found"]
def load_prompts(self, directory, filename, project, global_positive, global_negative, local_positive):
"""
Return the selected prompts
"""
# For now, just return the selected values
# The actual prompt text is what was selected in the dropdown
return (global_positive, global_negative, local_positive)
@classmethod
def IS_CHANGED(cls, **kwargs):
return float("NaN")
# =============================================================================
# Node Mappings
# =============================================================================
NODE_CLASS_MAPPINGS = {
"AF_Edit_Generated_Prompt": AF_Edit_Generated_Prompt,
"AF_Save_Prompt_History": AF_Save_Prompt_History,
"AF_Load_Prompt_History": AF_Load_Prompt_History,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"AF_Edit_Generated_Prompt": "AF - Edit Generated Prompt",
"AF_Save_Prompt_History": "AF - Save Prompt History",
"AF_Load_Prompt_History": "AF - Load Prompt History",
}