Files
fr0nky0ng-ComfyUI-JSON-Prompt/nodes.py
T
2025-11-05 13:50:54 +08:00

220 lines
8.7 KiB
Python

import json
import os
import re
import copy
'''
import google.generativeai as genai # 導入 Google AI 函式庫
'''
# ==============================================================================
# 步驟 1-3: 加載並提取 config.json 內容
# ==============================================================================
# 獲取 nodes.py 所在的文件夾路徑
current_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(current_dir, "config.json")
# 嘗試加載 config.json
try:
with open(config_path, 'r', encoding='utf-8') as f:
CONFIG_DATA = json.load(f)
except FileNotFoundError:
print(
f"[JSONTemplateGenerator Error] config.json not found at: {config_path}")
CONFIG_DATA = {"system_prompt": "ERROR: Configuration file not found.",
"template": {}, "camera_settings": {}}
except json.JSONDecodeError:
print("[JSONTemplateGenerator Error] config.json is invalid JSON.")
CONFIG_DATA = {"system_prompt": "ERROR: Configuration file is invalid JSON.",
"template": {}, "camera_settings": {}}
# 提取 system_prompt
SYSTEM_PROMPT = CONFIG_DATA.get("system_prompt", "System Prompt Missing.")
QUALITY_TARGETS = [
"accurate limb lengths and joint angles",
"correct finger count and articulation",
"realistic fabric tension and folds",
"accurate winking expression"
]
NEGATIVE_PROMPT = [
"accurate limb lengths and joint angles",
"correct finger count and articulation",
"realistic fabric tension and folds",
"accurate winking expression"
]
# 提取 template 字典並將其轉換為帶有縮排的 JSON 字符串,用於代碼塊顯示
TEMPLATE_DICT = CONFIG_DATA.get("template", {})
CAMERA_SETTINGS = CONFIG_DATA.get("camera_settings", {})
# 提取鏡頭選項 (轉換為 f/mm 格式的字符串列表,並在開頭添加默認選項)
LENS_OPTIONS = ["SET BY AI"] + \
[f"{item['type']} ({item['focal_length_mm']}mm)" for item in CAMERA_SETTINGS.get(
"lens", [])]
# 提取光圈選項 (轉換為字符串列表,並在開頭添加默認選項)
APERTURE_OPTIONS = ["SET BY AI"] + \
[f"f/{f}" for f in CAMERA_SETTINGS.get("exposure",
{}).get("aperture_f", [])]
# 提取測光選項
METERING_OPTIONS = ["SET BY AI"] + \
CAMERA_SETTINGS.get("exposure", {}).get("metering", [])
# 提取方向選項
ORIENTATION_OPTIONS = ["SET BY AI"] + \
CAMERA_SETTINGS.get("framing", {}).get("orientation", [])
# 提取裁剪選項
CROP_OPTIONS = ["SET BY AI"] + \
CAMERA_SETTINGS.get("framing", {}).get("crop", [])
# 提取角度選項
ANGLE_OPTIONS = ["SET BY AI"] + \
CAMERA_SETTINGS.get("framing", {}).get("angle", [])
class JSONPromptGenerator:
"""
ComfyUI 自訂義節點:用於生成包含用戶描述、System Prompt 和 JSON 模板的完整提示字串。
"""
# 節點的元數據
NODE_NAME = "JSON_Prompt_Generator"
CATEGORY = "Utils/Text"
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt_to_llm",)
FUNCTION = "generate_prompt"
# 將提取的常量作為類別屬性,供方法調用
SYSTEM_PROMPT = SYSTEM_PROMPT
TEMPLATE_DICT = TEMPLATE_DICT
@classmethod
def INPUT_TYPES(s):
"""
步驟 1: 定義輸入和文本輸入框的 placeholder。
"""
return {
"required": {
"text_description": ("STRING", {
"multiline": True, # 設定為多行文本框
"default": "",
"placeholder": "Describe the image you want to generate using the natural language you are best at (such as Chinese)."
}),
"lens": (LENS_OPTIONS, {"default": "SET BY AI"}),
"aperture": (APERTURE_OPTIONS, {"default": "SET BY AI"}),
"metering": (METERING_OPTIONS, {"default": "SET BY AI"}),
"orientation": (ORIENTATION_OPTIONS, {"default": "SET BY AI"}),
"crop": (CROP_OPTIONS, {"default": "SET BY AI"}),
"angle": (ANGLE_OPTIONS, {"default": "SET BY AI"}),
},
}
def generate_prompt(self, text_description, lens, aperture, metering, orientation, crop, angle):
"""
步驟 4: 按照指定順序組合字符串並輸出。
"""
user_input = text_description.strip()
# 將可能修改後的模板轉換為 JSON 字符串
template_str = json.dumps(self.TEMPLATE_DICT, indent=4, ensure_ascii=False)
# 構建用戶選擇的 JSON 結構
user_config_json = {}
# 處理 Lens 選擇
if lens != "SET BY AI":
# 從字符串中解析 type 和 focal_length_mm
match = re.search(r'(.+?)\s\((.+?)mm\)', lens)
if match:
lens_type = match.group(1).strip()
focal_length_mm = int(match.group(2))
user_config_json.setdefault(
"camera", {}).setdefault("lens", {})
user_config_json["camera"]["lens"]["type"] = lens_type
user_config_json["camera"]["lens"]["focal_length_mm"] = focal_length_mm
# 處理 Aperture 選擇
if aperture != "SET BY AI":
# f/X.X 格式
aperture_f = float(aperture.replace("f/", ""))
user_config_json.setdefault(
"camera", {}).setdefault("exposure", {})
user_config_json["camera"]["exposure"]["aperture_f"] = aperture_f
# 處理 Metering 選擇
if metering != "SET BY AI":
user_config_json.setdefault(
"camera", {}).setdefault("exposure", {})
user_config_json["camera"]["exposure"]["metering"] = metering
# 處理 Orientation 選擇
if orientation != "SET BY AI":
user_config_json.setdefault("camera", {}).setdefault("framing", {})
user_config_json["camera"]["framing"]["orientation"] = orientation
# 處理 Crop 選擇
if crop != "SET BY AI":
user_config_json.setdefault("camera", {}).setdefault("framing", {})
user_config_json["camera"]["framing"]["crop"] = crop
# 處理 Angle 選擇
if angle != "SET BY AI":
user_config_json.setdefault("camera", {}).setdefault("framing", {})
user_config_json["camera"]["framing"]["angle"] = angle
# 將用戶配置轉換為 JSON 字符串,如果非空
user_config_str = ""
if user_config_json:
user_config_str = f"Please include the following specific camera settings in your generated JSON:\n```json\n{json.dumps(user_config_json, indent=4, ensure_ascii=False)}\n```\n"
# 組合最終輸出字串: 輸入文本 + 用戶配置 JSON + system_prompt + 模板
template_block = f"\n\n```json\n{template_str}\n```\n"
output_string = (
f"{user_input}\n\n"
f"{user_config_str}\n\n"
f"{self.SYSTEM_PROMPT}"
f"{template_block}"
)
return (output_string,)
class FormatLLMOutput:
NODE_NAME = "Format_LLM_Output"
CATEGORY = "Utils/Text"
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("cleaned_json_string",)
FUNCTION = "format_output"
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"llm_output": ("STRING", {"forceInput": True}),
"include_quality_prompt": ("BOOLEAN", {"default": False, "label_on": "Yes", "label_off": "No"}),
"include_negative_prompt": ("BOOLEAN", {"default": False, "label_on": "Yes", "label_off": "No"}),
},
}
def format_output(self, llm_output, include_quality_prompt, include_negative_prompt):
cleaned_text = ""
if not llm_output or not isinstance(llm_output, str):
# 應該接收到 STRING 類型的輸入
return ("",)
if llm_output.strip().startswith("{") and llm_output.strip().endswith("}"):
cleaned_text = llm_output.strip()
else:
# 正则表达式:匹配 ```json 开头(允许空白和换行),捕获中间内容,到 ``` 结尾
pattern = r'```json\s*\n?(.*?)\n?\s*```'
match = re.search(pattern, llm_output, re.DOTALL | re.IGNORECASE)
if match:
cleaned_text = match.group(1).strip()
prompt_dict = json.loads(cleaned_text)
if include_quality_prompt:
prompt_dict["quality_targets"] = QUALITY_TARGETS
if include_negative_prompt:
prompt_dict["negative_prompt"] = NEGATIVE_PROMPT
cleaned_text = json.dumps(prompt_dict, indent=4, ensure_ascii=False)
# 最終再次清理首尾空格和換行符
final_output = cleaned_text.strip()
return (final_output,)