Files
2025-07-23 21:14:31 +09:00

372 lines
15 KiB
Python

"""
ComfyUI BuimenLabo - Unified Package
A comprehensive collection of ComfyUI custom nodes including:
- Multi-language locale toggle with 20 language support
- AI-powered pose analysis with Gemini integration
- Advanced LLM text generation with llama-cpp-python
- Smart ControlNet loader for SD/SDXL models
- AI-powered prompt translation
Author: BuimenLabo
Blog: https://note.com/hirodream44
"""
# Import version information
try:
from ._version import __version__, PACKAGE_NAME, AUTHOR, BLOG_URL, print_version_info
print(f"🚀 {PACKAGE_NAME} v{__version__} - {AUTHOR}")
print(f"📝 Blog: {BLOG_URL}")
except ImportError:
__version__ = "1.0.0"
print(f"🚀 ComfyUI BuimenLabo v{__version__}")
import os
import sys
import logging
import random
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
print("🚀 Loading ComfyUI BuimenLabo unified package...")
# Import all packages as-is (simple copy approach)
try:
# 1. Gemini Pose Analyzer
from .gemini_pose_analyzer import NODE_CLASS_MAPPINGS as GEMINI_MAPPINGS
from .gemini_pose_analyzer import NODE_DISPLAY_NAME_MAPPINGS as GEMINI_DISPLAY_MAPPINGS
print("✅ GeminiPoseAnalyzer loaded")
except Exception as e:
print(f"⚠️ GeminiPoseAnalyzer failed: {e}")
GEMINI_MAPPINGS = {}
GEMINI_DISPLAY_MAPPINGS = {}
try:
# 2. LlamaCpp Text (working version)
from .llama_cpp_node import (
LlamaCppLoader,
LlamaCppGenerate,
LlamaCppUnload,
LlamaCppSafeUnload,
LlamaCppMemoryInfo,
LlamaCppCompleteUnload,
LlamaCppAIO,
)
LLAMA_MAPPINGS = {
"LlamaCppLoader": LlamaCppLoader,
"LlamaCppGenerate": LlamaCppGenerate,
"LlamaCppUnload": LlamaCppUnload,
"LlamaCppSafeUnload": LlamaCppSafeUnload,
"LlamaCppMemoryInfo": LlamaCppMemoryInfo,
"LlamaCppCompleteUnload": LlamaCppCompleteUnload,
"LlamaCppAIO": LlamaCppAIO,
}
LLAMA_DISPLAY_MAPPINGS = {
"LlamaCppLoader": "Llama-CPP Loader",
"LlamaCppGenerate": "Llama-CPP Generate",
"LlamaCppUnload": "Llama-CPP Unload VRAM",
"LlamaCppSafeUnload": "Llama-CPP Safe Unload",
"LlamaCppMemoryInfo": "Llama-CPP Memory Info",
"LlamaCppCompleteUnload": "Llama-CPP Complete Unload",
"LlamaCppAIO": "🚀 Llama-CPP All-In-One",
}
print("✅ LlamaCppText loaded")
except Exception as e:
print(f"⚠️ LlamaCppText failed: {e}")
LLAMA_MAPPINGS = {}
LLAMA_DISPLAY_MAPPINGS = {}
try:
# 3. Multi ControlNet Loader
from .multi_controlnet_loader import NODE_CLASS_MAPPINGS as CONTROLNET_MAPPINGS
from .multi_controlnet_loader import NODE_DISPLAY_NAME_MAPPINGS as CONTROLNET_DISPLAY_MAPPINGS
print("✅ MultiControlNetLoader loaded")
except Exception as e:
print(f"⚠️ MultiControlNetLoader failed: {e}")
CONTROLNET_MAPPINGS = {}
CONTROLNET_DISPLAY_MAPPINGS = {}
try:
# 4. Prompt Translator
from .prompt_translator import NODE_CLASS_MAPPINGS as TRANSLATOR_MAPPINGS
from .prompt_translator import NODE_DISPLAY_NAME_MAPPINGS as TRANSLATOR_DISPLAY_MAPPINGS
print("✅ PromptTranslator loaded")
except Exception as e:
print(f"⚠️ PromptTranslator failed: {e}")
TRANSLATOR_MAPPINGS = {}
TRANSLATOR_DISPLAY_MAPPINGS = {}
try:
# 5. Locale Toggle
from .locale_toggle import NODE_CLASS_MAPPINGS as LOCALE_MAPPINGS
from .locale_toggle import NODE_DISPLAY_NAME_MAPPINGS as LOCALE_DISPLAY_MAPPINGS
print("✅ LocaleToggle loaded")
except Exception as e:
print(f"⚠️ LocaleToggle failed: {e}")
LOCALE_MAPPINGS = {}
LOCALE_DISPLAY_MAPPINGS = {}
# Setup LlamaCpp routes (from working version)
def setup_llama_routes():
"""Setup llama generation routes using ComfyUI's standard method."""
try:
from server import PromptServer
from aiohttp import web
import json
routes = PromptServer.instance.routes
@routes.post('/llama_cpp/generate')
async def handle_llama_generation(request):
try:
print(f"🔄 Llama generation request received")
# Get form data
data = await request.post()
instruction_prompt = data.get('instruction_prompt', '')
input_text = data.get('input_text', '')
model_path = data.get('model_path', '')
max_tokens = int(data.get('max_tokens', '128'))
temperature = float(data.get('temperature', '0.8'))
top_k = int(data.get('top_k', '40'))
top_p = float(data.get('top_p', '0.95'))
repeat_penalty = float(data.get('repeat_penalty', '1.1'))
n_ctx = int(data.get('n_ctx', '2048'))
n_gpu_layers = int(data.get('n_gpu_layers', '40'))
nsfw_filter = data.get('nsfw_filter', 'true').lower() == 'true'
auto_unload = data.get('auto_unload', 'true').lower() == 'true'
print(f"📋 Request data: model={model_path}, text='{input_text[:50]}...', max_tokens={max_tokens}")
if not input_text.strip() and not instruction_prompt.strip():
return web.json_response({'error': 'No input text provided'}, status=400)
# Import llama functions
from .llama_cpp_node import _get_or_create_model, _cleanup_memory
import random
# Load model
try:
llm = _get_or_create_model(model_path, n_ctx, n_gpu_layers, False)
print(f"✅ Model loaded for API: {model_path}")
except Exception as e:
print(f"❌ Model loading failed: {e}")
return web.json_response({'error': f'Model loading failed: {str(e)}'}, status=500)
# Generate text
try:
# Build prompt(指示と内容を明確に分離)
instruction = instruction_prompt if instruction_prompt else ""
content = input_text if input_text else "A beautiful scene"
# デフォルト指示を設定(ユーザー指示がない場合)
if not instruction.strip():
instruction = """You are a prompt generator for Stable Diffusion.
Task:
Convert the input description into ONE optimized English prompt for Stable Diffusion.
Rules:
- Output format: single line of comma-separated phrases only
- Stay faithful to the original description's content and intent
- Include: subject, setting/environment, lighting, mood/atmosphere, artistic style, quality tags
- Use descriptive English terms that accurately represent the input
- Do not censor or sanitize the content - convert faithfully
- End cleanly after final quality tag
Output:
<prompt>"""
# ランダムなバリエーション用のリスト(適度に調整)
variation_prompts = [
"Focus on artistic composition and visual details.",
"Emphasize mood and atmospheric elements.",
"Include creative styling and artistic elements.",
"Add detailed lighting and visual effects."
]
quality_tags = [
"masterpiece, best quality, highly detailed",
"high quality, detailed artwork",
"ultra detailed, premium quality",
"best quality, intricate details"
]
# 内部固定システムプロンプト
system_prompt = "You are a creative writing assistant. Respond in English."
# Apply NSFW filter if requested
if nsfw_filter:
safety_note = " IMPORTANT: If inappropriate content, respond with 'I cannot generate prompts for inappropriate content.' Otherwise, respond ONLY in English."
wrapped_prompt = f"""{system_prompt}
{instruction}{safety_note}
Input to convert: {content}
Response (English only):"""
else:
# 最小限のランダム性のみ追加
if random.choice([True, False]):
quality_hint = random.choice(quality_tags)
wrapped_prompt = f"""{system_prompt}
{instruction}
Input to convert: {content}
Hint: Consider quality tags like "{quality_hint}"
Response (English only):"""
else:
wrapped_prompt = f"""{system_prompt}
{instruction}
Input to convert: {content}
Response (English only):"""
# Generate
seed = random.randint(1, 2**31 - 1)
response = llm.create_completion(
prompt=wrapped_prompt,
max_tokens=max_tokens,
temperature=temperature,
top_k=top_k,
top_p=top_p,
repeat_penalty=repeat_penalty,
seed=seed,
stream=False,
echo=False,
stop=["</s>"]
)
if isinstance(response, dict) and "choices" in response:
result_text = response["choices"][0]["text"]
else:
result_text = str(response)
# Clean up result
result_text = result_text.strip()
for stop_token in ["</s>", "<eos>"]:
if stop_token in result_text:
result_text = result_text.split(stop_token)[0]
if not result_text or result_text.strip() == "":
result_text = "レスポンスが生成されませんでした。"
print(f"✅ Generation completed: {len(result_text)} characters")
# Auto unload if requested
if auto_unload:
print("🧹 Auto unloading VRAM...")
try:
_cleanup_memory()
print("✅ Auto unload completed")
except Exception as cleanup_e:
print(f"⚠️ Auto unload warning: {cleanup_e}")
return web.json_response({
'success': True,
'generated_text': result_text,
'input_text': f"{instruction}\n\n{content}",
'token_count': len(result_text.split()),
'model_path': model_path,
'auto_unload': auto_unload
})
except Exception as e:
print(f"❌ Generation error: {e}")
return web.json_response({'error': f'Generation failed: {str(e)}'}, status=500)
except Exception as e:
print(f"❌ API error: {e}")
import traceback
traceback.print_exc()
return web.json_response({
'success': False,
'error': str(e)
}, status=500)
@routes.post('/llama_cpp/unload')
async def handle_llama_unload(request):
try:
print(f"🗑️ Llama unload request received")
# Import cleanup functions
from .llama_cpp_node import _cleanup_memory, _force_cleanup_memory
# Execute force memory cleanup for better VRAM release
_force_cleanup_memory()
print(f"✅ VRAM cleanup completed")
return web.json_response({
'success': True,
'message': 'VRAM has been successfully freed. All models have been unloaded from memory.'
})
except Exception as e:
print(f"❌ Unload API error: {e}")
import traceback
traceback.print_exc()
return web.json_response({
'success': False,
'error': str(e)
}, status=500)
print("✅ Llama-CPP routes registered")
except Exception as e:
print(f"⚠️ Could not register llama routes: {e}")
# Setup routes when server is available
try:
setup_llama_routes()
print("🚀 BuimenLabo: Routes setup completed successfully")
except Exception as e:
print(f"⚠️ BuimenLabo: Routes setup failed: {e}")
# Combine all node mappings with BuimenLabo prefix
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
# Add with prefixes to avoid conflicts
for name, cls in GEMINI_MAPPINGS.items():
NODE_CLASS_MAPPINGS[f"BuimenLabo_{name}"] = cls
for name, display in GEMINI_DISPLAY_MAPPINGS.items():
NODE_DISPLAY_NAME_MAPPINGS[f"BuimenLabo_{name}"] = f"🔍 BuimenLabo {display}"
for name, cls in LLAMA_MAPPINGS.items():
NODE_CLASS_MAPPINGS[f"BuimenLabo_{name}"] = cls
for name, display in LLAMA_DISPLAY_MAPPINGS.items():
NODE_DISPLAY_NAME_MAPPINGS[f"BuimenLabo_{name}"] = f"🦙 BuimenLabo {display}"
for name, cls in CONTROLNET_MAPPINGS.items():
NODE_CLASS_MAPPINGS[f"BuimenLabo_{name}"] = cls
for name, display in CONTROLNET_DISPLAY_MAPPINGS.items():
NODE_DISPLAY_NAME_MAPPINGS[f"BuimenLabo_{name}"] = f"🎛️ BuimenLabo {display}"
for name, cls in TRANSLATOR_MAPPINGS.items():
NODE_CLASS_MAPPINGS[f"BuimenLabo_{name}"] = cls
for name, display in TRANSLATOR_DISPLAY_MAPPINGS.items():
NODE_DISPLAY_NAME_MAPPINGS[f"BuimenLabo_{name}"] = f"🌐 BuimenLabo {display}"
for name, cls in LOCALE_MAPPINGS.items():
NODE_CLASS_MAPPINGS[f"BuimenLabo_{name}"] = cls
for name, display in LOCALE_DISPLAY_MAPPINGS.items():
NODE_DISPLAY_NAME_MAPPINGS[f"BuimenLabo_{name}"] = f"🌍 BuimenLabo {display}"
# Set WEB_DIRECTORY for JavaScript files
WEB_DIRECTORY = "./web"
# Package information
print("📦 ComfyUI BuimenLabo package loaded successfully!")
print(f"🎯 Total nodes: {len(NODE_CLASS_MAPPINGS)}")
print("📝 Blog: https://note.com/hirodream44")
print("🔗 Features: Multi-language, AI pose analysis, LLM generation, Smart ControlNet, Prompt translation")
# Export for ComfyUI
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']