Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8d0378eff | ||
|
|
a269cc7c10 | ||
|
|
d30adb0991 | ||
|
|
a6e26965dc | ||
|
|
c1fda57913 | ||
|
|
2e86d327b0 | ||
|
|
5e35b2899d | ||
|
|
3e6c897e8a | ||
|
|
543e86c153 | ||
|
|
5ab3ed1d75 | ||
|
|
53a487f8ee | ||
|
|
e595e8e8b1 | ||
|
|
704c059b3f | ||
|
|
dcba492d07 | ||
|
|
da3be65a5a | ||
|
|
f32a1cb55e | ||
|
|
d9e2846ecd | ||
|
|
3ffae39bb3 | ||
|
|
aba688dbd6 |
@@ -0,0 +1,4 @@
|
||||
__pycache__/__init__.cpython-310.pyc
|
||||
__pycache__/local_gguf_llm_connector.cpython-310.pyc
|
||||
__pycache__/local_llm_connector.cpython-310.pyc
|
||||
__pycache__/local_prompt_generator.cpython-310.pyc
|
||||
@@ -1,6 +1,6 @@
|
||||
# ComfyUI_LocalLLMNodes
|
||||
|
||||
A custom node pack for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) to run Large Language Models (LLMs) locally for prompt generation and text tasks within ComfyUI workflows.
|
||||
A custom node pack for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) to run Large Language Models (LLMs) locally for flux kontext dev prompt generation within ComfyUI workflows.
|
||||
|
||||
**Purpose:**
|
||||
This pack is designed to simplify the creation of detailed, high-quality prompts for advanced image generation models like **Flux Kontext Dev**. It achieves this by using a **locally running LLM** (Large Language Model) to process:
|
||||
@@ -128,4 +128,4 @@ Running large LLMs alongside large image models can strain resources.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
This node pack builds upon concepts from the excellent [ComfyUI-MieNodes](https://github.com/MieMieeeee/ComfyUI-MieNodes) extension.
|
||||
This node pack builds upon concepts from the excellent [ComfyUI-MieNodes](https://github.com/MieMieeeee/ComfyUI-MieNodes) extension.
|
||||
|
||||
+9
-5
@@ -55,6 +55,8 @@ if GGUF_DEPS_AVAILABLE:
|
||||
else:
|
||||
print("[ComfyUI_LocalLLMNodes] GGUF LLM Connector node will NOT be available due to missing dependency (llama-cpp-python).")
|
||||
|
||||
from .arabic_product_node import ArabicProductDescriptionGenerator
|
||||
|
||||
# --- Define Mappings ---
|
||||
# Populate the dictionaries based on which nodes were successfully imported.
|
||||
|
||||
@@ -65,12 +67,14 @@ if LOCAL_LLM_NODES_AVAILABLE:
|
||||
"LocalKontextPromptGenerator": LocalKontextPromptGenerator,
|
||||
"AddUserLocalKontextPreset": AddUserLocalKontextPreset,
|
||||
"RemoveUserLocalKontextPreset": RemoveUserLocalKontextPreset,
|
||||
"ArabicProductDescriptionGenerator": ArabicProductDescriptionGenerator,
|
||||
})
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update({
|
||||
"SetLocalLLMServiceConnector": "Set Local LLM Service Connector 🐑 (HuggingFace)",
|
||||
"LocalKontextPromptGenerator": "Local Kontext Prompt Generator 🐑",
|
||||
"AddUserLocalKontextPreset": "Add User Local Kontext Preset 🐑",
|
||||
"RemoveUserLocalKontextPreset": "Remove User Local Kontext Preset 🐑",
|
||||
"SetLocalLLMServiceConnector": "Set Local LLM Service Connector (HuggingFace)",
|
||||
"LocalKontextPromptGenerator": "Local Kontext Prompt Generator",
|
||||
"AddUserLocalKontextPreset": "Add User Local Kontext Preset",
|
||||
"RemoveUserLocalKontextPreset": "Remove User Local Kontext Preset",
|
||||
"ArabicProductDescriptionGenerator": "Arabic Product Description Generator"
|
||||
})
|
||||
|
||||
# Add GGUF based nodes if available
|
||||
@@ -79,7 +83,7 @@ if LOCAL_GGUF_NODES_AVAILABLE:
|
||||
"SetLocalGGUFLLMServiceConnector": SetLocalGGUFLLMServiceConnector,
|
||||
})
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update({
|
||||
"SetLocalGGUFLLMServiceConnector": "Set Local GGUF LLM Service Connector 🐑",
|
||||
"SetLocalGGUFLLMServiceConnector": "Set Local GGUF LLM Service Connector",
|
||||
})
|
||||
|
||||
# --- Define what ComfyUI sees ---
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
# arabic_product_node.py
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import json
|
||||
|
||||
# --- Simple logging utility ---
|
||||
def log(message):
|
||||
print(f"[ArabicProductDesc] {message}")
|
||||
|
||||
# --- Paths and Preset Setup (Same as original) ---
|
||||
script_directory = os.path.dirname(os.path.abspath(__file__))
|
||||
USER_PRESETS_FILE = os.path.join(script_directory, "user_kontext_presets.json")
|
||||
|
||||
# --- Built-in Presets for Arabic Product Descriptions ---
|
||||
ARABIC_PRODUCT_PRESETS = {
|
||||
"عام - وصفي": {
|
||||
"system": (
|
||||
"أنت خبير تسويق إلكتروني. اكتب وصفًا احترافيًا باللغة العربية للمنتج بناءً على الوصف المرئي أو المعلومات المقدمة. "
|
||||
"يجب أن يتضمن الوصف: اسم المنتج (إن عُرف)، المكونات أو المواصفات، الفوائد الرئيسية، طريقة الاستخدام، الفئة المستهدفة، وأي ملاحظات أمان. "
|
||||
"استخدم لغة تسويقية جذابة، واضحة، وسلسة. لا تستخدم تنسيق Markdown أو عناوين فرعية. لا تضيف تعليقات خارجية."
|
||||
)
|
||||
},
|
||||
"عناية بالبشرة": {
|
||||
"system": (
|
||||
"أنت أخصائي عناية بالبشرة. اكتب وصفًا عربيًا احترافيًا لكريم أو سيروم بناءً على الوصف المرئي أو التقني. "
|
||||
"ركّز على المكونات الفعالة (مثل SPF، حمض الهيالورونيك)، الفوائد (ترطيب، حماية، تفتيح)، ومدى ملاءمته لأنواع البشرة. "
|
||||
"اذكر طريقة الاستخدام (قبل النوم، بعد التنظيف، إلخ)، وكم مرة يُستخدم. استخدم لغة جذابة لكن علمية."
|
||||
)
|
||||
},
|
||||
"طعام ومشروبات": {
|
||||
"system": (
|
||||
"أنت كاتب محتوى غذائي. اكتب وصفًا شهيًا وملهمًا بالعربية لمنتج غذائي أو شراب. "
|
||||
"صف الطعم، القوام، المكونات الطبيعية، القيمة الغذائية، ومتى يُفضل تناوله (صباحًا، بعد التمرين). "
|
||||
"استخدم لغة حسية ومشوقة. لا تبالغ. لا تستخدم تنسيق."
|
||||
)
|
||||
},
|
||||
"إلكترونيات": {
|
||||
"system": (
|
||||
"أنت خبير تقني. اكتب وصفًا عربيًا واضحًا لجهاز إلكتروني (مثل سماعة، شاحن، ساعة ذكية). "
|
||||
"اذكر المواصفات الأساسية (البطارية، التوصيل، الجودة)، الفوائد (راحة، دقة، كفاءة)، والجمهور المستهدف. "
|
||||
"اجعله سهل الفهم للمستخدم العادي مع لمسة احترافية."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def load_user_presets():
|
||||
"""Load user-defined presets from JSON file."""
|
||||
if os.path.exists(USER_PRESETS_FILE):
|
||||
try:
|
||||
with open(USER_PRESETS_FILE, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
log(f"Error loading user presets: {e}")
|
||||
return {}
|
||||
return {}
|
||||
|
||||
def get_all_product_presets():
|
||||
"""Get combined built-in and user-defined presets."""
|
||||
all_presets = ARABIC_PRODUCT_PRESETS.copy()
|
||||
user_presets = load_user_presets()
|
||||
all_presets.update(user_presets)
|
||||
return all_presets
|
||||
|
||||
# --- Category for UI ---
|
||||
PRODUCT_DESC_CATEGORY = "Local LLM Nodes/Product Description (Arabic)"
|
||||
|
||||
|
||||
class ArabicProductDescriptionGenerator:
|
||||
"""
|
||||
Generates a detailed Arabic product description using:
|
||||
- Optional image description
|
||||
- User instruction in Arabic
|
||||
- Selected preset (system prompt)
|
||||
- Local LLM via LLMServiceConnector
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
all_presets = get_all_product_presets()
|
||||
default_preset = next(iter(all_presets.keys()), "") if all_presets else ""
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"llm_service_connector": ("LLMServiceConnector",),
|
||||
"user_instruction_ar": ("STRING", {
|
||||
"default": "اكتب وصفًا تفصيليًا لهذا المنتج",
|
||||
"multiline": True,
|
||||
"tooltip": "التعليمات من المستخدم بالعربية، مثلاً: اكتب وصفًا تسويقيًا"
|
||||
}),
|
||||
"preset": (list(all_presets.keys()), {
|
||||
"default": default_preset,
|
||||
"tooltip": "اختر نمط الوصف (عام، عناية بالبشرة، إلخ)"
|
||||
}),
|
||||
"seed": ("INT", {
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 0xffffffffffffffff,
|
||||
"control_after_generate": True
|
||||
})
|
||||
},
|
||||
"optional": {
|
||||
"image_description": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"tooltip": "وصف الصورة أو التفاصيل التقنية عن المنتج (اختياري)"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("arabic_product_description",)
|
||||
FUNCTION = "generate_description"
|
||||
CATEGORY = PRODUCT_DESC_CATEGORY
|
||||
|
||||
def generate_description(self, llm_service_connector, user_instruction_ar, preset, seed=None, image_description=None):
|
||||
# Load preset
|
||||
all_presets = get_all_product_presets()
|
||||
preset_data = all_presets.get(preset)
|
||||
if not preset_data:
|
||||
raise ValueError(f"النمط غير معروف: {preset}")
|
||||
|
||||
# Safe string conversion
|
||||
def safe_str(s):
|
||||
return str(s) if s is not None else ""
|
||||
|
||||
img_desc = safe_str(image_description).strip()
|
||||
user_inst = safe_str(user_instruction_ar).strip()
|
||||
|
||||
# Build user content
|
||||
user_content_parts = []
|
||||
if img_desc:
|
||||
user_content_parts.append(f"وصف المنتج أو الصورة: {img_desc}")
|
||||
if user_inst:
|
||||
user_content_parts.append(f"التعليمات: {user_inst}")
|
||||
else:
|
||||
user_content_parts.append("التعليمات: اكتب وصفًا تفصيليًا للمنتج.")
|
||||
|
||||
user_content = " ".join(user_content_parts).strip()
|
||||
if not user_content:
|
||||
user_content = "وصف المنتج: منتج على خلفية بيضاء. التعليمات: اكتب وصفًا تسويقيًا بالعربية."
|
||||
|
||||
# Build messages
|
||||
messages = [
|
||||
{"role": "system", "content": preset_data["system"]},
|
||||
{"role": "user", "content": user_content}
|
||||
]
|
||||
|
||||
# Call LLM
|
||||
try:
|
||||
response = llm_service_connector.invoke(messages, seed=seed)
|
||||
return (response.strip(),)
|
||||
except Exception as e:
|
||||
error_msg = f"[خطأ] تعذر إنشاء الوصف: {str(e)}"
|
||||
log(error_msg)
|
||||
return (error_msg,)
|
||||
|
||||
def is_changed(self, llm_service_connector, user_instruction_ar, preset, seed, image_description=None):
|
||||
"""Ensure node re-runs only when inputs change."""
|
||||
try:
|
||||
hasher = hashlib.md5()
|
||||
hasher.update(str(user_instruction_ar).encode('utf-8'))
|
||||
hasher.update(preset.encode('utf-8'))
|
||||
hasher.update(str(seed).encode('utf-8'))
|
||||
hasher.update(str(image_description).encode('utf-8'))
|
||||
|
||||
# Add preset system prompt
|
||||
all_presets = get_all_product_presets()
|
||||
preset_data = all_presets.get(preset)
|
||||
if preset_data and "system" in preset_data:
|
||||
hasher.update(preset_data["system"].encode('utf-8'))
|
||||
|
||||
# Add connector state
|
||||
try:
|
||||
connector_state = llm_service_connector.get_state()
|
||||
except AttributeError:
|
||||
connector_state = str(llm_service_connector)
|
||||
hasher.update(connector_state.encode('utf-8'))
|
||||
|
||||
return hasher.hexdigest()
|
||||
except Exception as e:
|
||||
log(f"is_changed error: {e}")
|
||||
return float("nan")
|
||||
@@ -1,19 +1,61 @@
|
||||
# ComfyUI/custom_nodes/ComfyUI_LocalLLMNodes/local_gguf_llm_connector.py
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import platform
|
||||
import folder_paths
|
||||
# --- Library for GGUF local LLM inference ---
|
||||
|
||||
try:
|
||||
import llama_cpp
|
||||
# import llama_cpp.llama_tokenizer # Optional, remove if not used
|
||||
LLAMA_CPP_AVAILABLE = True
|
||||
except ImportError:
|
||||
# --- Simple logging utility for this package (reuse existing one) ---
|
||||
# Import here to avoid potential issues if log isn't defined yet if imported at top in case of circular import risks,
|
||||
# though usually safe. Ensure local_llm_connector.py defines 'log' early.
|
||||
from .local_llm_connector import log
|
||||
log("[LocalGGUFLLMConnector] Warning: llama-cpp-python library not found.")
|
||||
LLAMA_CPP_AVAILABLE = False
|
||||
llama_cpp = None
|
||||
|
||||
def check_nvidia_gpu():
|
||||
try:
|
||||
result = subprocess.run(['nvidia-smi'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
return result.returncode == 0
|
||||
except (FileNotFoundError, OSError):
|
||||
return False
|
||||
|
||||
def check_apple_metal():
|
||||
return platform.system() == "Darwin" and "arm" in platform.machine().lower()
|
||||
|
||||
log("[LocalGGUFLLMConnector] llama-cpp-python not found. Attempting auto-install...")
|
||||
|
||||
# Detect hardware
|
||||
cmake_args = []
|
||||
if check_nvidia_gpu():
|
||||
log("NVIDIA GPU detected: Enabling CUDA acceleration.")
|
||||
cmake_args.append("-DLLAMA_CUBLAS=on")
|
||||
elif check_apple_metal():
|
||||
log("Apple Silicon (M1/M2/M3) detected: Enabling Metal acceleration.")
|
||||
cmake_args.append("-DLLAMA_METAL=on")
|
||||
else:
|
||||
log("💻 No compatible GPU found: Installing CPU-only version.")
|
||||
|
||||
# Build pip install command
|
||||
cmd = [sys.executable, "-m", "pip", "install", "llama-cpp-python"]
|
||||
if cmake_args:
|
||||
cmd += [f"--config-settings=cmake_args={';'.join(cmake_args)}"]
|
||||
cmd += ["--force-reinstall", "--no-cache-dir"]
|
||||
|
||||
try:
|
||||
log(f"📦 Running: {' '.join(cmd)}")
|
||||
subprocess.check_call(cmd)
|
||||
log("llama-cpp-python installed successfully!")
|
||||
|
||||
# Force reload after install
|
||||
import importlib
|
||||
import llama_cpp
|
||||
importlib.reload(llama_cpp)
|
||||
LLAMA_CPP_AVAILABLE = True
|
||||
except subprocess.CalledProcessError as e:
|
||||
log(f"Installation failed: {e}")
|
||||
LLAMA_CPP_AVAILABLE = False
|
||||
except Exception as e:
|
||||
log(f"Unexpected error during install: {e}")
|
||||
LLAMA_CPP_AVAILABLE = False
|
||||
|
||||
# Reuse the logging function from local_llm_connector
|
||||
from .local_llm_connector import log # Ensure this is the final import for log
|
||||
|
||||
@@ -16,7 +16,7 @@ USER_PRESETS_FILE = os.path.join(script_directory, "user_kontext_presets.json")
|
||||
|
||||
# --- Replicate Built-in Presets (from MieNodes' prompt_generator.py) ---
|
||||
KONTEXT_PRESETS = {
|
||||
"Kid Clothes -> Flux Prompt": {
|
||||
"Kid Clothes": {
|
||||
"system": (
|
||||
"You are an expert prompt generator for Flux Kontext. Your task is to place clothing described by the user onto a baby or child with Arab facial features, while preserving all material, color, and style details from the original clothing description. "
|
||||
"Input: "
|
||||
@@ -36,7 +36,7 @@ KONTEXT_PRESETS = {
|
||||
"A photorealistic Arab baby with round cheeks and light tan skin, lying on a soft white fleece blanket, wearing a pale blue cotton romper with stitched white cloud patterns and integrated footies, captured in soft natural lighting with shallow DOF, cozy nursery ambiance, 8K resolution, cinematic baby fashion catalog style."
|
||||
)
|
||||
},
|
||||
"Women’s Clothes -> Flux Prompt": {
|
||||
"Women’s Clothes": {
|
||||
"system": (
|
||||
"You are an expert visual prompt composer for Flux Kontext. Your job is to place the clothing described by the user onto a realistic Muslim woman model in her 20s with Arab features, while preserving all original garment details. "
|
||||
"Input: "
|
||||
@@ -56,7 +56,7 @@ KONTEXT_PRESETS = {
|
||||
"A young Arab Muslim woman in her 20s with soft olive skin and delicate features, wearing a beige linen abaya with subtle gold-stitched cuffs and a loosely tied waist sash, standing in a minimalist white studio under soft diffused lighting, side-facing pose with natural folds in the fabric, fashion editorial style with cinematic shallow DOF."
|
||||
)
|
||||
},
|
||||
"Beauty Product Use -> Flux Prompt": {
|
||||
"Beauty Product Use": {
|
||||
"system": (
|
||||
"You are a creative image prompt generator for Flux Kontext focused on health, skincare, and beauty products. Your job is to place the described product in an artistic or commercial setting — either used by a model (Arab man or woman), or placed within a creative, beauty-inspired scene — while preserving all visual and material details of the product. "
|
||||
"Input: "
|
||||
@@ -75,7 +75,7 @@ KONTEXT_PRESETS = {
|
||||
"An elegant Arab woman with natural tan skin and voluminous dark hair, holding an amber glass dropper bottle with a gold label and black cap, delicately applying serum to her cheek in front of a glowing vanity mirror, soft candle-lit ambiance, subtle shadows, cinematic close-up, bokeh background, luxury skincare ad style."
|
||||
)
|
||||
},
|
||||
"Beauty Product Display -> Flux Prompt": {
|
||||
"Beauty Product Display": {
|
||||
"system": (
|
||||
"You are an expert in luxury beauty product visuals for Flux Kontext. Place the described product in a stunning, creative, brand-inspired environment (without any models), using beauty aesthetics and visual storytelling. "
|
||||
"Input: "
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
[project]
|
||||
name = "LocalLLMnodes"
|
||||
description = "Run local LLMs in ComfyUI and generate Flux Kontext.dev prompts from simple English descriptions"
|
||||
readme = "README.md"
|
||||
version = "0.1.0"
|
||||
license = { text = "MIT" }
|
||||
dependencies = ["# Core dependencies for Hugging Face transformers based nodes (e.g., Hermes-2-Pro-Llama-3-8B)", "transformers>=4.38.0 # Or a specific version you know works", "torch>=2.0.0 # Or a specific version you know works", "# Optional, for quantization with transformers (e.g., 4-bit)", "# bitsandbytes # Uncomment if you use BitsAndBytesConfig in local_llm_connector.py", "# accelerate # Often needed with transformers, especially for device_map", "# Core dependency for GGUF based nodes (e.g., Mistral-7B-Instruct-Q8.gguf)", "llama-cpp-python>=0.1.0 # Or a specific version", "# Optional: For downloading models from Hugging Face Hub", "huggingface_hub", "# Optional: If your package uses any specific utility libraries", "# (Although deepdiff, pillow, etc. might be pulled in by ComfyUI or other nodes)", "# deepdiff", "# pillow", "# Notes for Users:", "# For llama-cpp-python with GPU support (CUDA), installation is more complex.", "# Example for CUDA 11.8 (check llama-cpp-python docs for your version):", "# CMAKE_ARGS=\"-DLLAMA_CUBLAS=on\" pip install llama-cpp-python", "# Example for CUDA 12.1:", "# CMAKE_ARGS=\"-DLLAMA_CUDA=on\" pip install llama-cpp-python"]
|
||||
authors = [
|
||||
{ name = "Obada Shawabke", email = "obdkml@gmail.com" }
|
||||
]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/kmlbdh/ComfyUI_LocalLLMNodes"
|
||||
Homepage = "https://github.com/kmlbdh/ComfyUI_LocalLLMNodes"
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "kmlbdh"
|
||||
DisplayName = "ComfyUI_LocalLLMNodes"
|
||||
Icon = "🤖"
|
||||
includes = []
|
||||
Reference in New Issue
Block a user