feat: consolidate style system to 9 styles across both nodes
This commit is contained in:
@@ -17,4 +17,3 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
}
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# Code Review: ComfyUI-PromptGenerator
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The codebase is well-structured with good error handling and graceful degradation patterns. However, there is a **critical inconsistency** in the style system between the two nodes, plus several architectural concerns worth addressing.
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Critical Issues
|
||||
|
||||
### 1. Style System Inconsistency (High Priority)
|
||||
|
||||
**Problem:** The two nodes have completely different style options:
|
||||
|
||||
| Node | Available Styles |
|
||||
|------|------------------|
|
||||
| `PromptGeneratorNode` | cinematic, anime, photorealistic, fantasy, abstract, cyberpunk, sci-fi, video_wan (8 styles) |
|
||||
| `StyleApplierNode` | cinematic, still_image (2 styles via `StylePreset.get_style_choices()`) |
|
||||
|
||||
The `style_presets.py` module only defines 2 styles, while `config/templates.yaml` and `DEFAULT_STYLES` define 8 styles.
|
||||
|
||||
**Impact:** Users expect consistency. If they select "anime" style in PromptGenerator, they cannot use StyleApplier with "anime" - it will fail validation.
|
||||
|
||||
**Recommendation:** Either:
|
||||
|
||||
- Expand `STYLE_DEFINITIONS` in `style_presets.py` to include all 8 styles
|
||||
- Or consolidate to use templates.yaml as the single source of truth
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Architectural Concerns
|
||||
|
||||
### 2. Triple Source of Truth for Styles
|
||||
|
||||
The codebase has three different places defining styles:
|
||||
|
||||
1. `config/templates.yaml` - YAML templates
|
||||
2. `DEFAULT_STYLES` dict in Python (`prompt_generator_node.py`)
|
||||
3. `style_presets.py` - StyleKeywords dataclass
|
||||
|
||||
**Recommendation:** Consolidate to a single source. The YAML file is user-editable and should be the authoritative source.
|
||||
|
||||
### 3. Unused `style_presets.py` Module
|
||||
|
||||
The `StylePreset` class is only used by `StyleApplierNode`. The main `PromptGeneratorNode` uses its own `DEFAULT_STYLES` dict and YAML templates.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Code Quality Issues
|
||||
|
||||
### 4. Missing Style Validation in `generate()`
|
||||
|
||||
In `generate()`, there's no validation that the selected style exists - it silently falls back to cinematic.
|
||||
|
||||
### 5. Inconsistent Error Messages
|
||||
|
||||
- `PromptGeneratorNode` returns emoji-prefixed messages: `"⚠️ Please enter an image description."`
|
||||
- `StyleApplierNode` returns bracket-prefixed: `"[StyleApplier] Error: Unknown style..."`
|
||||
|
||||
**Recommendation:** Standardize error message format.
|
||||
|
||||
### 6. Hardcoded Values
|
||||
|
||||
- `chunk_timeout = 30` is hardcoded in streaming
|
||||
- Model list in `_get_available_models()` has hardcoded defaults
|
||||
|
||||
---
|
||||
|
||||
## ✅ Positive Patterns
|
||||
|
||||
1. **Graceful Degradation:** Excellent use of optional imports with fallbacks
|
||||
2. **Streaming Implementation:** Well-designed background thread approach for timeout enforcement
|
||||
3. **Cold Start Detection:** Smart 1.3x timeout multiplier for unloaded models
|
||||
4. **Model Caching:** 60-second cache prevents UI freezes
|
||||
5. **LoRA Prioritization:** Good sorting logic to surface fine-tuned models first
|
||||
|
||||
---
|
||||
|
||||
## 📋 Recommendations Priority List
|
||||
|
||||
| Priority | Issue | Action |
|
||||
|----------|-------|--------|
|
||||
| P0 | Style inconsistency | Sync `style_presets.py` with 8 styles or consolidate |
|
||||
| P1 | Triple source of truth | Make templates.yaml the single source |
|
||||
| P2 | Silent style fallback | Add validation/warning when style not found |
|
||||
| P3 | Error message format | Standardize across nodes |
|
||||
| P4 | Hardcoded values | Move to config/constants |
|
||||
@@ -71,12 +71,14 @@ class PromptGeneratorNode:
|
||||
ComfyUI node for generating Stable Diffusion prompts using Qwen3-8B via Ollama.
|
||||
|
||||
Features:
|
||||
- 7 style presets (cinematic, anime, photorealistic, fantasy, abstract, cyberpunk, sci-fi)
|
||||
- 9 style presets loaded from templates.yaml (single source of truth)
|
||||
- Temperature and Top-P sampling controls
|
||||
- Optional focus area (emphasis) and mood inputs
|
||||
- Reasoning toggle to show/hide model's thinking process
|
||||
"""
|
||||
|
||||
CHUNK_TIMEOUT = 30
|
||||
|
||||
# Default style templates (fallback when YAML not available)
|
||||
DEFAULT_STYLES = {
|
||||
"cinematic": {
|
||||
@@ -210,6 +212,24 @@ Format the response as a single, detailed sci-fi prompt.""",
|
||||
"description": "Minimalist template optimized for WanVideo LoRA",
|
||||
"template": "Generate a video prompt for: {{ description }}{% if emphasis %} with focus on {{ emphasis }}{% endif %}{% if mood %}, mood is {{ mood }}{% endif %}",
|
||||
},
|
||||
"still_image": {
|
||||
"name": "Still Image (Photography)",
|
||||
"description": "Sharp, realistic photography with technical camera specifications",
|
||||
"template": """Write a detailed Stable Diffusion prompt for: {{ description }}
|
||||
|
||||
Style: Generate a professional photography still image with sharp focus.
|
||||
{% if emphasis %}Focus particularly on: {{ emphasis }}{% endif %}
|
||||
{% if mood %}Mood/Atmosphere: {{ mood }}{% endif %}
|
||||
|
||||
Include specific details about:
|
||||
- Camera settings (ISO 100, f/2.8 aperture, sharp optics)
|
||||
- Natural or studio lighting
|
||||
- Realistic textures and fine details
|
||||
- Clean, balanced composition
|
||||
- Professional photography qualities
|
||||
|
||||
Format the response as a single, detailed photography prompt.""",
|
||||
},
|
||||
}
|
||||
|
||||
# Class-level cache for available models
|
||||
@@ -264,10 +284,25 @@ Format the response as a single, detailed sci-fi prompt.""",
|
||||
print(f"[PromptGenerator] Could not fetch models: {e}")
|
||||
return default_models
|
||||
|
||||
@classmethod
|
||||
def _get_style_list(cls) -> list:
|
||||
"""Get style list from templates.yaml, falling back to DEFAULT_STYLES keys."""
|
||||
template_path = Path(__file__).parent.parent / "config" / "templates.yaml"
|
||||
if YAML_AVAILABLE and template_path.exists():
|
||||
try:
|
||||
with open(template_path, "r") as f:
|
||||
templates = yaml.safe_load(f)
|
||||
if templates:
|
||||
return list(templates.keys())
|
||||
except Exception:
|
||||
pass
|
||||
return list(cls.DEFAULT_STYLES.keys())
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> Dict[str, Any]:
|
||||
"""Define input parameters for the node."""
|
||||
available_models = cls._get_available_models()
|
||||
available_styles = cls._get_style_list()
|
||||
|
||||
return {
|
||||
"required": {
|
||||
@@ -280,17 +315,12 @@ Format the response as a single, detailed sci-fi prompt.""",
|
||||
},
|
||||
),
|
||||
"style": (
|
||||
[
|
||||
"cinematic",
|
||||
"anime",
|
||||
"photorealistic",
|
||||
"fantasy",
|
||||
"abstract",
|
||||
"cyberpunk",
|
||||
"sci-fi",
|
||||
"video_wan",
|
||||
],
|
||||
{"default": "cinematic"},
|
||||
available_styles,
|
||||
{
|
||||
"default": available_styles[0]
|
||||
if available_styles
|
||||
else "cinematic"
|
||||
},
|
||||
),
|
||||
"model": (
|
||||
available_models,
|
||||
@@ -395,9 +425,13 @@ Format the response as a single, detailed sci-fi prompt.""",
|
||||
mood: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Render a Jinja2 template with the given variables."""
|
||||
template_data = self.style_templates.get(
|
||||
style, self.DEFAULT_STYLES.get("cinematic")
|
||||
)
|
||||
template_data = self.style_templates.get(style)
|
||||
if template_data is None:
|
||||
print(
|
||||
f"[PromptGenerator] Warning: style '{style}' not found in templates, "
|
||||
f"falling back to 'cinematic'"
|
||||
)
|
||||
template_data = self.DEFAULT_STYLES.get("cinematic")
|
||||
|
||||
# Handle YAML format with 'template' key
|
||||
if isinstance(template_data, dict) and "template" in template_data:
|
||||
@@ -484,7 +518,7 @@ Format the response as a single, detailed sci-fi prompt.""",
|
||||
chunks = []
|
||||
start = time.monotonic()
|
||||
first_chunk_timeout = min(timeout * 0.6, 90)
|
||||
chunk_timeout = 30
|
||||
chunk_timeout = self.CHUNK_TIMEOUT
|
||||
got_first_chunk = False
|
||||
|
||||
try:
|
||||
@@ -604,7 +638,7 @@ Format the response as a single, detailed sci-fi prompt.""",
|
||||
Tuple containing the generated prompt string
|
||||
"""
|
||||
if not description.strip():
|
||||
return ("⚠️ Please enter an image description.",)
|
||||
return ("[PromptGenerator] Please enter an image description.",)
|
||||
|
||||
# Render the template
|
||||
prompt = self._render_template(
|
||||
@@ -666,7 +700,7 @@ Format the response as a single, detailed sci-fi prompt.""",
|
||||
print(f"[PromptGenerator] Generated {len(output)} characters")
|
||||
return (output,)
|
||||
else:
|
||||
return ("⚠️ Generation returned empty result.",)
|
||||
return ("[PromptGenerator] Generation returned empty result.",)
|
||||
|
||||
print("[PromptGenerator] Streaming failed, falling back to subprocess")
|
||||
|
||||
@@ -680,7 +714,7 @@ Format the response as a single, detailed sci-fi prompt.""",
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return (f"⚠️ Ollama error: {result.stderr}",)
|
||||
return (f"[PromptGenerator] Ollama error: {result.stderr}",)
|
||||
|
||||
output = result.stdout.strip()
|
||||
|
||||
@@ -696,11 +730,13 @@ Format the response as a single, detailed sci-fi prompt.""",
|
||||
)
|
||||
return (output,)
|
||||
else:
|
||||
return ("⚠️ Generation returned empty result.",)
|
||||
return ("[PromptGenerator] Generation returned empty result.",)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return (f"⚠️ Generation timed out after {timeout}s",)
|
||||
return (f"[PromptGenerator] Generation timed out after {timeout}s",)
|
||||
except FileNotFoundError:
|
||||
return ("⚠️ Ollama not found. Install from: https://ollama.ai",)
|
||||
return (
|
||||
"[PromptGenerator] Ollama not found. Install from: https://ollama.ai",
|
||||
)
|
||||
except Exception as e:
|
||||
return (f"⚠️ Error: {str(e)}",)
|
||||
return (f"[PromptGenerator] Error: {str(e)}",)
|
||||
|
||||
+29
-19
@@ -9,52 +9,61 @@ from typing import Tuple
|
||||
class StyleApplierNode:
|
||||
"""
|
||||
ComfyUI node for applying Cinematic or Still Image style keywords to prompts.
|
||||
|
||||
|
||||
Inputs:
|
||||
- prompt: Base prompt text
|
||||
- style: "cinematic" or "still_image"
|
||||
- position: Where to add keywords ("prefix", "suffix", or "wrap")
|
||||
- emphasis: Optional emphasis level ("low", "medium", "high")
|
||||
- include_technical: Include camera/technical specs
|
||||
|
||||
|
||||
Outputs:
|
||||
- styled_prompt: The prompt with style keywords added
|
||||
- style_keywords: Just the style keywords (for reference)
|
||||
"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
"""Define input parameters for the node."""
|
||||
from style_presets import StylePreset
|
||||
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"placeholder": "Enter your base prompt..."
|
||||
}),
|
||||
"prompt": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"placeholder": "Enter your base prompt...",
|
||||
},
|
||||
),
|
||||
"style": (StylePreset.get_style_choices(), {"default": "cinematic"}),
|
||||
},
|
||||
"optional": {
|
||||
"position": (["suffix", "prefix", "wrap"], {"default": "suffix"}),
|
||||
"emphasis": (["medium", "low", "high"], {"default": "medium"}),
|
||||
"include_technical": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING",)
|
||||
RETURN_NAMES = ("styled_prompt", "style_keywords",)
|
||||
|
||||
RETURN_TYPES = (
|
||||
"STRING",
|
||||
"STRING",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"styled_prompt",
|
||||
"style_keywords",
|
||||
)
|
||||
FUNCTION = "apply_style"
|
||||
CATEGORY = "text/generation"
|
||||
|
||||
|
||||
def apply_style(
|
||||
self,
|
||||
prompt: str,
|
||||
style: str,
|
||||
position: str = "suffix",
|
||||
emphasis: str = "medium",
|
||||
include_technical: bool = True
|
||||
include_technical: bool = True,
|
||||
) -> Tuple[str, str]:
|
||||
"""Apply style keywords to a prompt."""
|
||||
from style_presets import StylePreset
|
||||
@@ -66,14 +75,15 @@ class StyleApplierNode:
|
||||
# Validate style
|
||||
available_styles = StylePreset.get_style_choices()
|
||||
if style not in available_styles:
|
||||
return (f"[StyleApplier] Error: Unknown style '{style}'. Available: {available_styles}", "")
|
||||
return (
|
||||
f"[StyleApplier] Error: Unknown style '{style}'. Available: {available_styles}",
|
||||
"",
|
||||
)
|
||||
|
||||
# Get style keywords
|
||||
try:
|
||||
style_keywords = StylePreset().get_style_prompt(
|
||||
style=style,
|
||||
emphasis=emphasis,
|
||||
include_technical=include_technical
|
||||
style=style, emphasis=emphasis, include_technical=include_technical
|
||||
)
|
||||
except ValueError as e:
|
||||
return (f"[StyleApplier] Error getting style: {e}", "")
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Social Media Posts - v1.1.6 Bug Fix Release
|
||||
|
||||
## LinkedIn Post
|
||||
|
||||
---
|
||||
|
||||
**Fixed the 120s timeout issue in ComfyUI Prompt Generator (v1.1.6)**
|
||||
|
||||
If you've been hitting "Generation Timed Out 120s" errors with the ComfyUI Prompt Generator node - this is now resolved.
|
||||
|
||||
The root cause: the blocking `ollama.generate()` call had no way to handle slow model responses or cold starts gracefully.
|
||||
|
||||
What changed in v1.1.6:
|
||||
- Replaced blocking API call with streaming + per-chunk timeouts
|
||||
- Added cold start detection with automatic timeout multiplier
|
||||
- Integrated ComfyUI ProgressBar so you can see generation progress in real time
|
||||
|
||||
Update via ComfyUI Manager or pull the latest from GitHub.
|
||||
|
||||
https://github.com/Limbicnation/ComfyUI-PromptGenerator
|
||||
|
||||
#ComfyUI #StableDiffusion #Ollama #AIArt #OpenSource #BugFix
|
||||
|
||||
---
|
||||
|
||||
## YouTube Reply to @luci0c4s3
|
||||
|
||||
---
|
||||
|
||||
> @luci0c4s3: "Getting Generation Timed 120s as an error each time I run the node."
|
||||
|
||||
**Reply:**
|
||||
|
||||
Hey! This was a known issue and it's been fixed in v1.1.6. The blocking Ollama call was replaced with streaming + per-chunk timeouts, which eliminates the 120s timeout error. Cold starts also get extra time automatically now. Update via ComfyUI Manager or grab the latest from GitHub: https://github.com/Limbicnation/ComfyUI-PromptGenerator
|
||||
|
||||
---
|
||||
+276
-36
@@ -1,13 +1,13 @@
|
||||
"""
|
||||
Style Presets Module for ComfyUI
|
||||
Modular style system for prompt generation with Cinematic and Still Image modes.
|
||||
Modular style system for prompt generation with keyword sets for each style.
|
||||
|
||||
Usage in ComfyUI nodes:
|
||||
from style_presets import StylePreset
|
||||
|
||||
|
||||
# Get style choices for INPUT_TYPES dropdown
|
||||
choices = StylePreset.get_style_choices() # ("cinematic", "still_image")
|
||||
|
||||
choices = StylePreset.get_style_choices()
|
||||
|
||||
# Get style keywords as a prompt string
|
||||
preset = StylePreset()
|
||||
style_prompt = preset.get_style_prompt("cinematic")
|
||||
@@ -20,38 +20,54 @@ from enum import Enum
|
||||
|
||||
class StyleMode(Enum):
|
||||
"""Enumeration of available style modes."""
|
||||
|
||||
CINEMATIC = "cinematic"
|
||||
STILL_IMAGE = "still_image"
|
||||
ANIME = "anime"
|
||||
PHOTOREALISTIC = "photorealistic"
|
||||
FANTASY = "fantasy"
|
||||
ABSTRACT = "abstract"
|
||||
CYBERPUNK = "cyberpunk"
|
||||
SCI_FI = "sci-fi"
|
||||
VIDEO_WAN = "video_wan"
|
||||
|
||||
|
||||
@dataclass
|
||||
class StyleKeywords:
|
||||
"""Container for style-specific keywords and descriptors."""
|
||||
|
||||
primary: List[str] = field(default_factory=list)
|
||||
lighting: List[str] = field(default_factory=list)
|
||||
technical: List[str] = field(default_factory=list)
|
||||
composition: List[str] = field(default_factory=list)
|
||||
texture: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
def to_prompt_string(self, separator: str = ", ") -> str:
|
||||
"""Convert all keywords to a single prompt string."""
|
||||
all_keywords = (
|
||||
self.primary + self.lighting + self.technical +
|
||||
self.composition + self.texture
|
||||
self.primary
|
||||
+ self.lighting
|
||||
+ self.technical
|
||||
+ self.composition
|
||||
+ self.texture
|
||||
)
|
||||
return separator.join(all_keywords)
|
||||
|
||||
|
||||
def to_list(self) -> List[str]:
|
||||
"""Return all keywords as a flat list (ComfyUI-compatible)."""
|
||||
return (
|
||||
self.primary + self.lighting + self.technical +
|
||||
self.composition + self.texture
|
||||
self.primary
|
||||
+ self.lighting
|
||||
+ self.technical
|
||||
+ self.composition
|
||||
+ self.texture
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class StyleDefinition:
|
||||
"""Complete style definition with metadata and keywords."""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
keywords: StyleKeywords
|
||||
@@ -64,23 +80,251 @@ STYLE_DEFINITIONS: Dict[StyleMode, StyleDefinition] = {
|
||||
description="Film-like visuals with dramatic lighting and anamorphic qualities",
|
||||
keywords=StyleKeywords(
|
||||
primary=["cinematic shot", "film grain", "movie still", "dramatic scene"],
|
||||
lighting=["dramatic lighting", "volumetric light", "rim lighting", "chiaroscuro", "golden hour"],
|
||||
technical=["anamorphic lens", "shallow depth of field", "bokeh", "35mm film", "wide aspect ratio"],
|
||||
composition=["rule of thirds", "leading lines", "dynamic composition", "cinematic framing"],
|
||||
texture=["rich color grading", "film texture", "atmospheric haze"]
|
||||
)
|
||||
lighting=[
|
||||
"dramatic lighting",
|
||||
"volumetric light",
|
||||
"rim lighting",
|
||||
"chiaroscuro",
|
||||
"golden hour",
|
||||
],
|
||||
technical=[
|
||||
"anamorphic lens",
|
||||
"shallow depth of field",
|
||||
"bokeh",
|
||||
"35mm film",
|
||||
"wide aspect ratio",
|
||||
],
|
||||
composition=[
|
||||
"rule of thirds",
|
||||
"leading lines",
|
||||
"dynamic composition",
|
||||
"cinematic framing",
|
||||
],
|
||||
texture=["rich color grading", "film texture", "atmospheric haze"],
|
||||
),
|
||||
),
|
||||
StyleMode.STILL_IMAGE: StyleDefinition(
|
||||
name="Still Image (Photography)",
|
||||
description="Sharp, realistic photography with technical camera specifications",
|
||||
keywords=StyleKeywords(
|
||||
primary=["professional photography", "high resolution", "sharp focus", "studio quality"],
|
||||
lighting=["natural lighting", "soft diffused light", "studio lighting", "balanced exposure"],
|
||||
technical=["f/2.8 aperture", "ISO 100", "sharp optics", "full frame sensor", "RAW quality"],
|
||||
composition=["centered composition", "clean framing", "balanced layout", "professional angle"],
|
||||
texture=["realistic textures", "fine details", "crisp definition", "accurate colors"]
|
||||
)
|
||||
)
|
||||
primary=[
|
||||
"professional photography",
|
||||
"high resolution",
|
||||
"sharp focus",
|
||||
"studio quality",
|
||||
],
|
||||
lighting=[
|
||||
"natural lighting",
|
||||
"soft diffused light",
|
||||
"studio lighting",
|
||||
"balanced exposure",
|
||||
],
|
||||
technical=[
|
||||
"f/2.8 aperture",
|
||||
"ISO 100",
|
||||
"sharp optics",
|
||||
"full frame sensor",
|
||||
"RAW quality",
|
||||
],
|
||||
composition=[
|
||||
"centered composition",
|
||||
"clean framing",
|
||||
"balanced layout",
|
||||
"professional angle",
|
||||
],
|
||||
texture=[
|
||||
"realistic textures",
|
||||
"fine details",
|
||||
"crisp definition",
|
||||
"accurate colors",
|
||||
],
|
||||
),
|
||||
),
|
||||
StyleMode.ANIME: StyleDefinition(
|
||||
name="Anime",
|
||||
description="Vibrant anime-style illustration with dynamic colors",
|
||||
keywords=StyleKeywords(
|
||||
primary=[
|
||||
"anime style",
|
||||
"manga illustration",
|
||||
"cel shading",
|
||||
"vibrant colors",
|
||||
],
|
||||
lighting=[
|
||||
"soft glow",
|
||||
"dramatic backlighting",
|
||||
"ambient light",
|
||||
"bloom effect",
|
||||
],
|
||||
technical=[
|
||||
"clean linework",
|
||||
"flat color areas",
|
||||
"high contrast",
|
||||
"detailed eyes",
|
||||
],
|
||||
composition=[
|
||||
"dynamic pose",
|
||||
"expressive composition",
|
||||
"layered background",
|
||||
],
|
||||
texture=["smooth gradients", "soft skin tones", "vivid saturation"],
|
||||
),
|
||||
),
|
||||
StyleMode.PHOTOREALISTIC: StyleDefinition(
|
||||
name="Photorealistic",
|
||||
description="High-detail realistic images with natural lighting",
|
||||
keywords=StyleKeywords(
|
||||
primary=[
|
||||
"photorealistic",
|
||||
"ultra realistic",
|
||||
"lifelike detail",
|
||||
"high fidelity",
|
||||
],
|
||||
lighting=[
|
||||
"natural lighting",
|
||||
"golden hour",
|
||||
"soft shadows",
|
||||
"ambient occlusion",
|
||||
],
|
||||
technical=[
|
||||
"DSLR quality",
|
||||
"85mm lens",
|
||||
"shallow depth of field",
|
||||
"8K resolution",
|
||||
],
|
||||
composition=["rule of thirds", "natural framing", "environmental portrait"],
|
||||
texture=[
|
||||
"realistic skin texture",
|
||||
"fine material detail",
|
||||
"natural imperfections",
|
||||
],
|
||||
),
|
||||
),
|
||||
StyleMode.FANTASY: StyleDefinition(
|
||||
name="Fantasy",
|
||||
description="Magical elements and themes with ethereal atmosphere",
|
||||
keywords=StyleKeywords(
|
||||
primary=["fantasy art", "magical scene", "mythical", "enchanted"],
|
||||
lighting=[
|
||||
"ethereal glow",
|
||||
"mystical light rays",
|
||||
"bioluminescence",
|
||||
"aurora",
|
||||
],
|
||||
technical=["digital painting", "matte painting", "concept art quality"],
|
||||
composition=[
|
||||
"epic scale",
|
||||
"layered depth",
|
||||
"grand vista",
|
||||
"ornate framing",
|
||||
],
|
||||
texture=[
|
||||
"iridescent surfaces",
|
||||
"crystalline detail",
|
||||
"ancient stonework",
|
||||
"enchanted flora",
|
||||
],
|
||||
),
|
||||
),
|
||||
StyleMode.ABSTRACT: StyleDefinition(
|
||||
name="Abstract",
|
||||
description="Artistic abstract compositions with creative expression",
|
||||
keywords=StyleKeywords(
|
||||
primary=["abstract art", "non-representational", "artistic composition"],
|
||||
lighting=["color field lighting", "gradient transitions", "luminous forms"],
|
||||
technical=["mixed media", "generative art", "high dynamic range"],
|
||||
composition=[
|
||||
"visual rhythm",
|
||||
"asymmetric balance",
|
||||
"flowing forms",
|
||||
"geometric patterns",
|
||||
],
|
||||
texture=[
|
||||
"paint strokes",
|
||||
"textured layers",
|
||||
"organic forms",
|
||||
"splatter effects",
|
||||
],
|
||||
),
|
||||
),
|
||||
StyleMode.CYBERPUNK: StyleDefinition(
|
||||
name="Cyberpunk",
|
||||
description="Neon lights, high technology, and urban dystopia",
|
||||
keywords=StyleKeywords(
|
||||
primary=[
|
||||
"cyberpunk",
|
||||
"neon noir",
|
||||
"dystopian future",
|
||||
"high tech low life",
|
||||
],
|
||||
lighting=[
|
||||
"neon glow",
|
||||
"holographic reflections",
|
||||
"LED strips",
|
||||
"rain-soaked neon",
|
||||
],
|
||||
technical=[
|
||||
"ray tracing",
|
||||
"volumetric fog",
|
||||
"chromatic aberration",
|
||||
"lens flare",
|
||||
],
|
||||
composition=[
|
||||
"urban canyon",
|
||||
"vertical composition",
|
||||
"dense layering",
|
||||
"vanishing point",
|
||||
],
|
||||
texture=[
|
||||
"wet asphalt reflections",
|
||||
"rust and chrome",
|
||||
"holographic surfaces",
|
||||
"circuit patterns",
|
||||
],
|
||||
),
|
||||
),
|
||||
StyleMode.SCI_FI: StyleDefinition(
|
||||
name="Sci-Fi",
|
||||
description="Futuristic technology and space exploration scenes",
|
||||
keywords=StyleKeywords(
|
||||
primary=[
|
||||
"science fiction",
|
||||
"futuristic",
|
||||
"space opera",
|
||||
"advanced technology",
|
||||
],
|
||||
lighting=["starlight", "plasma glow", "engine flare", "atmospheric entry"],
|
||||
technical=[
|
||||
"hard surface modeling",
|
||||
"concept art",
|
||||
"matte painting",
|
||||
"photobashing",
|
||||
],
|
||||
composition=[
|
||||
"epic scale",
|
||||
"orbital view",
|
||||
"dramatic perspective",
|
||||
"vast emptiness",
|
||||
],
|
||||
texture=[
|
||||
"polished metal",
|
||||
"energy fields",
|
||||
"alien materials",
|
||||
"hull plating",
|
||||
],
|
||||
),
|
||||
),
|
||||
StyleMode.VIDEO_WAN: StyleDefinition(
|
||||
name="Video (WanVideo)",
|
||||
description="Minimalist keywords optimized for WanVideo LoRA",
|
||||
keywords=StyleKeywords(
|
||||
primary=["video shot", "motion capture", "fluid movement"],
|
||||
lighting=["cinematic lighting", "natural light"],
|
||||
technical=["smooth motion", "24fps", "high resolution video"],
|
||||
composition=["dynamic camera", "tracking shot"],
|
||||
texture=["temporal consistency", "clean frames"],
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -101,40 +345,37 @@ def get_available_styles() -> List[str]:
|
||||
|
||||
class StylePreset:
|
||||
"""ComfyUI-compatible style preset class."""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self._styles = STYLE_DEFINITIONS
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_style_choices() -> Tuple[str, ...]:
|
||||
"""Get available style choices as tuple (ComfyUI dropdown format)."""
|
||||
return tuple(mode.value for mode in StyleMode)
|
||||
|
||||
|
||||
def get_style_keywords(self, style: str) -> List[str]:
|
||||
"""Get style keywords as a list."""
|
||||
return get_style_keywords(style).to_list()
|
||||
|
||||
|
||||
def get_style_prompt(
|
||||
self,
|
||||
style: str,
|
||||
emphasis: Optional[str] = None,
|
||||
include_technical: bool = True
|
||||
self, style: str, emphasis: Optional[str] = None, include_technical: bool = True
|
||||
) -> str:
|
||||
"""Get a formatted style prompt string."""
|
||||
keywords = get_style_keywords(style)
|
||||
|
||||
|
||||
parts = []
|
||||
parts.extend(keywords.primary)
|
||||
parts.extend(keywords.lighting)
|
||||
parts.extend(keywords.composition)
|
||||
parts.extend(keywords.texture)
|
||||
|
||||
|
||||
if include_technical:
|
||||
parts.extend(keywords.technical)
|
||||
|
||||
|
||||
if emphasis:
|
||||
parts.insert(0, f"emphasis on {emphasis}")
|
||||
|
||||
|
||||
return ", ".join(parts)
|
||||
|
||||
|
||||
@@ -145,6 +386,5 @@ __all__ = [
|
||||
"StylePreset",
|
||||
"get_style_keywords",
|
||||
"get_available_styles",
|
||||
"STYLE_DEFINITIONS"
|
||||
"STYLE_DEFINITIONS",
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user