Merge branch 'main' into pyproject

This commit is contained in:
gmorks
2025-01-30 10:06:03 -06:00
committed by GitHub
10 changed files with 700 additions and 61 deletions
+28
View File
@@ -0,0 +1,28 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- main
- master
paths:
- "pyproject.toml"
permissions:
issues: write
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'gmorks' }}
steps:
- name: Check out code
uses: actions/checkout@v4
with:
submodules: true
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@v1
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+6 -1
View File
@@ -1 +1,6 @@
__pycache__/
__pycache__/
To-Do.md
logs/
config/animagine_state.json
config/csv_history.json
logs/animagine_prompt_20250130.log
+102 -2
View File
@@ -1,4 +1,104 @@
## ComfyUI Animagine prompt
- ComfyUI note to style your positive and negative prompts with the suggested guidelines on the page model for [Animagine-xl-4.0](https://huggingface.co/cagliostrolab/animagine-xl-4.0)
# ComfyUI Animagine Prompt Node
A ComfyUI node designed to help you structure your prompts following the recommended guidelines for the [Animagine-XL-4.0](https://huggingface.co/cagliostrolab/animagine-xl-4.0) model. This node makes it easy to maintain consistency in your prompts and experiment with different combinations of quality and style tags.
![Node Preview](node_preview.png)
## Features
- **Smart Tag Management**: Easily add and combine quality tags, score tags, and other important modifiers
- **CSV Character Integration**: Load and use character descriptions from CSV files for consistent character generation
- **Flexible Controls**: Toggle different tag groups on/off as needed
- **Built-in Best Practices**: Incorporates recommended negative prompts and tag structures
## Installation
1. Navigate to your ComfyUI custom nodes directory
2. Clone this repository:
```bash
git clone https://github.com/yourusername/ComfyUI-Animagine-Prompt.git
```
3. Restart ComfyUI
## Using the Node
### Basic Usage
1. Add the "Animagine Prompt" node to your workflow
2. Enter your base prompt in the "positive_prompt" field
3. Toggle the desired tag groups (Good Tags, Bad Tags, etc.)
4. Connect the outputs to your text input nodes
### Tag Groups
The node provides several tag groups you can mix and match:
- **Good Tags**
- Quality tags (masterpiece, best quality)
- Score tags (high score, great score, good score)
- **Bad Tags**
- Quality tags (low quality, worst quality)
- Score tags (average score, bad score, low score)
- **Rating Tags**
- Safe, Sensitive, NSFW, Explicit
- **Year Tag**
- Specify a year to influence the style
### Using CSV Character Data
You can load character descriptions from a CSV file to maintain consistency in character generation:
1. Enable the CSV feature using the "use_csv" toggle
2. Provide the path to your CSV file
3. Select the character entry using the index selector
#### CSV File Structure
Your CSV file should have the following columns:
```
GENDER,CHARACTER,COPYRIGHT
1girl,character name,series name
1boy,another character,another series
```
See `create_your_own.txt` for detailed instructions on creating and editing CSV files.
## Tips and Best Practices
- Start with the basic tags enabled and gradually experiment with different combinations
- Keep your base prompts clean and focused - the node will handle the structural elements
- Use the CSV feature for characters you generate frequently to maintain consistency
- Monitor the logs for any issues with CSV loading or prompt generation
## Troubleshooting
### Common Issues
**CSV file not loading:**
- Check that the file path is correct
- Ensure the CSV has the required columns: GENDER, CHARACTER, COPYRIGHT
- Verify the file is saved in UTF-8 format
**Prompts not generating as expected:**
- Check that the appropriate tag groups are enabled
- Verify your base prompt doesn't contain conflicting tags
- Look for any trailing commas or spaces in your prompts
### Performance Notes
- Large CSV files may impact loading times
- The node includes a simple caching system for CSV data
- Consider splitting very large CSV files into smaller, focused collections
## Support
Found a bug or have a suggestion? Please open an issue on the GitHub repository.
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Acknowledgments
- Thanks to the Animagine-XL model creators for their guidelines
- Thanks to the ComfyUI community for their support and feedback
+6 -6
View File
@@ -7,29 +7,29 @@ import os
import logging
from .animagine_node import AnimaginePromptNode
# Configuración básica de logging
# Basic logging configuration
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('AnimaginePrompt')
# Directorio base del nodo
# Base directory of the node
NODE_DIR = os.path.dirname(os.path.realpath(__file__))
SAMPLE_DATA_DIR = os.path.join(NODE_DIR, "sample_data")
# Asegurarse de que existe el directorio de sample_data
# Ensure the sample_data directory exists
if not os.path.exists(SAMPLE_DATA_DIR):
os.makedirs(SAMPLE_DATA_DIR)
# Mapping para el registro del nodo
# Mapping for node registration
NODE_CLASS_MAPPINGS = {
"AnimaginePrompt": AnimaginePromptNode
}
# Mapping para el nombre de visualización
# Mapping for display name
NODE_DISPLAY_NAME_MAPPINGS = {
"AnimaginePrompt": "Animagine Prompt"
}
__version__ = "1.0.0"
__version__ = "1.0.1"
+145 -35
View File
@@ -1,27 +1,28 @@
"""
Main node implementation for Animagine Prompt
Provides prompt structuring and enhancement functionality following Animagine model guidelines
"""
import os
import time
from typing import Tuple, Dict, Any, List, Optional
import logging
from .csv_handler import CSVHandler
logger = logging.getLogger('AnimaginePrompt')
from .logger import logger
from .state_manager import state_manager
class AnimaginePromptNode:
"""
A ComfyUI node that helps structure prompts according to specific guidelines
"""
# Definición de tags predefinidas
# Predefined tag sets
QUALITY_TAGS_GOOD = ["masterpiece", "best quality"]
QUALITY_TAGS_BAD = ["low quality", "worst quality"]
SCORE_TAGS_GOOD = ["high score", "great score", "good score"]
SCORE_TAGS_BAD = ["average score", "bad score", "low score"]
RATING_TAGS = ["safe", "sensitive", "nsfw", "explicit"]
# Tags negativas por defecto según las guidelines
# Default negative tags according to guidelines
DEFAULT_NEGATIVE_TAGS = [
"low quality", "worst quality", "blurry", "bad anatomy",
"bad hands", "text", "error", "missing fingers",
@@ -32,31 +33,39 @@ class AnimaginePromptNode:
def __init__(self):
self.csv_handler = CSVHandler()
self.last_csv_path = None
# Load last state or use defaults
self.current_state = state_manager.get_state()
if not self.current_state:
state_manager.reset_state()
self.current_state = state_manager.get_state()
logger.logger.info("AnimaginePromptNode initialized with saved state")
@classmethod
def INPUT_TYPES(cls) -> Dict[str, Dict[str, Any]]:
"""Define input parameters and their types"""
state = state_manager.get_state()
return {
"required": {
"positive_prompt": ("STRING", {"multiline": True}),
"negative_prompt": ("STRING", {"multiline": True}),
# Good tags controls
"use_good_tags": ("BOOLEAN", {"default": True}),
"use_good_tags": ("BOOLEAN", {"default": state.get("use_good_tags", True)}),
"quality_good": (cls.QUALITY_TAGS_GOOD,),
"score_good": (cls.SCORE_TAGS_GOOD,),
# Bad tags controls
"use_bad_tags": ("BOOLEAN", {"default": False}),
"use_bad_tags": ("BOOLEAN", {"default": state.get("use_bad_tags", False)}),
"quality_bad": (cls.QUALITY_TAGS_BAD,),
"score_bad": (cls.SCORE_TAGS_BAD,),
# Year control
"use_year": ("BOOLEAN", {"default": False}),
"year": ("INT", {"default": 2024}),
"use_year": ("BOOLEAN", {"default": state.get("use_year", False)}),
"year": ("INT", {"default": state.get("year", 2024)}),
# Rating control
"use_rating": ("BOOLEAN", {"default": False}),
"use_rating": ("BOOLEAN", {"default": state.get("use_rating", False)}),
"rating": (cls.RATING_TAGS,),
# CSV control
"use_csv": ("BOOLEAN", {"default": False}),
"csv_path": ("STRING", {"default": ""}),
"csv_index": ("INT", {"default": 0, "min": 0, "max": 9999})
"use_csv": ("BOOLEAN", {"default": state.get("use_csv", False)}),
"csv_path": ("STRING", {"default": state.get("csv_path", "")}),
"csv_index": ("INT", {"default": state.get("csv_index", 0), "min": 0, "max": 9999})
}
}
@@ -66,32 +75,55 @@ class AnimaginePromptNode:
CATEGORY = "prompt"
def _build_good_tags(self, quality_good: str, score_good: str) -> str:
"""Construye la sección de tags positivas"""
"""Build the positive tags section"""
tags = []
if quality_good:
tags.append(quality_good)
if score_good:
tags.append(score_good)
return ", ".join(tags)
result = ", ".join(tags)
logger.logger.debug(f"Built good tags: {result}")
return result
def _build_bad_tags(self, quality_bad: str, score_bad: str) -> str:
"""Construye la sección de tags negativas"""
"""Build the negative tags section"""
tags = []
if quality_bad:
tags.append(quality_bad)
if score_bad:
tags.append(score_bad)
return ", ".join(tags)
result = ", ".join(tags)
logger.logger.debug(f"Built bad tags: {result}")
return result
def _get_csv_entry(self, csv_path: str, index: int) -> Optional[str]:
"""Obtiene y formatea una entrada del CSV"""
"""Get and format an entry from the CSV file"""
start_time = time.time()
if self.last_csv_path != csv_path:
self.csv_handler.load_csv(csv_path)
logger.logger.info(f"Loading new CSV file: {csv_path}")
df = self.csv_handler.load_csv(csv_path)
if df is not None:
state_manager.add_csv_to_history(csv_path, len(df))
self.last_csv_path = csv_path
entry = self.csv_handler.get_entry(index)
end_time = time.time()
logger.log_performance(
"CSV Entry Retrieval",
start_time,
end_time,
{"path": csv_path, "index": index}
)
if entry:
return f"{entry['gender']}, {entry['character']}, {entry['copyright']}"
formatted_entry = f"{entry['gender']}, {entry['character']}, {entry['copyright']}"
logger.logger.debug(f"Formatted CSV entry: {formatted_entry}")
return formatted_entry
return None
def generate_prompt(
@@ -113,58 +145,136 @@ class AnimaginePromptNode:
csv_index: int
) -> Tuple[str, str]:
"""
Genera los prompts finales basados en los parámetros proporcionados
Generate final prompts based on provided parameters
Args:
positive_prompt (str): Base positive prompt
negative_prompt (str): Base negative prompt
use_good_tags (bool): Whether to include good tags
quality_good (str): Selected quality tag from good tags
score_good (str): Selected score tag from good tags
use_bad_tags (bool): Whether to include bad tags
quality_bad (str): Selected quality tag from bad tags
score_bad (str): Selected score tag from bad tags
use_year (bool): Whether to include year tag
year (int): Year to include in prompt
use_rating (bool): Whether to include rating tag
rating (str): Selected rating tag
use_csv (bool): Whether to include CSV entry
csv_path (str): Path to CSV file
csv_index (int): Index of CSV entry to use
Returns:
Tuple[str, str]: Final positive and negative prompts
"""
start_time = time.time()
try:
# Lista para construir el prompt positivo
# Update state with current values
state_manager.update_state({
"use_good_tags": use_good_tags,
"use_bad_tags": use_bad_tags,
"use_year": use_year,
"year": year,
"use_rating": use_rating,
"rating": rating,
"use_csv": use_csv,
"csv_path": csv_path,
"csv_index": csv_index
})
# Log initial state
logger.log_prompt_generation(
positive_prompt,
negative_prompt,
{
"use_good_tags": use_good_tags,
"use_bad_tags": use_bad_tags,
"use_year": use_year,
"use_rating": use_rating,
"use_csv": use_csv
}
)
# Build positive prompt parts
positive_parts = []
# Agregar tags positivas si están activadas
# Add good tags if enabled
if use_good_tags:
good_tags = self._build_good_tags(quality_good, score_good)
if good_tags:
positive_parts.append(good_tags)
logger.logger.debug(f"Added good tags: {good_tags}")
# Agregar bad tags al prompt positivo si están activadas
# Add bad tags to positive prompt if enabled
if use_bad_tags:
bad_tags = self._build_bad_tags(quality_bad, score_bad)
if bad_tags:
positive_parts.append(bad_tags)
logger.logger.debug(f"Added bad tags: {bad_tags}")
# Agregar entrada del CSV si está activado
# Add CSV entry if enabled
if use_csv and csv_path:
csv_entry = self._get_csv_entry(csv_path, csv_index)
if csv_entry:
positive_parts.append(csv_entry)
logger.logger.debug(f"Added CSV entry: {csv_entry}")
# Agregar el prompt base del usuario
# Add base positive prompt
if positive_prompt:
positive_parts.append(positive_prompt)
logger.logger.debug(f"Added base positive prompt: {positive_prompt}")
# Agregar year tag si está activado
# Add year tag if enabled
if use_year:
positive_parts.append(f"year {year}")
year_tag = f"year {year}"
positive_parts.append(year_tag)
logger.logger.debug(f"Added year tag: {year_tag}")
# Agregar rating si está activado
# Add rating if enabled
if use_rating:
positive_parts.append(rating)
logger.logger.debug(f"Added rating: {rating}")
# Construir el prompt negativo
# Build negative prompt parts
negative_parts = []
# Agregar tags negativas por defecto
# Add default negative tags
negative_parts.extend(self.DEFAULT_NEGATIVE_TAGS)
logger.logger.debug("Added default negative tags")
# Agregar el prompt negativo del usuario
# Add user negative prompt
if negative_prompt:
negative_parts.append(negative_prompt)
logger.logger.debug(f"Added user negative prompt: {negative_prompt}")
# Unir todas las partes
# Join all parts
final_positive = ", ".join(part for part in positive_parts if part)
final_negative = ", ".join(part for part in negative_parts if part)
end_time = time.time()
logger.log_performance(
"Prompt Generation",
start_time,
end_time,
{
"positive_parts": len(positive_parts),
"negative_parts": len(negative_parts)
}
)
logger.logger.info("Prompt generation completed successfully")
return final_positive, final_negative
except Exception as e:
logger.error(f"Error generating prompt: {str(e)}")
logger.log_error(
e,
"Prompt Generation",
{
"positive_prompt": positive_prompt,
"negative_prompt": negative_prompt,
"use_csv": use_csv,
"csv_path": csv_path
}
)
raise e
+65 -14
View File
@@ -3,18 +3,17 @@ CSV handling functionality for Animagine Prompt Node
"""
import pandas as pd
import logging
from typing import Optional, List, Dict
from typing import Optional, Dict
import os
logger = logging.getLogger('AnimaginePrompt')
import time
from .logger import logger
class CSVHandler:
"""
Handles loading and processing of CSV files containing character data
"""
REQUIRED_COLUMNS = ['GENDER', 'CHARACTER', 'COPYRIGHT'] # Notar las mayúsculas
REQUIRED_COLUMNS = ['GENDER', 'CHARACTER', 'COPYRIGHT']
def __init__(self):
self.current_data = None
@@ -26,21 +25,33 @@ class CSVHandler:
"""
try:
if not os.path.exists(filepath):
logger.error(f"CSV file not found: {filepath}")
logger.log_error(
FileNotFoundError(f"CSV file not found: {filepath}"),
"CSV Validation",
{"filepath": filepath}
)
return False
df = pd.read_csv(filepath)
logger.info(f"CSV columns found: {df.columns.tolist()}")
logger.log_csv_operation(
"Validation",
filepath,
{"columns_found": df.columns.tolist()}
)
missing_columns = [col for col in self.REQUIRED_COLUMNS if col not in df.columns]
if missing_columns:
logger.error(f"Missing required columns: {missing_columns}")
logger.log_error(
ValueError("Missing required columns"),
"CSV Validation",
{"missing_columns": missing_columns}
)
return False
return True
except Exception as e:
logger.error(f"Error validating CSV structure: {str(e)}")
logger.log_error(e, "CSV Validation", {"filepath": filepath})
return False
def load_csv(self, filepath: str) -> Optional[pd.DataFrame]:
@@ -48,20 +59,39 @@ class CSVHandler:
Loads CSV file with basic caching
"""
try:
start_time = time.time()
if filepath in self.cache:
logger.log_csv_operation(
"Cache Hit",
filepath,
{"cache_size": len(self.cache)}
)
return self.cache[filepath]
if not self.validate_csv_structure(filepath):
logger.error(f"Invalid CSV structure in file: {filepath}")
return None
df = pd.read_csv(filepath)
self.cache[filepath] = df
self.current_data = df
end_time = time.time()
logger.log_performance(
"CSV Load",
start_time,
end_time,
{
"filepath": filepath,
"rows": len(df),
"cached": True
}
)
return df
except Exception as e:
logger.error(f"Error loading CSV file: {str(e)}")
logger.log_error(e, "CSV Load", {"filepath": filepath})
return None
def get_entry(self, index: int) -> Optional[Dict[str, str]]:
@@ -70,18 +100,39 @@ class CSVHandler:
"""
try:
if self.current_data is None:
logger.log_error(
ValueError("No CSV data loaded"),
"Get Entry",
{"index": index}
)
return None
if index >= len(self.current_data):
logger.error(f"Index {index} out of bounds for CSV with {len(self.current_data)} entries")
logger.log_error(
IndexError(f"Index {index} out of bounds"),
"Get Entry",
{
"index": index,
"max_index": len(self.current_data) - 1
}
)
return None
row = self.current_data.iloc[index]
return {
entry = {
'gender': row['GENDER'],
'character': row['CHARACTER'],
'copyright': row['COPYRIGHT']
}
logger.log_csv_operation(
"Get Entry",
None,
{"index": index, "entry": entry}
)
return entry
except Exception as e:
logger.error(f"Error getting CSV entry: {str(e)}")
logger.log_error(e, "Get Entry", {"index": index})
return None
+132
View File
@@ -0,0 +1,132 @@
"""
logger.py - Logging system for ComfyUI Animagine Prompt Node
"""
import logging
import os
from datetime import datetime
from typing import Optional, Dict, Any
class AnimagineLogger:
"""
Custom logger for the Animagine Prompt Node with enhanced debugging capabilities
"""
def __init__(self, log_dir: str = "logs"):
self.log_dir = log_dir
self.logger = None
self.setup_logger()
def setup_logger(self) -> None:
"""
Initialize the logger with both file and console handlers
"""
# Create logs directory if it doesn't exist
if not os.path.exists(self.log_dir):
os.makedirs(self.log_dir)
# Create logger
self.logger = logging.getLogger('AnimaginePrompt')
self.logger.setLevel(logging.DEBUG)
# Remove existing handlers if any
if self.logger.handlers:
self.logger.handlers.clear()
# File handler - detailed logging
log_file = os.path.join(
self.log_dir,
f"animagine_prompt_{datetime.now().strftime('%Y%m%d')}.log"
)
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
file_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(file_formatter)
self.logger.addHandler(file_handler)
# Console handler - important messages only
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_formatter = logging.Formatter(
'%(levelname)s - %(message)s'
)
console_handler.setFormatter(console_formatter)
self.logger.addHandler(console_handler)
def log_prompt_generation(
self,
positive_prompt: str,
negative_prompt: str,
params: Dict[str, Any]
) -> None:
"""
Log prompt generation details
"""
self.logger.debug("=== Prompt Generation Started ===")
self.logger.debug(f"Parameters used: {params}")
self.logger.debug(f"Base positive prompt: {positive_prompt}")
self.logger.debug(f"Base negative prompt: {negative_prompt}")
def log_csv_operation(
self,
operation: str,
filepath: Optional[str] = None,
details: Optional[Dict[str, Any]] = None
) -> None:
"""
Log CSV-related operations
"""
self.logger.debug(f"=== CSV Operation: {operation} ===")
if filepath:
self.logger.debug(f"File: {filepath}")
if details:
self.logger.debug(f"Details: {details}")
def log_error(
self,
error: Exception,
context: str,
details: Optional[Dict[str, Any]] = None
) -> None:
"""
Log errors with context and details
"""
self.logger.error(f"=== Error in {context} ===")
self.logger.error(f"Error type: {type(error).__name__}")
self.logger.error(f"Error message: {str(error)}")
if details:
self.logger.error(f"Additional details: {details}")
def log_performance(
self,
operation: str,
start_time: float,
end_time: float,
details: Optional[Dict[str, Any]] = None
) -> None:
"""
Log performance metrics
"""
duration = end_time - start_time
self.logger.debug(f"=== Performance: {operation} ===")
self.logger.debug(f"Duration: {duration:.4f} seconds")
if details:
self.logger.debug(f"Details: {details}")
def log_state_change(
self,
component: str,
old_state: Any,
new_state: Any
) -> None:
"""
Log state changes in the node
"""
self.logger.debug(f"=== State Change: {component} ===")
self.logger.debug(f"Old state: {old_state}")
self.logger.debug(f"New state: {new_state}")
# Singleton instance
logger = AnimagineLogger()
+40
View File
@@ -0,0 +1,40 @@
2025-01-30 08:55:54,686 - AnimaginePrompt - DEBUG - === CSV Operation: Validation ===
2025-01-30 08:55:54,687 - AnimaginePrompt - DEBUG - File: C:\Users\Alex\Desktop\anime_prompt\ComfyUI\custom_nodes\ComfyUI-Animagine-prompt\example_csv\example.csv
2025-01-30 08:55:54,687 - AnimaginePrompt - DEBUG - Details: {'columns_found': ['GENDER', 'CHARACTER', 'COPYRIGHT']}
2025-01-30 08:55:54,688 - AnimaginePrompt - DEBUG - === Performance: CSV Load ===
2025-01-30 08:55:54,689 - AnimaginePrompt - DEBUG - Duration: 0.0150 seconds
2025-01-30 08:55:54,689 - AnimaginePrompt - DEBUG - Details: {'filepath': 'C:\\Users\\Alex\\Desktop\\anime_prompt\\ComfyUI\\custom_nodes\\ComfyUI-Animagine-prompt\\example_csv\\example.csv', 'rows': 23, 'cached': True}
2025-01-30 08:55:54,689 - AnimaginePrompt - DEBUG - === CSV Operation: Get Entry ===
2025-01-30 08:55:54,690 - AnimaginePrompt - DEBUG - Details: {'index': 4, 'entry': {'gender': '1girl', 'character': 'mokota mememe', 'copyright': '.live'}}
2025-01-30 08:59:14,187 - AnimaginePrompt - INFO - AnimaginePromptNode initialized
2025-01-30 08:59:14,187 - AnimaginePrompt - DEBUG - === Prompt Generation Started ===
2025-01-30 08:59:14,188 - AnimaginePrompt - DEBUG - Parameters used: {'use_good_tags': True, 'use_bad_tags': True, 'use_year': True, 'use_rating': True, 'use_csv': True}
2025-01-30 08:59:14,188 - AnimaginePrompt - DEBUG - Base positive prompt: Positive Prompt
2025-01-30 08:59:14,188 - AnimaginePrompt - DEBUG - Base negative prompt: Negative Prompt
2025-01-30 08:59:14,188 - AnimaginePrompt - DEBUG - Built good tags: masterpiece, high score
2025-01-30 08:59:14,188 - AnimaginePrompt - DEBUG - Added good tags: masterpiece, high score
2025-01-30 08:59:14,188 - AnimaginePrompt - DEBUG - Built bad tags: low quality, average score
2025-01-30 08:59:14,188 - AnimaginePrompt - DEBUG - Added bad tags: low quality, average score
2025-01-30 08:59:14,189 - AnimaginePrompt - INFO - Loading new CSV file: C:\Users\Alex\Desktop\anime_prompt\ComfyUI\custom_nodes\ComfyUI-Animagine-prompt\example_csv\example.csv
2025-01-30 08:59:14,192 - AnimaginePrompt - DEBUG - === CSV Operation: Validation ===
2025-01-30 08:59:14,192 - AnimaginePrompt - DEBUG - File: C:\Users\Alex\Desktop\anime_prompt\ComfyUI\custom_nodes\ComfyUI-Animagine-prompt\example_csv\example.csv
2025-01-30 08:59:14,193 - AnimaginePrompt - DEBUG - Details: {'columns_found': ['GENDER', 'CHARACTER', 'COPYRIGHT']}
2025-01-30 08:59:14,194 - AnimaginePrompt - DEBUG - === Performance: CSV Load ===
2025-01-30 08:59:14,194 - AnimaginePrompt - DEBUG - Duration: 0.0056 seconds
2025-01-30 08:59:14,195 - AnimaginePrompt - DEBUG - Details: {'filepath': 'C:\\Users\\Alex\\Desktop\\anime_prompt\\ComfyUI\\custom_nodes\\ComfyUI-Animagine-prompt\\example_csv\\example.csv', 'rows': 23, 'cached': True}
2025-01-30 08:59:14,195 - AnimaginePrompt - DEBUG - === CSV Operation: Get Entry ===
2025-01-30 08:59:14,195 - AnimaginePrompt - DEBUG - Details: {'index': 4, 'entry': {'gender': '1girl', 'character': 'mokota mememe', 'copyright': '.live'}}
2025-01-30 08:59:14,195 - AnimaginePrompt - DEBUG - === Performance: CSV Entry Retrieval ===
2025-01-30 08:59:14,196 - AnimaginePrompt - DEBUG - Duration: 0.0066 seconds
2025-01-30 08:59:14,196 - AnimaginePrompt - DEBUG - Details: {'path': 'C:\\Users\\Alex\\Desktop\\anime_prompt\\ComfyUI\\custom_nodes\\ComfyUI-Animagine-prompt\\example_csv\\example.csv', 'index': 4}
2025-01-30 08:59:14,196 - AnimaginePrompt - DEBUG - Formatted CSV entry: 1girl, mokota mememe, .live
2025-01-30 08:59:14,196 - AnimaginePrompt - DEBUG - Added CSV entry: 1girl, mokota mememe, .live
2025-01-30 08:59:14,196 - AnimaginePrompt - DEBUG - Added base positive prompt: Positive Prompt
2025-01-30 08:59:14,197 - AnimaginePrompt - DEBUG - Added year tag: year 2024
2025-01-30 08:59:14,197 - AnimaginePrompt - DEBUG - Added rating: safe
2025-01-30 08:59:14,197 - AnimaginePrompt - DEBUG - Added default negative tags
2025-01-30 08:59:14,197 - AnimaginePrompt - DEBUG - Added user negative prompt: Negative Prompt
2025-01-30 08:59:14,197 - AnimaginePrompt - DEBUG - === Performance: Prompt Generation ===
2025-01-30 08:59:14,198 - AnimaginePrompt - DEBUG - Duration: 0.0101 seconds
2025-01-30 08:59:14,198 - AnimaginePrompt - DEBUG - Details: {'positive_parts': 6, 'negative_parts': 17}
2025-01-30 08:59:14,198 - AnimaginePrompt - INFO - Prompt generation completed successfully
+3 -3
View File
@@ -1,7 +1,7 @@
[project]
name = "animagine-prompt"
description = "Comfy UI node to prompt build for [a/https://huggingface.co/cagliostrolab/animagine-xl-4.0](https://huggingface.co/cagliostrolab/animagine-xl-4.0) model"
version = "1.0.0"
description = "Comfy UI node to prompt build for https://huggingface.co/cagliostrolab/animagine-xl-4.0 model "
version = "1.0.1"
license = {file = "LICENSE"}
[project.urls]
@@ -9,6 +9,6 @@ Repository = "https://github.com/gmorks/ComfyUI-Animagine-Prompt"
# Used by Comfy Registry https://comfyregistry.org
[tool.comfy]
PublisherId = ""
PublisherId = "gmorks"
DisplayName = "ComfyUI-Animagine-Prompt"
Icon = ""
+173
View File
@@ -0,0 +1,173 @@
"""
state_manager.py - State management for ComfyUI Animagine Prompt Node
"""
import json
import os
from typing import Dict, Any, Optional
from datetime import datetime
from .logger import logger
class StateManager:
"""
Manages the persistence and restoration of node states
"""
def __init__(self, config_dir: str = "config"):
self.config_dir = config_dir
self.state_file = os.path.join(config_dir, "animagine_state.json")
self.history_file = os.path.join(config_dir, "csv_history.json")
self.current_state: Dict[str, Any] = {}
self.csv_history: Dict[str, Dict[str, Any]] = {}
self._initialize()
def _initialize(self) -> None:
"""Initialize the state system"""
# Create config directory if it doesn't exist
if not os.path.exists(self.config_dir):
os.makedirs(self.config_dir)
# Load existing state if available
self._load_state()
self._load_csv_history()
def _load_state(self) -> None:
"""Load the last saved state"""
try:
if os.path.exists(self.state_file):
with open(self.state_file, 'r', encoding='utf-8') as f:
self.current_state = json.load(f)
logger.logger.debug("State loaded successfully")
except Exception as e:
logger.log_error(e, "State Loading")
self.current_state = {}
def _load_csv_history(self) -> None:
"""Load CSV usage history"""
try:
if os.path.exists(self.history_file):
with open(self.history_file, 'r', encoding='utf-8') as f:
self.csv_history = json.load(f)
logger.logger.debug("CSV history loaded successfully")
except Exception as e:
logger.log_error(e, "CSV History Loading")
self.csv_history = {}
def _save_state(self) -> None:
"""Save current state to file"""
try:
with open(self.state_file, 'w', encoding='utf-8') as f:
json.dump(self.current_state, f, indent=2)
logger.logger.debug("State saved successfully")
except Exception as e:
logger.log_error(e, "State Saving")
def _save_csv_history(self) -> None:
"""Save CSV history to file"""
try:
with open(self.history_file, 'w', encoding='utf-8') as f:
json.dump(self.csv_history, f, indent=2)
logger.logger.debug("CSV history saved successfully")
except Exception as e:
logger.log_error(e, "CSV History Saving")
def update_state(self, new_state: Dict[str, Any]) -> None:
"""
Update the current state with new values
"""
old_state = self.current_state.copy()
self.current_state.update(new_state)
self._save_state()
logger.log_state_change(
"Node State",
old_state,
self.current_state
)
def get_state(self) -> Dict[str, Any]:
"""
Get the current state
"""
return self.current_state
def reset_state(self) -> None:
"""
Reset state to default values
"""
default_state = {
"use_good_tags": True,
"use_bad_tags": False,
"use_year": False,
"year": 2024,
"use_rating": False,
"rating": "safe",
"use_csv": False,
"csv_path": "",
"csv_index": 0
}
old_state = self.current_state.copy()
self.current_state = default_state
self._save_state()
logger.log_state_change(
"State Reset",
old_state,
default_state
)
def add_csv_to_history(self, filepath: str, entry_count: int) -> None:
"""
Add a CSV file to the usage history
"""
if filepath not in self.csv_history:
self.csv_history[filepath] = {
"first_used": datetime.now().isoformat(),
"last_used": datetime.now().isoformat(),
"use_count": 1,
"entry_count": entry_count
}
else:
self.csv_history[filepath].update({
"last_used": datetime.now().isoformat(),
"use_count": self.csv_history[filepath]["use_count"] + 1,
"entry_count": entry_count
})
self._save_csv_history()
logger.logger.debug(f"Added CSV to history: {filepath}")
def get_csv_history(self, limit: Optional[int] = None) -> Dict[str, Dict[str, Any]]:
"""
Get CSV usage history, optionally limited to the most recent entries
"""
if not limit:
return self.csv_history
# Sort by last_used date and return limited number
sorted_history = dict(
sorted(
self.csv_history.items(),
key=lambda x: x[1]["last_used"],
reverse=True
)[:limit]
)
return sorted_history
def clear_csv_history(self) -> None:
"""
Clear the CSV usage history
"""
old_history = self.csv_history.copy()
self.csv_history = {}
self._save_csv_history()
logger.log_state_change(
"CSV History Clear",
old_history,
{}
)
# Singleton instance
state_manager = StateManager()