diff --git a/CLAUDE.md b/CLAUDE.md index 3d330fe..564c2a6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -88,3 +88,31 @@ comfy node publish --confirm 1. Add entry to `config/templates.yaml` following existing format 2. Add style key to `INPUT_TYPES()` style combo list in `prompt_generator_node.py` 3. Optionally add to `DEFAULT_STYLES` dict for fallback when YAML unavailable + +- **Current version**: `1.1.1` - Added dynamic LoRA model selection and prioritization. (2026-02-02) +- **LoRA Training**: Trained QLoRA on Qwen3-4B-Instruct-2507 using the Limbicnation Video Diffusion Prompt dataset. +- **Quantization**: Merged LoRA and converted to Q8_0 GGUF for Ollama. +- **Integration**: `PromptGeneratorNode` now auto-discovers and prioritizes models containing `lora` or `limbicnation` keywords. + +### Creating a LoRA-Enhanced Model + +1. Fine-tune a LoRA on the [Limbicnation/Images-Diffusion-Prompt-Style](https://huggingface.co/datasets/Limbicnation/Images-Diffusion-Prompt-Style) dataset (750 prompts) + +2. Export as `.safetensors` (non-quantized recommended) + +3. Create the Ollama model: + + ```bash + # Edit config/Modelfile.limbicnation with your adapter path + ollama create qwen3-limbicnation -f config/Modelfile.limbicnation + ``` + +4. Restart ComfyUI - the new model will appear in the dropdown + +### Modelfile Template + +See `config/Modelfile.limbicnation` for a pre-configured template with: + +- Limbicnation system prompt +- Optimal temperature/top_p settings +- ADAPTER placeholder for your LoRA diff --git a/config/Modelfile.limbicnation b/config/Modelfile.limbicnation new file mode 100644 index 0000000..f70bb3e --- /dev/null +++ b/config/Modelfile.limbicnation @@ -0,0 +1,33 @@ +# Limbicnation LoRA Modelfile Template +# +# This Modelfile creates an Ollama model with the Limbicnation image prompt style LoRA. +# +# Usage: +# 1. Fine-tune your LoRA using the 750 prompts from Limbicnation/Images-Diffusion-Prompt-Style +# 2. Export as .safetensors (non-quantized recommended) +# 3. Update the ADAPTER path below +# 4. Run: ollama create qwen3-limbicnation -f Modelfile.limbicnation +# 5. Test: ollama run qwen3-limbicnation "Generate a cinematic forest prompt" + +# Base model - use the same model you fine-tuned the LoRA on +FROM qwen3:4b + +# LoRA adapter path (update this to your fine-tuned adapter) +# ADAPTER /path/to/limbicnation-lora.safetensors + +# System prompt for image prompt generation +SYSTEM """You are an expert AI Image Prompt Engineer specializing in the "Limbicnation" aesthetic. +Your prompts emphasize: cinematic lighting, intricate textures, evocative atmosphere, dramatic compositions. + +When generating image prompts: +1. Use rich, sensory language +2. Include quality tokens: photorealistic, 8k, detailed, masterpiece +3. Specify lighting and atmosphere +4. Add negative prompts when requested +5. Format for Flux, Z Image, or Stable Diffusion models +""" + +# Optimal parameters for prompt generation +PARAMETER temperature 0.7 +PARAMETER top_p 0.9 +PARAMETER num_ctx 4096 diff --git a/nodes/prompt_generator_node.py b/nodes/prompt_generator_node.py index 6608e5a..3ff1a8e 100644 --- a/nodes/prompt_generator_node.py +++ b/nodes/prompt_generator_node.py @@ -191,14 +191,64 @@ Format the response as a single, detailed sci-fi prompt.""" } } + # Class-level cache for available models + _cached_models = None + _cache_time = 0 + def __init__(self): """Initialize the node and load style templates.""" self.style_templates = self._load_templates() self.timeout = 120 + @classmethod + def _get_available_models(cls) -> list: + """ + Fetch available Ollama models with caching. + Prioritizes LoRA-enhanced models (containing 'lora', 'limbicnation', 'fine'). + """ + import time + + # Cache for 60 seconds + if cls._cached_models and (time.time() - cls._cache_time) < 60: + return cls._cached_models + + default_models = ["qwen3:8b", "qwen3:4b", "llama3.2:latest"] + + if not OLLAMA_API_AVAILABLE: + return default_models + + try: + result = ollama.list() + models = [m['name'] for m in result.get('models', []) if 'name' in m] + + if not models: + return default_models + + # Sort: LoRA/fine-tuned models first, then alphabetically + lora_keywords = ['lora', 'limbicnation', 'fine', 'style', 'prompt'] + + def sort_key(name): + name_lower = name.lower() + is_lora = any(kw in name_lower for kw in lora_keywords) + return (0 if is_lora else 1, name) + + models = sorted(models, key=sort_key) + + cls._cached_models = models + cls._cache_time = time.time() + + print(f"[PromptGenerator] Found {len(models)} Ollama models") + return models + + except Exception as e: + print(f"[PromptGenerator] Could not fetch models: {e}") + return default_models + @classmethod def INPUT_TYPES(cls) -> Dict[str, Any]: """Define input parameters for the node.""" + available_models = cls._get_available_models() + return { "required": { "description": ("STRING", { @@ -210,6 +260,10 @@ Format the response as a single, detailed sci-fi prompt.""" "abstract", "cyberpunk", "sci-fi"], { "default": "cinematic" }), + "model": (available_models, { + "default": available_models[0] if available_models else "qwen3:8b", + "tooltip": "Select Ollama model. LoRA-enhanced models appear first." + }), }, "optional": { "emphasis": ("STRING", { @@ -239,9 +293,6 @@ Format the response as a single, detailed sci-fi prompt.""" "label_on": "Show Reasoning", "label_off": "Hide Reasoning" }), - "model": ("STRING", { - "default": "qwen3:8b" - }), } } @@ -250,6 +301,7 @@ Format the response as a single, detailed sci-fi prompt.""" FUNCTION = "generate" CATEGORY = "text/generation" OUTPUT_NODE = False + def _load_templates(self) -> Dict[str, Any]: """Load style templates from YAML file or use defaults.""" diff --git a/pyproject.toml b/pyproject.toml index 612d01c..f9e9060 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-prompt-generator" -description = "Generate Stable Diffusion prompts using Qwen3-8B via Ollama with 7 style presets" -version = "1.0.5" +description = "Generate Stable Diffusion prompts using Qwen/Ollama with LoRA support and 7 style presets" +version = "1.1.2" license = {file = "LICENSE"} readme = "README.md" requires-python = ">=3.10"