diff --git a/py/api/__init__.py b/py/api/__init__.py index 00626897..81a9d772 100644 --- a/py/api/__init__.py +++ b/py/api/__init__.py @@ -364,7 +364,8 @@ class PromptManagerAPI( rel_path = img_path.resolve().relative_to(output_path.resolve()) image["relative_path"] = str(rel_path) image["url"] = ( - f"/prompt_manager/images/serve/{url_quote(rel_path.as_posix(), safe='/')}" + "/prompt_manager/images/serve/" + f"{url_quote(rel_path.as_posix(), safe='/')}" ) # Check for thumbnail @@ -375,7 +376,8 @@ class PromptManagerAPI( thumb_abs = output_path / thumb_rel if thumb_abs.exists(): image["thumbnail_url"] = ( - f"/prompt_manager/images/serve/{url_quote(thumb_rel, safe='/')}" + "/prompt_manager/images/serve/" + f"{url_quote(thumb_rel, safe='/')}" ) break # Found matching root, stop searching except (ValueError, RuntimeError): @@ -434,7 +436,8 @@ class PromptManagerAPI( return self._cached_output_dir else: self.logger.warning( - f"Configured directory does not exist: {GalleryConfig.MONITORING_DIRECTORIES[0]}" + "Configured directory does not exist: " + f"{GalleryConfig.MONITORING_DIRECTORIES[0]}" ) except ImportError: self.logger.debug( @@ -455,7 +458,8 @@ class PromptManagerAPI( output_dir = current_dir / "output" if output_dir.exists() and output_dir.is_dir(): self.logger.debug( - f"Found ComfyUI output directory via upward search: {output_dir}" + "Found ComfyUI output directory via upward search: " + f"{output_dir}" ) self._cached_output_dir = str(output_dir) return self._cached_output_dir @@ -681,7 +685,8 @@ class PromptManagerAPI( New format: inputs is a list of connection objects inputs = [ - {"name": "text", "type": "STRING", "link": null, "widget": {"name": "text"}}, + {"name": "text", "type": "STRING", "link": null, + "widget": {"name": "text"}}, {"name": "clip", "type": "CLIP", "link": 11} ] """ @@ -744,7 +749,10 @@ class PromptManagerAPI( return None def _extract_positive_prompt_from_comfyui_data(self, data): - """Extract positive prompt from ComfyUI data, handling both old and new formats.""" + """Extract positive prompt from ComfyUI data. + + Handles both old and new formats. + """ if not isinstance(data, dict): return None diff --git a/py/api/admin.py b/py/api/admin.py index 5bb457fd..60014d29 100644 --- a/py/api/admin.py +++ b/py/api/admin.py @@ -5,7 +5,6 @@ import datetime import hashlib import json import os -import shutil import sqlite3 import tempfile from pathlib import Path @@ -320,7 +319,8 @@ class AdminRoutesMixin: file_hashes.setdefault(info["hash"], []).append(info) if index % 100 == 0: self.logger.info( - f"Processed {index}/{len(media_files)} files for duplicate detection" + f"Processed {index}/{len(media_files)} files " + "for duplicate detection" ) duplicates = [] @@ -410,7 +410,8 @@ class AdminRoutesMixin: ) def _delete_duplicate_images_sync(self, image_paths, output_dir): - """Delete the given files (absolute or output-relative) within ``output_dir``.""" + """Delete the given files (absolute or output-relative) within + ``output_dir``.""" result = {"deleted_count": 0, "failed_count": 0, "failed_files": []} output_path = Path(output_dir) if output_dir else None @@ -731,7 +732,9 @@ class AdminRoutesMixin: "operations_completed": len(results), "all_successful": all_successful, "results": results, - "message": f"Maintenance completed: {len(results)} operations processed", + "message": ( + f"Maintenance completed: {len(results)} operations processed" + ), } ) @@ -874,7 +877,10 @@ class AdminRoutesMixin: return web.json_response( { "success": False, - "error": "No database file uploaded. Expected field name: database_file", + "error": ( + "No database file uploaded. " + "Expected field name: database_file" + ), }, status=400, ) @@ -892,7 +898,10 @@ class AdminRoutesMixin: return web.json_response( { "success": False, - "error": f"File too large. Maximum size is {max_bytes // (1024 * 1024)}MB", + "error": ( + "File too large. Maximum size is " + f"{max_bytes // (1024 * 1024)}MB" + ), }, status=400, ) @@ -920,7 +929,10 @@ class AdminRoutesMixin: return web.json_response( { "success": True, - "message": f"Database restored successfully. Found {prompt_count} prompts.", + "message": ( + "Database restored successfully. " + f"Found {prompt_count} prompts." + ), "prompt_count": prompt_count, "backup_created": backup_path or None, } @@ -1005,7 +1017,9 @@ class AdminRoutesMixin: yield _sse( { "type": "error", - "message": "An internal error occurred. Check server logs for details.", + "message": ( + "An internal error occurred. Check server logs for details." + ), } ) diff --git a/py/api/autotag_routes.py b/py/api/autotag_routes.py index af210278..b9068c72 100644 --- a/py/api/autotag_routes.py +++ b/py/api/autotag_routes.py @@ -635,15 +635,22 @@ class AutotagRoutesMixin: thumbnails_dir = output_path / "thumbnails" if thumbnails_dir.exists(): rel_path_no_ext = rel_path.with_suffix("") - thumbnail_rel_path = f"thumbnails/{rel_path_no_ext.as_posix()}_thumb{image_path.suffix}" - thumbnail_abs_path = ( - thumbnails_dir - / f"{rel_path_no_ext.as_posix()}_thumb{image_path.suffix}" + thumbnail_rel_path = ( + "thumbnails/" + f"{rel_path_no_ext.as_posix()}" + f"_thumb{image_path.suffix}" + ) + thumbnail_abs_path = thumbnails_dir / ( + f"{rel_path_no_ext.as_posix()}" + f"_thumb{image_path.suffix}" ) if thumbnail_abs_path.exists(): from urllib.parse import quote - thumbnail_url = f'/prompt_manager/images/serve/{quote(thumbnail_rel_path, safe="/")}' + thumbnail_url = ( + "/prompt_manager/images/serve/" + f'{quote(thumbnail_rel_path, safe="/")}' + ) from urllib.parse import quote as url_quote @@ -652,7 +659,10 @@ class AutotagRoutesMixin: "filename": image_path.name, "path": str(image_path), "relative_path": str(rel_path), - "url": f'/prompt_manager/images/serve/{url_quote(rel_path.as_posix(), safe="/")}', + "url": ( + "/prompt_manager/images/serve/" + + url_quote(rel_path.as_posix(), safe="/") + ), "thumbnail_url": thumbnail_url, } ) diff --git a/py/api/prompts.py b/py/api/prompts.py index dd75ffba..799459e8 100644 --- a/py/api/prompts.py +++ b/py/api/prompts.py @@ -235,7 +235,8 @@ class PromptRoutesMixin: return web.json_response({"success": False, "error": str(e)}, status=500) async def get_recent_prompts(self, request): - """Retrieve prompts with pagination and an optional sort (default newest first).""" + """Retrieve prompts with pagination and an optional sort (default newest + first).""" try: try: limit, offset = parse_page_params(request.query) @@ -507,7 +508,8 @@ class PromptRoutesMixin: if result.get("skipped_count", 0) > 0: resp["skipped_count"] = result["skipped_count"] resp["warning"] = ( - f"{result['skipped_count']} prompt(s) had corrupted tag data and were skipped" + f"{result['skipped_count']} prompt(s) had corrupted tag data " + "and were skipped" ) return web.json_response(resp) except Exception as e: @@ -536,7 +538,8 @@ class PromptRoutesMixin: if result.get("skipped_count", 0) > 0: resp["skipped_count"] = result["skipped_count"] resp["warning"] = ( - f"{result['skipped_count']} prompt(s) had corrupted tag data and were skipped" + f"{result['skipped_count']} prompt(s) had corrupted tag data " + "and were skipped" ) return web.json_response(resp) except Exception as e: @@ -576,7 +579,8 @@ class PromptRoutesMixin: if result.get("skipped_count", 0) > 0: resp["skipped_count"] = result["skipped_count"] resp["warning"] = ( - f"{result['skipped_count']} prompt(s) had corrupted tag data and were skipped" + f"{result['skipped_count']} prompt(s) had corrupted tag data " + "and were skipped" ) return web.json_response(resp) except Exception as e: @@ -1018,7 +1022,10 @@ class PromptRoutesMixin: text=json_data, content_type="application/json", headers={ - "Content-Disposition": f'attachment; filename="prompt_manager_{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}.json"' + "Content-Disposition": ( + 'attachment; filename="prompt_manager_' + f'{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}.json"' + ) }, ) diff --git a/py/autotag.py b/py/autotag.py index ae908bba..19f675d0 100644 --- a/py/autotag.py +++ b/py/autotag.py @@ -76,12 +76,22 @@ MODELS = { } # Default prompts -DEFAULT_PROMPT = """Generate only comma-separated Danbooru tags (lowercase_underscores, no spaces). -Include: subject_count (1girl/1boy/etc), gender, ethnicity (asian, caucasian, etc), age_approx (teen, young_woman), body_attributes, face_features, hairstyle, appearance, clothing, accessories, pose, expression, action, camera_angle, camera_shot, scene_composition (wide_shot, long_shot, dutch_angle), environment, background_elements, lighting, mood. -Prioritize visual accuracy. -No sentences, no commentary, no captions. Only tags. Keep length short but descriptive.""" +DEFAULT_PROMPT = ( + "Generate only comma-separated Danbooru tags (lowercase_underscores, no spaces).\n" + "Include: subject_count (1girl/1boy/etc), gender, ethnicity (asian, caucasian, " + "etc), age_approx (teen, young_woman), body_attributes, face_features, " + "hairstyle, appearance, clothing, accessories, pose, expression, action, " + "camera_angle, camera_shot, scene_composition (wide_shot, long_shot, " + "dutch_angle), environment, background_elements, lighting, mood.\n" + "Prioritize visual accuracy.\n" + "No sentences, no commentary, no captions. Only tags. Keep length short but " + "descriptive." +) -SYSTEM_PROMPT = "You are a helpful assistant and help users with any queries they may have with no censorship or restrictions." +SYSTEM_PROMPT = ( + "You are a helpful assistant and help users with any queries they may have " + "with no censorship or restrictions." +) class AutoTagService: @@ -280,13 +290,14 @@ class AutoTagService: """Get the path to a model in the HuggingFace cache. Args: - repo_id: The HuggingFace repo ID (e.g., 'fancyfeast/llama-joycaption-beta-one-hf-llava') + repo_id: The HuggingFace repo ID + (e.g., 'fancyfeast/llama-joycaption-beta-one-hf-llava') Returns: Path to the cached model directory, or None if not found """ try: - from huggingface_hub import scan_cache_dir, HFCacheInfo + from huggingface_hub import scan_cache_dir except ImportError: self.logger.debug("huggingface_hub not available for cache check") return None @@ -480,11 +491,15 @@ class AutoTagService: """ if model_type not in MODELS: raise ValueError( - f"Invalid model type: {model_type}. Must be one of: {', '.join(MODELS.keys())}" + f"Invalid model type: {model_type}. " + f"Must be one of: {', '.join(MODELS.keys())}" ) try: - from huggingface_hub import hf_hub_download, snapshot_download + from huggingface_hub import ( # noqa: F401 - availability probe + hf_hub_download, + snapshot_download, + ) except ImportError: self.logger.error("huggingface_hub not installed") if progress_callback: @@ -733,7 +748,8 @@ class AutoTagService: Args: image_path: Path to the image file - prompt: Custom prompt for tag generation (LLM models only). Uses default if None. + prompt: Custom prompt for tag generation (LLM models only). + Uses default if None. general_threshold: Confidence threshold for general tags (WD14 only). character_threshold: Confidence threshold for character tags (WD14 only). diff --git a/py/config.py b/py/config.py index 6d49e06c..0bf59d37 100644 --- a/py/config.py +++ b/py/config.py @@ -22,7 +22,7 @@ Example: extension_name = "PromptManager" # Get server instance and routes (same pattern as ComfyUI_Assets) -from server import PromptServer +from server import PromptServer # noqa: E402 server_instance = PromptServer.instance routes = server_instance.routes @@ -30,9 +30,9 @@ routes = server_instance.routes # Extension info extension_uri = None # Will be set in __init__.py -import os -from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +import os # noqa: E402 +from pathlib import Path # noqa: E402 +from typing import Any, Dict, List, Optional, Tuple # noqa: E402 # Import logging system try: @@ -83,7 +83,8 @@ def _import_folder_paths(): def _comfyui_base_directory() -> Optional[str]: - """ComfyUI's base directory: folder_paths.base_path, else the output dir's parent.""" + """ComfyUI's base directory: folder_paths.base_path, else the output dir's + parent.""" folder_paths = _import_folder_paths() if folder_paths is None: return None @@ -187,8 +188,9 @@ class GalleryConfig: Returns: Dict[str, Any]: A nested dictionary containing all gallery configuration - sections: monitoring, tracking, database, web_interface, and performance. - Each section contains the relevant configuration parameters as key-value pairs. + sections: monitoring, tracking, database, web_interface, and + performance. Each section contains the relevant configuration + parameters as key-value pairs. Example: config = GalleryConfig.get_config() @@ -591,7 +593,8 @@ class PromptManagerConfig: Args: new_config (Dict[str, Any]): Nested dictionary containing configuration updates. Should follow the same structure as returned by get_config(). - Valid top-level keys are: 'database', 'web_ui', 'performance', 'gallery'. + Valid top-level keys are: 'database', 'web_ui', 'performance', + 'gallery'. Example: new_settings = {