Merge pull request #11 from ComfyAssets/fixup_scanning
Fixup scanning and possible fixes for #8
This commit is contained in:
+27
-12
@@ -3,15 +3,30 @@ PromptManager: A ComfyUI custom node that extends the standard text encoder
|
||||
with persistent prompt storage and advanced search capabilities using SQLite.
|
||||
"""
|
||||
|
||||
print("[PromptManager] Starting to load PromptManager custom node...")
|
||||
# Import logging system
|
||||
try:
|
||||
from .utils.logging_config import get_logger
|
||||
init_logger = get_logger('prompt_manager.init')
|
||||
init_logger.info("Starting to load PromptManager custom node...")
|
||||
except ImportError:
|
||||
# Fallback to print if logging isn't available yet
|
||||
init_logger = None
|
||||
print("[PromptManager] Starting to load PromptManager custom node...")
|
||||
|
||||
def log_message(message, level='info'):
|
||||
"""Helper to log messages with fallback to print."""
|
||||
if init_logger:
|
||||
getattr(init_logger, level)(message.replace("[PromptManager] ", ""))
|
||||
else:
|
||||
print(f"[PromptManager] {message}")
|
||||
|
||||
try:
|
||||
from .prompt_manager import PromptManager
|
||||
print("[PromptManager] Successfully imported PromptManager class")
|
||||
log_message("Successfully imported PromptManager class")
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] ERROR: Failed to import PromptManager class: {e}")
|
||||
log_message(f"ERROR: Failed to import PromptManager class: {e}", 'error')
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
log_message(f"Traceback: {traceback.format_exc()}", 'error')
|
||||
raise
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
@@ -22,8 +37,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PromptManager": "Prompt Manager",
|
||||
}
|
||||
|
||||
print(f"[PromptManager] NODE_CLASS_MAPPINGS: {NODE_CLASS_MAPPINGS}")
|
||||
print(f"[PromptManager] NODE_DISPLAY_NAME_MAPPINGS: {NODE_DISPLAY_NAME_MAPPINGS}")
|
||||
log_message(f"NODE_CLASS_MAPPINGS: {NODE_CLASS_MAPPINGS}")
|
||||
log_message(f"NODE_DISPLAY_NAME_MAPPINGS: {NODE_DISPLAY_NAME_MAPPINGS}")
|
||||
|
||||
# Define path to web directory for UI components
|
||||
WEB_DIRECTORY = "web"
|
||||
@@ -43,15 +58,15 @@ try:
|
||||
api = PromptManagerAPI()
|
||||
api.add_routes(routes)
|
||||
|
||||
print("[PromptManager] API routes registered successfully")
|
||||
print(f"[PromptManager] Server instance: {config.server_instance}")
|
||||
print(f"[PromptManager] Routes object: {routes}")
|
||||
log_message("API routes registered successfully")
|
||||
log_message(f"Server instance: {config.server_instance}")
|
||||
log_message(f"Routes object: {routes}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Failed to register API routes: {e}")
|
||||
log_message(f"Failed to register API routes: {e}", 'error')
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
log_message(f"Traceback: {traceback.format_exc()}", 'error')
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
|
||||
print("[PromptManager] ✅ PromptManager custom node loaded successfully!")
|
||||
log_message("✅ PromptManager custom node loaded successfully!")
|
||||
+21
-10
@@ -6,6 +6,15 @@ import sqlite3
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
# Import logging system
|
||||
try:
|
||||
from ..utils.logging_config import get_logger
|
||||
except ImportError:
|
||||
import sys
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, current_dir)
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
|
||||
class PromptModel:
|
||||
"""Database model for prompt storage and schema management."""
|
||||
@@ -17,6 +26,8 @@ class PromptModel:
|
||||
Args:
|
||||
db_path: Path to the SQLite database file
|
||||
"""
|
||||
self.logger = get_logger('prompt_manager.database.models')
|
||||
self.logger.info(f"Initializing database model with path: {db_path}")
|
||||
self.db_path = db_path
|
||||
self._ensure_database_exists()
|
||||
|
||||
@@ -29,7 +40,7 @@ class PromptModel:
|
||||
self._create_indexes(conn)
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
print(f"Error creating database: {e}")
|
||||
self.logger.error(f"Error creating database: {e}")
|
||||
raise
|
||||
|
||||
def _create_tables(self, conn: sqlite3.Connection) -> None:
|
||||
@@ -109,7 +120,7 @@ class PromptModel:
|
||||
columns = [column[1] for column in cursor.fetchall()]
|
||||
|
||||
if 'workflow_name' in columns:
|
||||
print("[KikoTextEncode] Migrating database: removing workflow_name column")
|
||||
self.logger.info("Migrating database: removing workflow_name column")
|
||||
|
||||
# Create new table without workflow_name
|
||||
conn.execute("""
|
||||
@@ -137,10 +148,10 @@ class PromptModel:
|
||||
conn.execute("DROP TABLE prompts")
|
||||
conn.execute("ALTER TABLE prompts_new RENAME TO prompts")
|
||||
|
||||
print("[KikoTextEncode] Database migration completed")
|
||||
self.logger.info("Database migration completed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[KikoTextEncode] Migration error: {e}")
|
||||
self.logger.error(f"Migration error: {e}")
|
||||
# If migration fails, the table creation will handle it
|
||||
|
||||
def _migrate_foreign_key_types(self, conn: sqlite3.Connection) -> None:
|
||||
@@ -151,7 +162,7 @@ class PromptModel:
|
||||
columns = {column[1]: column[2] for column in cursor.fetchall()}
|
||||
|
||||
if 'prompt_id' in columns and columns['prompt_id'] == 'TEXT':
|
||||
print("[KikoTextEncode] Migrating foreign key types: prompt_id TEXT -> INTEGER")
|
||||
self.logger.info("Migrating foreign key types: prompt_id TEXT -> INTEGER")
|
||||
|
||||
# Create new table with correct types
|
||||
conn.execute("""
|
||||
@@ -188,10 +199,10 @@ class PromptModel:
|
||||
conn.execute("DROP TABLE generated_images")
|
||||
conn.execute("ALTER TABLE generated_images_new RENAME TO generated_images")
|
||||
|
||||
print("[KikoTextEncode] Foreign key migration completed")
|
||||
self.logger.info("Foreign key migration completed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[KikoTextEncode] Foreign key migration error: {e}")
|
||||
self.logger.error(f"Foreign key migration error: {e}")
|
||||
# If migration fails, continue with existing schema
|
||||
|
||||
def migrate_database(self) -> None:
|
||||
@@ -206,7 +217,7 @@ class PromptModel:
|
||||
conn.execute("VACUUM")
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
print(f"Error vacuuming database: {e}")
|
||||
self.logger.error(f"Error vacuuming database: {e}")
|
||||
|
||||
def get_database_info(self) -> dict:
|
||||
"""
|
||||
@@ -241,7 +252,7 @@ class PromptModel:
|
||||
'database_path': os.path.abspath(self.db_path)
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error getting database info: {e}")
|
||||
self.logger.error(f"Error getting database info: {e}")
|
||||
return {}
|
||||
|
||||
def backup_database(self, backup_path: str) -> bool:
|
||||
@@ -259,5 +270,5 @@ class PromptModel:
|
||||
shutil.copy2(self.db_path, backup_path)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error creating database backup: {e}")
|
||||
self.logger.error(f"Error creating database backup: {e}")
|
||||
return False
|
||||
+24
-4
@@ -10,6 +10,15 @@ from typing import Optional, List, Dict, Any, Union
|
||||
|
||||
from .models import PromptModel
|
||||
|
||||
# Import logging system
|
||||
try:
|
||||
from ..utils.logging_config import get_logger
|
||||
except ImportError:
|
||||
import sys
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, current_dir)
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
|
||||
class PromptDatabase:
|
||||
"""Database operations class for managing prompts."""
|
||||
@@ -21,7 +30,10 @@ class PromptDatabase:
|
||||
Args:
|
||||
db_path: Path to the SQLite database file
|
||||
"""
|
||||
self.logger = get_logger('prompt_manager.database')
|
||||
self.logger.info(f"Initializing database operations with path: {db_path}")
|
||||
self.model = PromptModel(db_path)
|
||||
self.logger.debug("Database operations initialized successfully")
|
||||
|
||||
def save_prompt(
|
||||
self,
|
||||
@@ -58,6 +70,8 @@ class PromptDatabase:
|
||||
|
||||
tags_json = json.dumps(tags) if tags else None
|
||||
|
||||
self.logger.debug(f"Saving prompt: text_length={len(text)}, category={category}, tags={tags}, rating={rating}")
|
||||
|
||||
with self.model.get_connection() as conn:
|
||||
cursor = conn.execute(
|
||||
"""
|
||||
@@ -77,7 +91,9 @@ class PromptDatabase:
|
||||
)
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
prompt_id = cursor.lastrowid
|
||||
self.logger.info(f"Successfully saved prompt with ID: {prompt_id}")
|
||||
return prompt_id
|
||||
|
||||
def get_prompt_by_id(self, prompt_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -410,7 +426,7 @@ class PromptDatabase:
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error exporting prompts: {e}")
|
||||
self.logger.error(f"Error exporting prompts: {e}")
|
||||
return False
|
||||
|
||||
def cleanup_duplicates(self) -> int:
|
||||
@@ -420,6 +436,7 @@ class PromptDatabase:
|
||||
Returns:
|
||||
int: Number of duplicates removed
|
||||
"""
|
||||
self.logger.info("Starting duplicate cleanup process")
|
||||
try:
|
||||
with self.model.get_connection() as conn:
|
||||
# Find duplicates by text content (case-insensitive)
|
||||
@@ -432,6 +449,7 @@ class PromptDatabase:
|
||||
""")
|
||||
|
||||
duplicates = cursor.fetchall()
|
||||
self.logger.debug(f"Found {len(duplicates)} groups of duplicate prompts")
|
||||
total_removed = 0
|
||||
|
||||
for duplicate in duplicates:
|
||||
@@ -449,12 +467,14 @@ class PromptDatabase:
|
||||
conn.commit()
|
||||
|
||||
if total_removed > 0:
|
||||
print(f"[KikoTextEncode] Removed {total_removed} duplicate prompts")
|
||||
self.logger.info(f"Removed {total_removed} duplicate prompts")
|
||||
else:
|
||||
self.logger.info("No duplicate prompts found")
|
||||
|
||||
return total_removed
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error cleaning up duplicates: {e}")
|
||||
self.logger.error(f"Error cleaning up duplicates: {e}")
|
||||
return 0
|
||||
|
||||
# Gallery-related methods
|
||||
|
||||
+44
-19
@@ -10,6 +10,14 @@ import webbrowser
|
||||
import os
|
||||
from typing import Optional, Dict, Any, Tuple, List
|
||||
|
||||
# Import logging system
|
||||
try:
|
||||
from .utils.logging_config import get_logger
|
||||
except ImportError:
|
||||
import sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
try:
|
||||
from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict
|
||||
except ImportError:
|
||||
@@ -48,12 +56,16 @@ class PromptManager(ComfyNodeABC):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.logger = get_logger('prompt_manager.node')
|
||||
self.logger.info("Initializing PromptManager node")
|
||||
|
||||
self.db = PromptDatabase()
|
||||
self.prompt_tracker = PromptTracker(self.db)
|
||||
self.image_monitor = ImageMonitor(self.db, self.prompt_tracker)
|
||||
|
||||
# Start image monitoring automatically
|
||||
self._start_gallery_system()
|
||||
self.logger.info("PromptManager node initialization completed")
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> InputTypeDict:
|
||||
@@ -125,15 +137,18 @@ class PromptManager(ComfyNodeABC):
|
||||
|
||||
# Validate CLIP model
|
||||
if clip is None:
|
||||
raise RuntimeError(
|
||||
error_msg = (
|
||||
"ERROR: clip input is invalid: None\n\n"
|
||||
"If the clip is from a checkpoint loader node your checkpoint does not "
|
||||
"contain a valid clip or text encoder model."
|
||||
)
|
||||
self.logger.error("CLIP validation failed: clip input is None")
|
||||
raise RuntimeError(error_msg)
|
||||
|
||||
# Save prompt to database and set execution context for gallery tracking
|
||||
prompt_id = None
|
||||
if text and text.strip():
|
||||
self.logger.debug(f"Processing prompt text: {text[:100]}...")
|
||||
try:
|
||||
prompt_id = self._save_prompt_to_database(
|
||||
text=text.strip(), # Always strip whitespace
|
||||
@@ -151,16 +166,19 @@ class PromptManager(ComfyNodeABC):
|
||||
'prompt_id': prompt_id
|
||||
}
|
||||
)
|
||||
print(f"[PromptManager] Set execution context: {execution_id}")
|
||||
self.logger.info(f"Set execution context: {execution_id} for prompt ID: {prompt_id}")
|
||||
|
||||
except Exception as e:
|
||||
# Log error but don't fail the encoding
|
||||
print(f"Warning: Failed to save prompt to database: {e}")
|
||||
self.logger.warning(f"Failed to save prompt to database: {e}")
|
||||
# Already logged above, no need for additional print
|
||||
|
||||
# Perform standard CLIP text encoding
|
||||
self.logger.debug("Performing CLIP text encoding")
|
||||
tokens = clip.tokenize(text)
|
||||
conditioning = clip.encode_from_tokens_scheduled(tokens)
|
||||
|
||||
self.logger.debug("CLIP encoding completed successfully")
|
||||
return (conditioning,)
|
||||
|
||||
def _save_prompt_to_database(
|
||||
@@ -183,10 +201,12 @@ class PromptManager(ComfyNodeABC):
|
||||
try:
|
||||
# Generate hash for duplicate detection
|
||||
prompt_hash = self._generate_hash(text)
|
||||
self.logger.debug(f"Generated hash for prompt: {prompt_hash[:16]}...")
|
||||
|
||||
# Check if prompt already exists
|
||||
existing = self.db.get_prompt_by_hash(prompt_hash)
|
||||
if existing:
|
||||
self.logger.info(f"Found existing prompt with ID {existing['id']}, updating metadata")
|
||||
# Update metadata if this is a duplicate with new info
|
||||
if any([category, tags]):
|
||||
self.db.update_prompt_metadata(
|
||||
@@ -194,9 +214,11 @@ class PromptManager(ComfyNodeABC):
|
||||
category=category,
|
||||
tags=tags
|
||||
)
|
||||
self.logger.debug("Updated metadata for existing prompt")
|
||||
return existing['id']
|
||||
|
||||
# Save new prompt
|
||||
self.logger.debug(f"Saving new prompt with category: {category}, tags: {tags}")
|
||||
prompt_id = self.db.save_prompt(
|
||||
text=text,
|
||||
category=category,
|
||||
@@ -204,10 +226,16 @@ class PromptManager(ComfyNodeABC):
|
||||
prompt_hash=prompt_hash
|
||||
)
|
||||
|
||||
if prompt_id:
|
||||
self.logger.info(f"Successfully saved new prompt with ID: {prompt_id}")
|
||||
else:
|
||||
self.logger.warning("Failed to save prompt - no ID returned")
|
||||
|
||||
return prompt_id
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error saving prompt to database: {e}")
|
||||
self.logger.error(f"Error saving prompt to database: {e}")
|
||||
# Already logged above, no need for additional print
|
||||
return None
|
||||
|
||||
def _generate_hash(self, text: str) -> str:
|
||||
@@ -241,7 +269,7 @@ class PromptManager(ComfyNodeABC):
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error searching prompts: {e}")
|
||||
self.logger.error(f"Error searching prompts: {e}")
|
||||
return []
|
||||
|
||||
def _open_web_interface(self):
|
||||
@@ -256,17 +284,14 @@ class PromptManager(ComfyNodeABC):
|
||||
index_path = os.path.join(web_dir, "index.html")
|
||||
if os.path.exists(index_path):
|
||||
webbrowser.open(f"file://{index_path}")
|
||||
print("\n=== Web interface opened in browser ===")
|
||||
self.logger.info("Web interface opened in browser")
|
||||
else:
|
||||
print("\n=== Web interface directory found but no index.html ===")
|
||||
print(f"Please check {web_dir} for setup instructions")
|
||||
self.logger.warning(f"Web interface directory found but no index.html. Please check {web_dir} for setup instructions")
|
||||
else:
|
||||
print("\n=== Web interface not yet implemented ===")
|
||||
print("This feature will open a web-based prompt management interface")
|
||||
print("when the web_interface directory is created.")
|
||||
self.logger.info("Web interface not yet implemented. This feature will open a web-based prompt management interface when the web_interface directory is created.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error opening web interface: {e}")
|
||||
self.logger.error(f"Error opening web interface: {e}")
|
||||
|
||||
def search_prompts_api(self, search_text: str = "") -> List[Dict[str, Any]]:
|
||||
"""API method for JavaScript UI to search prompts."""
|
||||
@@ -277,22 +302,22 @@ class PromptManager(ComfyNodeABC):
|
||||
try:
|
||||
return self.db.get_recent_prompts(limit=limit)
|
||||
except Exception as e:
|
||||
print(f"Error getting recent prompts: {e}")
|
||||
self.logger.error(f"Error getting recent prompts: {e}")
|
||||
return []
|
||||
|
||||
def _start_gallery_system(self):
|
||||
"""Initialize and start the gallery monitoring system."""
|
||||
try:
|
||||
print("[PromptManager] Starting gallery system...")
|
||||
self.logger.info("Starting gallery system...")
|
||||
|
||||
# Start image monitoring
|
||||
self.image_monitor.start_monitoring()
|
||||
|
||||
print("[PromptManager] Gallery system started successfully")
|
||||
self.logger.info("Gallery system started successfully")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Failed to start gallery system: {e}")
|
||||
print("[PromptManager] Gallery features will be disabled")
|
||||
self.logger.error(f"Failed to start gallery system: {e}")
|
||||
self.logger.warning("Gallery features will be disabled")
|
||||
|
||||
def get_gallery_status(self) -> Dict[str, Any]:
|
||||
"""Get status of the gallery system."""
|
||||
@@ -306,9 +331,9 @@ class PromptManager(ComfyNodeABC):
|
||||
try:
|
||||
if hasattr(self, 'image_monitor'):
|
||||
self.image_monitor.stop_monitoring()
|
||||
print("[PromptManager] Gallery system cleaned up")
|
||||
self.logger.info("Gallery system cleaned up")
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error cleaning up gallery system: {e}")
|
||||
self.logger.error(f"Error cleaning up gallery system: {e}")
|
||||
|
||||
def __del__(self):
|
||||
"""Cleanup when object is destroyed."""
|
||||
|
||||
@@ -13,6 +13,7 @@ from PIL import Image
|
||||
# Import database operations
|
||||
try:
|
||||
from ..database.operations import PromptDatabase
|
||||
from ..utils.logging_config import get_logger
|
||||
except ImportError:
|
||||
# Fallback for when module isn't imported as package
|
||||
import os
|
||||
@@ -20,23 +21,27 @@ except ImportError:
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from database.operations import PromptDatabase
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
|
||||
class PromptManagerAPI:
|
||||
"""API class for PromptManager database operations."""
|
||||
|
||||
def __init__(self):
|
||||
self.logger = get_logger('prompt_manager.api')
|
||||
self.logger.info("Initializing PromptManager API")
|
||||
|
||||
self.db = PromptDatabase()
|
||||
|
||||
# Run cleanup on initialization to remove any existing duplicates
|
||||
try:
|
||||
removed = self.db.cleanup_duplicates()
|
||||
if removed > 0:
|
||||
print(
|
||||
f"[PromptManager] Startup cleanup: removed {removed} duplicate prompts"
|
||||
)
|
||||
self.logger.info(f"Startup cleanup: removed {removed} duplicate prompts")
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Startup cleanup failed: {e}")
|
||||
self.logger.error(f"Startup cleanup failed: {e}")
|
||||
|
||||
self.logger.info("PromptManager API initialization completed")
|
||||
|
||||
def add_routes(self, routes):
|
||||
"""Add API routes to ComfyUI server using decorator pattern."""
|
||||
@@ -250,7 +255,36 @@ class PromptManagerAPI:
|
||||
async def scan_images_route(request):
|
||||
return await self.scan_images(request)
|
||||
|
||||
print("[PromptManager] All routes registered with decorator pattern")
|
||||
# Logging endpoints
|
||||
@routes.get("/prompt_manager/logs")
|
||||
async def get_logs_route(request):
|
||||
return await self.get_logs(request)
|
||||
|
||||
@routes.get("/prompt_manager/logs/files")
|
||||
async def get_log_files_route(request):
|
||||
return await self.get_log_files(request)
|
||||
|
||||
@routes.get("/prompt_manager/logs/download/{filename}")
|
||||
async def download_log_route(request):
|
||||
return await self.download_log_file(request)
|
||||
|
||||
@routes.post("/prompt_manager/logs/truncate")
|
||||
async def truncate_logs_route(request):
|
||||
return await self.truncate_logs(request)
|
||||
|
||||
@routes.get("/prompt_manager/logs/config")
|
||||
async def get_log_config_route(request):
|
||||
return await self.get_log_config(request)
|
||||
|
||||
@routes.post("/prompt_manager/logs/config")
|
||||
async def update_log_config_route(request):
|
||||
return await self.update_log_config(request)
|
||||
|
||||
@routes.get("/prompt_manager/logs/stats")
|
||||
async def get_log_stats_route(request):
|
||||
return await self.get_log_stats(request)
|
||||
|
||||
self.logger.info("All routes registered with decorator pattern")
|
||||
|
||||
async def search_prompts(self, request):
|
||||
"""
|
||||
@@ -290,8 +324,7 @@ class PromptManagerAPI:
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Search error: {e}")
|
||||
print(traceback.format_exc())
|
||||
self.logger.error(f"Search error: {e}", exc_info=True)
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Search failed: {str(e)}", "results": []},
|
||||
status=500,
|
||||
@@ -312,8 +345,7 @@ class PromptManagerAPI:
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Recent prompts error: {e}")
|
||||
print(traceback.format_exc())
|
||||
self.logger.error(f"Recent prompts error: {e}", exc_info=True)
|
||||
return web.json_response(
|
||||
{
|
||||
"success": False,
|
||||
@@ -334,7 +366,7 @@ class PromptManagerAPI:
|
||||
return web.json_response({"success": True, "categories": categories})
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Categories error: {e}")
|
||||
self.logger.error(f"Categories error: {e}")
|
||||
return web.json_response(
|
||||
{
|
||||
"success": False,
|
||||
@@ -355,7 +387,7 @@ class PromptManagerAPI:
|
||||
return web.json_response({"success": True, "tags": tags})
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Tags error: {e}")
|
||||
self.logger.error(f"Tags error: {e}")
|
||||
return web.json_response(
|
||||
{
|
||||
"success": False,
|
||||
@@ -430,8 +462,7 @@ class PromptManagerAPI:
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Save error: {e}")
|
||||
print(traceback.format_exc())
|
||||
self.logger.error(f"Save error: {e}", exc_info=True)
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to save prompt: {str(e)}"},
|
||||
status=500,
|
||||
@@ -465,7 +496,7 @@ class PromptManagerAPI:
|
||||
{"success": False, "error": "Invalid prompt ID"}, status=400
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Delete error: {e}")
|
||||
self.logger.error(f"Delete error: {e}")
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to delete prompt: {str(e)}"},
|
||||
status=500,
|
||||
@@ -488,7 +519,7 @@ class PromptManagerAPI:
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Cleanup error: {e}")
|
||||
self.logger.error(f"Cleanup error: {e}")
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to cleanup duplicates: {str(e)}"},
|
||||
status=500,
|
||||
@@ -539,7 +570,7 @@ class PromptManagerAPI:
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Stats error: {e}")
|
||||
self.logger.error(f"Stats error: {e}")
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to get statistics: {str(e)}"},
|
||||
status=500,
|
||||
@@ -611,7 +642,7 @@ class PromptManagerAPI:
|
||||
{"success": False, "error": "Invalid prompt ID"}, status=400
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Update prompt error: {e}")
|
||||
self.logger.error(f"Update prompt error: {e}")
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to update prompt: {str(e)}"},
|
||||
status=500,
|
||||
@@ -655,7 +686,7 @@ class PromptManagerAPI:
|
||||
{"success": False, "error": "Invalid prompt ID"}, status=400
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Update rating error: {e}")
|
||||
self.logger.error(f"Update rating error: {e}")
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to update rating: {str(e)}"},
|
||||
status=500,
|
||||
@@ -710,7 +741,7 @@ class PromptManagerAPI:
|
||||
{"success": False, "error": "Invalid prompt ID"}, status=400
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Add tag error: {e}")
|
||||
self.logger.error(f"Add tag error: {e}")
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to add tag: {str(e)}"}, status=500
|
||||
)
|
||||
@@ -759,7 +790,7 @@ class PromptManagerAPI:
|
||||
{"success": False, "error": "Invalid prompt ID"}, status=400
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Remove tag error: {e}")
|
||||
self.logger.error(f"Remove tag error: {e}")
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to remove tag: {str(e)}"},
|
||||
status=500,
|
||||
@@ -798,7 +829,7 @@ class PromptManagerAPI:
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Bulk delete error: {e}")
|
||||
self.logger.error(f"Bulk delete error: {e}")
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to delete prompts: {str(e)}"},
|
||||
status=500,
|
||||
@@ -865,7 +896,7 @@ class PromptManagerAPI:
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Bulk add tags error: {e}")
|
||||
self.logger.error(f"Bulk add tags error: {e}")
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to add tags: {str(e)}"}, status=500
|
||||
)
|
||||
@@ -906,7 +937,7 @@ class PromptManagerAPI:
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Bulk set category error: {e}")
|
||||
self.logger.error(f"Bulk set category error: {e}")
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to set category: {str(e)}"},
|
||||
status=500,
|
||||
@@ -937,7 +968,7 @@ class PromptManagerAPI:
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Export error: {e}")
|
||||
self.logger.error(f"Export error: {e}")
|
||||
return web.json_response(
|
||||
{"success": False, "error": f"Failed to export prompts: {str(e)}"},
|
||||
status=500,
|
||||
@@ -987,7 +1018,7 @@ class PromptManagerAPI:
|
||||
|
||||
return web.json_response(cleaned_data)
|
||||
except Exception as json_error:
|
||||
print(f"[PromptManager API] JSON cleaning error: {json_error}")
|
||||
self.logger.error(f"JSON cleaning error: {json_error}")
|
||||
# Fallback to original response
|
||||
return web.json_response({
|
||||
'success': True,
|
||||
@@ -995,7 +1026,7 @@ class PromptManagerAPI:
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Get prompt images error: {e}")
|
||||
self.logger.error(f"Get prompt images error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
@@ -1012,7 +1043,7 @@ class PromptManagerAPI:
|
||||
'images': images
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Get recent images error: {e}")
|
||||
self.logger.error(f"Get recent images error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
@@ -1036,7 +1067,7 @@ class PromptManagerAPI:
|
||||
'query': query
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Search images error: {e}")
|
||||
self.logger.error(f"Search images error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
@@ -1083,7 +1114,7 @@ class PromptManagerAPI:
|
||||
except ValueError:
|
||||
return web.json_response({'error': 'Invalid image ID'}, status=400)
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Serve image error: {e}")
|
||||
self.logger.error(f"Serve image error: {e}")
|
||||
return web.json_response({'error': str(e)}, status=500)
|
||||
|
||||
async def link_image_to_prompt(self, request):
|
||||
@@ -1118,7 +1149,7 @@ class PromptManagerAPI:
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Link image error: {e}")
|
||||
self.logger.error(f"Link image error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
@@ -1144,7 +1175,7 @@ class PromptManagerAPI:
|
||||
except ValueError:
|
||||
return web.json_response({'error': 'Invalid image ID'}, status=400)
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Delete image error: {e}")
|
||||
self.logger.error(f"Delete image error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
@@ -1241,9 +1272,7 @@ class PromptManagerAPI:
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Diagnostics error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.logger.error(f"Diagnostics error: {e}", exc_info=True)
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
@@ -1298,9 +1327,7 @@ class PromptManagerAPI:
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Test link error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.logger.error(f"Test link error: {e}", exc_info=True)
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
@@ -1456,9 +1483,7 @@ class PromptManagerAPI:
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Maintenance error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.logger.error(f"Maintenance error: {e}", exc_info=True)
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': f'Maintenance failed: {str(e)}'
|
||||
@@ -1512,9 +1537,7 @@ class PromptManagerAPI:
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Backup error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.logger.error(f"Backup error: {e}", exc_info=True)
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': f'Failed to backup database: {str(e)}'
|
||||
@@ -1586,7 +1609,7 @@ class PromptManagerAPI:
|
||||
# Create backup of current database if it exists
|
||||
if os.path.exists(db_path):
|
||||
shutil.copy2(db_path, backup_path)
|
||||
print(f"[PromptManager] Current database backed up to: {backup_path}")
|
||||
self.logger.info(f"Current database backed up to: {backup_path}")
|
||||
|
||||
# Replace current database with uploaded one
|
||||
shutil.copy2(temp_path, db_path)
|
||||
@@ -1617,9 +1640,7 @@ class PromptManagerAPI:
|
||||
os.unlink(temp_path)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager API] Restore error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.logger.error(f"Restore error: {e}", exc_info=True)
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': f'Failed to restore database: {str(e)}'
|
||||
@@ -1637,9 +1658,17 @@ class PromptManagerAPI:
|
||||
|
||||
async def stream_response():
|
||||
try:
|
||||
self.logger.info("Starting image scan operation")
|
||||
|
||||
# Clear any active prompt tracking timers to avoid conflicts
|
||||
# Note: For now we'll skip this since we don't have easy access to the tracker instance
|
||||
# This is mainly important if scans are run during active generation, which is rare
|
||||
self.logger.info("Starting scan (timer clearing not implemented yet)")
|
||||
|
||||
# Find ComfyUI output directory
|
||||
output_dir = self._find_comfyui_output_dir()
|
||||
if not output_dir:
|
||||
self.logger.error("ComfyUI output directory not found")
|
||||
yield f"data: {json.dumps({'type': 'error', 'message': 'ComfyUI output directory not found'})}\n\n"
|
||||
return
|
||||
|
||||
@@ -1658,6 +1687,7 @@ class PromptManagerAPI:
|
||||
processed_count = 0
|
||||
found_count = 0
|
||||
added_count = 0
|
||||
linked_count = 0 # Count of images linked to existing prompts
|
||||
|
||||
for i, png_file in enumerate(png_files):
|
||||
try:
|
||||
@@ -1666,11 +1696,11 @@ class PromptManagerAPI:
|
||||
processed_count += 1
|
||||
|
||||
if metadata:
|
||||
print(f"[PromptManager] Found metadata in {os.path.basename(png_file)}: {list(metadata.keys())}")
|
||||
self.logger.debug(f"Found metadata in {os.path.basename(png_file)}: {list(metadata.keys())}")
|
||||
|
||||
# Parse ComfyUI prompt data
|
||||
parsed_data = self._parse_comfyui_prompt(metadata)
|
||||
print(f"[PromptManager] Parsed data keys: {list(parsed_data.keys())}, has prompt: {bool(parsed_data.get('prompt'))}, has parameters: {bool(parsed_data.get('parameters'))}")
|
||||
self.logger.debug(f"Parsed data keys: {list(parsed_data.keys())}, has prompt: {bool(parsed_data.get('prompt'))}, has parameters: {bool(parsed_data.get('parameters'))}")
|
||||
|
||||
# Check if we found any meaningful prompt data
|
||||
if parsed_data.get('prompt') or parsed_data.get('parameters'):
|
||||
@@ -1681,13 +1711,13 @@ class PromptManagerAPI:
|
||||
|
||||
# Debug: print what we found and its type
|
||||
if prompt_text:
|
||||
print(f"[PromptManager] Found prompt in {os.path.basename(png_file)} (type: {type(prompt_text)}): {str(prompt_text)[:100]}...")
|
||||
self.logger.debug(f"Found prompt in {os.path.basename(png_file)} (type: {type(prompt_text)}): {str(prompt_text)[:100]}...")
|
||||
else:
|
||||
print(f"[PromptManager] No readable prompt found in {os.path.basename(png_file)}, parsed_data keys: {list(parsed_data.keys())}")
|
||||
self.logger.debug(f"No readable prompt found in {os.path.basename(png_file)}, parsed_data keys: {list(parsed_data.keys())}")
|
||||
|
||||
# Ensure prompt_text is a string
|
||||
if prompt_text and not isinstance(prompt_text, str):
|
||||
print(f"[PromptManager] Converting prompt_text from {type(prompt_text)} to string")
|
||||
self.logger.debug(f"Converting prompt_text from {type(prompt_text)} to string")
|
||||
prompt_text = str(prompt_text)
|
||||
|
||||
if prompt_text and prompt_text.strip():
|
||||
@@ -1703,17 +1733,22 @@ class PromptManagerAPI:
|
||||
from utils.hashing import generate_prompt_hash
|
||||
|
||||
prompt_hash = generate_prompt_hash(prompt_text.strip())
|
||||
self.logger.debug(f"Generated hash for prompt: {prompt_hash[:16]}...")
|
||||
|
||||
# Check if prompt already exists
|
||||
existing = self.db.get_prompt_by_hash(prompt_hash)
|
||||
if existing:
|
||||
self.logger.debug(f"Found existing prompt ID {existing['id']} for image {os.path.basename(png_file)}")
|
||||
# Link image to existing prompt
|
||||
try:
|
||||
self.db.link_image_to_prompt(existing['id'], str(png_file))
|
||||
linked_count += 1
|
||||
self.logger.debug(f"Linked image {os.path.basename(png_file)} to existing prompt {existing['id']}")
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Failed to link image {png_file}: {e}")
|
||||
self.logger.error(f"Failed to link image {png_file} to existing prompt: {e}")
|
||||
else:
|
||||
# Save new prompt
|
||||
self.logger.debug(f"Saving new prompt from {os.path.basename(png_file)}")
|
||||
prompt_id = self.db.save_prompt(
|
||||
text=prompt_text.strip(),
|
||||
category='scanned',
|
||||
@@ -1724,15 +1759,19 @@ class PromptManagerAPI:
|
||||
|
||||
if prompt_id:
|
||||
added_count += 1
|
||||
self.logger.info(f"Successfully saved new prompt with ID {prompt_id} from {os.path.basename(png_file)}")
|
||||
|
||||
# Link image to new prompt
|
||||
try:
|
||||
self.db.link_image_to_prompt(prompt_id, str(png_file))
|
||||
self.logger.debug(f"Linked image {os.path.basename(png_file)} to new prompt {prompt_id}")
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Failed to link image {png_file}: {e}")
|
||||
self.logger.error(f"Failed to link image {png_file} to new prompt: {e}")
|
||||
else:
|
||||
self.logger.error(f"Failed to save prompt from {os.path.basename(png_file)} - no ID returned")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Failed to save prompt from {png_file}: {e}")
|
||||
self.logger.error(f"Failed to save prompt from {png_file}: {e}")
|
||||
|
||||
# Update progress every 10 files or so
|
||||
if i % 10 == 0 or i == total_files - 1:
|
||||
@@ -1745,16 +1784,17 @@ class PromptManagerAPI:
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error processing {png_file}: {e}")
|
||||
self.logger.error(f"Error processing {png_file}: {e}")
|
||||
continue
|
||||
|
||||
# Send completion message
|
||||
yield f"data: {json.dumps({'type': 'complete', 'processed': processed_count, 'found': found_count, 'added': added_count})}\n\n"
|
||||
self.logger.info(f"Scan completed: processed={processed_count}, found={found_count}, new_prompts_added={added_count}, images_linked_to_existing={linked_count}")
|
||||
yield f"data: {json.dumps({'type': 'complete', 'processed': processed_count, 'found': found_count, 'added': added_count, 'linked': linked_count})}\n\n"
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Scan error: {e}")
|
||||
self.logger.error(f"Scan error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.logger.error(f"Scan error traceback: {traceback.format_exc()}")
|
||||
yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n"
|
||||
|
||||
# Return streaming response
|
||||
@@ -1791,10 +1831,10 @@ class PromptManagerAPI:
|
||||
for path in possible_paths:
|
||||
abs_path = os.path.abspath(path)
|
||||
if os.path.exists(abs_path) and os.path.isdir(abs_path):
|
||||
print(f"[PromptManager] Found ComfyUI output directory: {abs_path}")
|
||||
self.logger.info(f"Found ComfyUI output directory: {abs_path}")
|
||||
return abs_path
|
||||
|
||||
print("[PromptManager] ComfyUI output directory not found")
|
||||
self.logger.warning("ComfyUI output directory not found")
|
||||
return None
|
||||
|
||||
def _extract_comfyui_metadata(self, image_path):
|
||||
@@ -1807,7 +1847,7 @@ class PromptManagerAPI:
|
||||
metadata[key] = value
|
||||
return metadata
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error reading {image_path}: {e}")
|
||||
self.logger.error(f"Error reading {image_path}: {e}")
|
||||
return {}
|
||||
|
||||
def _parse_comfyui_prompt(self, metadata):
|
||||
@@ -1886,3 +1926,267 @@ class PromptManagerAPI:
|
||||
return safe_to_string(parsed_data['parameters']['positive'])
|
||||
|
||||
return None
|
||||
|
||||
# Logging API endpoints
|
||||
async def get_logs(self, request):
|
||||
"""
|
||||
Get recent log entries.
|
||||
GET /prompt_manager/logs?limit=100&level=INFO
|
||||
"""
|
||||
try:
|
||||
# Import logger here to avoid circular imports
|
||||
try:
|
||||
from ..utils.logging_config import get_logger_manager
|
||||
except ImportError:
|
||||
import sys
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, current_dir)
|
||||
from utils.logging_config import get_logger_manager
|
||||
|
||||
logger_manager = get_logger_manager()
|
||||
|
||||
# Get query parameters
|
||||
limit = int(request.query.get('limit', 100))
|
||||
level = request.query.get('level', None)
|
||||
|
||||
# Validate limit
|
||||
if limit > 1000:
|
||||
limit = 1000
|
||||
elif limit < 1:
|
||||
limit = 1
|
||||
|
||||
# Get logs from memory buffer
|
||||
logs = logger_manager.get_recent_logs(limit=limit, level=level)
|
||||
|
||||
return web.json_response({
|
||||
'success': True,
|
||||
'logs': logs,
|
||||
'count': len(logs),
|
||||
'level_filter': level,
|
||||
'limit': limit
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Get logs error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'logs': []
|
||||
}, status=500)
|
||||
|
||||
async def get_log_files(self, request):
|
||||
"""
|
||||
Get information about available log files.
|
||||
GET /prompt_manager/logs/files
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
from ..utils.logging_config import get_logger_manager
|
||||
except ImportError:
|
||||
import sys
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, current_dir)
|
||||
from utils.logging_config import get_logger_manager
|
||||
|
||||
logger_manager = get_logger_manager()
|
||||
log_files = logger_manager.get_log_files()
|
||||
|
||||
return web.json_response({
|
||||
'success': True,
|
||||
'files': log_files,
|
||||
'count': len(log_files)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Get log files error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'files': []
|
||||
}, status=500)
|
||||
|
||||
async def download_log_file(self, request):
|
||||
"""
|
||||
Download a specific log file.
|
||||
GET /prompt_manager/logs/download/{filename}
|
||||
"""
|
||||
try:
|
||||
filename = request.match_info['filename']
|
||||
|
||||
try:
|
||||
from ..utils.logging_config import get_logger_manager
|
||||
except ImportError:
|
||||
import sys
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, current_dir)
|
||||
from utils.logging_config import get_logger_manager
|
||||
|
||||
logger_manager = get_logger_manager()
|
||||
|
||||
# Validate filename for security
|
||||
if not filename or '..' in filename or '/' in filename or '\\' in filename:
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': 'Invalid filename'
|
||||
}, status=400)
|
||||
|
||||
# Get the file content
|
||||
log_file_path = logger_manager.log_dir / filename
|
||||
|
||||
if not log_file_path.exists():
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': 'Log file not found'
|
||||
}, status=404)
|
||||
|
||||
# Read file content
|
||||
with open(log_file_path, 'rb') as f:
|
||||
file_content = f.read()
|
||||
|
||||
return web.Response(
|
||||
body=file_content,
|
||||
content_type='text/plain',
|
||||
headers={
|
||||
'Content-Disposition': f'attachment; filename="{filename}"',
|
||||
'Content-Length': str(len(file_content))
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Download log file error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
async def truncate_logs(self, request):
|
||||
"""
|
||||
Truncate all log files.
|
||||
POST /prompt_manager/logs/truncate
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
from ..utils.logging_config import get_logger_manager
|
||||
except ImportError:
|
||||
import sys
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, current_dir)
|
||||
from utils.logging_config import get_logger_manager
|
||||
|
||||
logger_manager = get_logger_manager()
|
||||
results = logger_manager.truncate_logs()
|
||||
|
||||
return web.json_response({
|
||||
'success': True,
|
||||
'message': f"Truncated {len(results['truncated'])} log files",
|
||||
'results': results
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Truncate logs error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
async def get_log_config(self, request):
|
||||
"""
|
||||
Get current logging configuration.
|
||||
GET /prompt_manager/logs/config
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
from ..utils.logging_config import get_logger_manager
|
||||
except ImportError:
|
||||
import sys
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, current_dir)
|
||||
from utils.logging_config import get_logger_manager
|
||||
|
||||
logger_manager = get_logger_manager()
|
||||
config = logger_manager.get_config()
|
||||
|
||||
return web.json_response({
|
||||
'success': True,
|
||||
'config': config
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Get log config error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
async def update_log_config(self, request):
|
||||
"""
|
||||
Update logging configuration.
|
||||
POST /prompt_manager/logs/config
|
||||
Body: {"level": "DEBUG", "console_logging": true, ...}
|
||||
"""
|
||||
try:
|
||||
data = await request.json()
|
||||
|
||||
try:
|
||||
from ..utils.logging_config import get_logger_manager
|
||||
except ImportError:
|
||||
import sys
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, current_dir)
|
||||
from utils.logging_config import get_logger_manager
|
||||
|
||||
logger_manager = get_logger_manager()
|
||||
|
||||
# Validate level if provided
|
||||
if 'level' in data:
|
||||
valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
|
||||
if data['level'].upper() not in valid_levels:
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': f'Invalid log level. Must be one of: {valid_levels}'
|
||||
}, status=400)
|
||||
data['level'] = data['level'].upper()
|
||||
|
||||
logger_manager.update_config(data)
|
||||
|
||||
return web.json_response({
|
||||
'success': True,
|
||||
'message': 'Logging configuration updated',
|
||||
'config': logger_manager.get_config()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Update log config error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
async def get_log_stats(self, request):
|
||||
"""
|
||||
Get logging statistics.
|
||||
GET /prompt_manager/logs/stats
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
from ..utils.logging_config import get_logger_manager
|
||||
except ImportError:
|
||||
import sys
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, current_dir)
|
||||
from utils.logging_config import get_logger_manager
|
||||
|
||||
logger_manager = get_logger_manager()
|
||||
stats = logger_manager.get_log_stats()
|
||||
|
||||
return web.json_response({
|
||||
'success': True,
|
||||
'stats': stats
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Get log stats error: {e}")
|
||||
return web.json_response({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
+18
-6
@@ -18,6 +18,18 @@ Configuration settings for PromptManager gallery and monitoring system.
|
||||
import os
|
||||
from typing import Dict, Any, List
|
||||
|
||||
# Import logging system
|
||||
try:
|
||||
from ..utils.logging_config import get_logger
|
||||
except ImportError:
|
||||
import sys
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, current_dir)
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
# Initialize logger for config operations
|
||||
config_logger = get_logger('prompt_manager.config')
|
||||
|
||||
|
||||
class GalleryConfig:
|
||||
"""Configuration for the gallery monitoring system."""
|
||||
@@ -127,11 +139,11 @@ class PromptManagerConfig:
|
||||
with open(config_path, 'r') as f:
|
||||
config = json.load(f)
|
||||
cls.update_config(config)
|
||||
print(f"[PromptManager] Loaded configuration from {config_path}")
|
||||
config_logger.info(f"Loaded configuration from {config_path}")
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error loading config from {config_path}: {e}")
|
||||
config_logger.error(f"Error loading config from {config_path}: {e}")
|
||||
else:
|
||||
print(f"[PromptManager] Config file not found: {config_path}, using defaults")
|
||||
config_logger.info(f"Config file not found: {config_path}, using defaults")
|
||||
|
||||
@classmethod
|
||||
def save_to_file(cls, config_path: str):
|
||||
@@ -145,9 +157,9 @@ class PromptManagerConfig:
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
print(f"[PromptManager] Saved configuration to {config_path}")
|
||||
config_logger.info(f"Saved configuration to {config_path}")
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error saving config to {config_path}: {e}")
|
||||
config_logger.error(f"Error saving config to {config_path}: {e}")
|
||||
|
||||
@classmethod
|
||||
def update_config(cls, new_config: Dict[str, Any]):
|
||||
@@ -187,4 +199,4 @@ try:
|
||||
config_file = os.path.join(config_dir, 'config.json')
|
||||
PromptManagerConfig.load_from_file(config_file)
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error during config initialization: {e}")
|
||||
config_logger.error(f"Error during config initialization: {e}")
|
||||
+37
-34
@@ -7,18 +7,21 @@ import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List
|
||||
|
||||
from .logging_config import get_logger
|
||||
|
||||
|
||||
class GalleryDiagnostics:
|
||||
"""Diagnostics for the gallery system."""
|
||||
|
||||
def __init__(self, db_path: str = "prompts.db"):
|
||||
self.db_path = db_path
|
||||
self.logger = get_logger('prompt_manager.diagnostics')
|
||||
|
||||
def run_full_diagnostic(self) -> Dict[str, Any]:
|
||||
"""Run a complete diagnostic check."""
|
||||
print("\n" + "="*60)
|
||||
print("🔍 PROMPTMANAGER GALLERY DIAGNOSTICS")
|
||||
print("="*60)
|
||||
self.logger.info("\n" + "="*60)
|
||||
self.logger.info("🔍 PROMPTMANAGER GALLERY DIAGNOSTICS")
|
||||
self.logger.info("="*60)
|
||||
|
||||
results = {
|
||||
'database': self.check_database(),
|
||||
@@ -28,22 +31,22 @@ class GalleryDiagnostics:
|
||||
'dependencies': self.check_dependencies()
|
||||
}
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("📋 DIAGNOSTIC SUMMARY")
|
||||
print("="*60)
|
||||
self.logger.info("\n" + "="*60)
|
||||
self.logger.info("📋 DIAGNOSTIC SUMMARY")
|
||||
self.logger.info("="*60)
|
||||
|
||||
for category, result in results.items():
|
||||
status = "✅ PASS" if result['status'] == 'ok' else "❌ FAIL"
|
||||
print(f"{category.upper():<20} {status}")
|
||||
self.logger.info(f"{category.upper():<20} {status}")
|
||||
if result['status'] != 'ok':
|
||||
print(f" Issue: {result['message']}")
|
||||
self.logger.warning(f" Issue: {result['message']}")
|
||||
|
||||
print("\n" + "="*60)
|
||||
self.logger.info("\n" + "="*60)
|
||||
return results
|
||||
|
||||
def check_database(self) -> Dict[str, Any]:
|
||||
"""Check database connection and structure."""
|
||||
print("\n🗄️ Checking Database...")
|
||||
self.logger.info("\n🗄️ Checking Database...")
|
||||
|
||||
try:
|
||||
if not os.path.exists(self.db_path):
|
||||
@@ -58,7 +61,7 @@ class GalleryDiagnostics:
|
||||
# Check prompts table
|
||||
cursor = conn.execute("SELECT COUNT(*) as count FROM prompts")
|
||||
prompt_count = cursor.fetchone()['count']
|
||||
print(f" 📝 Prompts in database: {prompt_count}")
|
||||
self.logger.info(f" 📝 Prompts in database: {prompt_count}")
|
||||
|
||||
# Check if generated_images table exists
|
||||
cursor = conn.execute("""
|
||||
@@ -67,7 +70,7 @@ class GalleryDiagnostics:
|
||||
""")
|
||||
|
||||
has_images_table = cursor.fetchone() is not None
|
||||
print(f" 🖼️ Images table exists: {has_images_table}")
|
||||
self.logger.info(f" 🖼️ Images table exists: {has_images_table}")
|
||||
|
||||
return {
|
||||
'status': 'ok',
|
||||
@@ -83,7 +86,7 @@ class GalleryDiagnostics:
|
||||
|
||||
def check_images_table(self) -> Dict[str, Any]:
|
||||
"""Check the generated_images table specifically."""
|
||||
print("\n🖼️ Checking Images Table...")
|
||||
self.logger.info("\n🖼️ Checking Images Table...")
|
||||
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
@@ -104,7 +107,7 @@ class GalleryDiagnostics:
|
||||
# Check image records
|
||||
cursor = conn.execute("SELECT COUNT(*) as count FROM generated_images")
|
||||
image_count = cursor.fetchone()['count']
|
||||
print(f" 📊 Images in database: {image_count}")
|
||||
self.logger.info(f" 📊 Images in database: {image_count}")
|
||||
|
||||
# Get recent images
|
||||
cursor = conn.execute("""
|
||||
@@ -116,9 +119,9 @@ class GalleryDiagnostics:
|
||||
""")
|
||||
recent_images = [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
print(f" 🕒 Recent images: {len(recent_images)}")
|
||||
self.logger.info(f" 🕒 Recent images: {len(recent_images)}")
|
||||
for img in recent_images:
|
||||
print(f" - {img['filename']} -> Prompt {img['prompt_id']}")
|
||||
self.logger.info(f" - {img['filename']} -> Prompt {img['prompt_id']}")
|
||||
|
||||
return {
|
||||
'status': 'ok',
|
||||
@@ -134,12 +137,12 @@ class GalleryDiagnostics:
|
||||
|
||||
def check_file_system(self) -> Dict[str, Any]:
|
||||
"""Check file system and permissions."""
|
||||
print("\n📁 Checking File System...")
|
||||
self.logger.info("\n📁 Checking File System...")
|
||||
|
||||
try:
|
||||
# Check current directory
|
||||
current_dir = os.getcwd()
|
||||
print(f" 📂 Current directory: {current_dir}")
|
||||
self.logger.info(f" 📂 Current directory: {current_dir}")
|
||||
|
||||
# Check if we can write to current directory
|
||||
test_file = "test_write.tmp"
|
||||
@@ -151,7 +154,7 @@ class GalleryDiagnostics:
|
||||
except:
|
||||
can_write = False
|
||||
|
||||
print(f" ✏️ Can write to directory: {can_write}")
|
||||
self.logger.info(f" ✏️ Can write to directory: {can_write}")
|
||||
|
||||
return {
|
||||
'status': 'ok',
|
||||
@@ -167,7 +170,7 @@ class GalleryDiagnostics:
|
||||
|
||||
def check_comfyui_output(self) -> Dict[str, Any]:
|
||||
"""Check ComfyUI output directories."""
|
||||
print("\n🎨 Checking ComfyUI Output...")
|
||||
self.logger.info("\n🎨 Checking ComfyUI Output...")
|
||||
|
||||
output_dirs = []
|
||||
|
||||
@@ -185,16 +188,16 @@ class GalleryDiagnostics:
|
||||
abs_path = os.path.abspath(dir_path)
|
||||
if os.path.exists(abs_path):
|
||||
output_dirs.append(abs_path)
|
||||
print(f" 📁 Found output dir: {abs_path}")
|
||||
self.logger.info(f" 📁 Found output dir: {abs_path}")
|
||||
|
||||
# Count images in this directory
|
||||
try:
|
||||
image_files = []
|
||||
for ext in ['.png', '.jpg', '.jpeg', '.webp']:
|
||||
image_files.extend(Path(abs_path).rglob(f'*{ext}'))
|
||||
print(f" 🖼️ Images found: {len(image_files)}")
|
||||
self.logger.info(f" 🖼️ Images found: {len(image_files)}")
|
||||
except Exception as e:
|
||||
print(f" ❌ Error scanning: {e}")
|
||||
self.logger.error(f" ❌ Error scanning: {e}")
|
||||
|
||||
# Try ComfyUI's folder_paths
|
||||
try:
|
||||
@@ -202,9 +205,9 @@ class GalleryDiagnostics:
|
||||
comfyui_output = folder_paths.get_output_directory()
|
||||
if comfyui_output and comfyui_output not in output_dirs:
|
||||
output_dirs.append(comfyui_output)
|
||||
print(f" 📁 ComfyUI output dir: {comfyui_output}")
|
||||
self.logger.info(f" 📁 ComfyUI output dir: {comfyui_output}")
|
||||
except ImportError:
|
||||
print(" ⚠️ ComfyUI folder_paths not available")
|
||||
self.logger.warning(" ⚠️ ComfyUI folder_paths not available")
|
||||
|
||||
return {
|
||||
'status': 'ok' if output_dirs else 'warning',
|
||||
@@ -214,7 +217,7 @@ class GalleryDiagnostics:
|
||||
|
||||
def check_dependencies(self) -> Dict[str, Any]:
|
||||
"""Check required dependencies."""
|
||||
print("\n📦 Checking Dependencies...")
|
||||
self.logger.info("\n📦 Checking Dependencies...")
|
||||
|
||||
dependencies = {
|
||||
'watchdog': False,
|
||||
@@ -226,25 +229,25 @@ class GalleryDiagnostics:
|
||||
try:
|
||||
import watchdog
|
||||
dependencies['watchdog'] = True
|
||||
print(f" ✅ watchdog: {watchdog.__version__}")
|
||||
self.logger.info(f" ✅ watchdog: {watchdog.__version__}")
|
||||
except ImportError:
|
||||
print(" ❌ watchdog: NOT INSTALLED")
|
||||
self.logger.error(" ❌ watchdog: NOT INSTALLED")
|
||||
|
||||
# Check PIL
|
||||
try:
|
||||
from PIL import Image
|
||||
dependencies['PIL'] = True
|
||||
print(f" ✅ PIL (Pillow): Available")
|
||||
self.logger.info(f" ✅ PIL (Pillow): Available")
|
||||
except ImportError:
|
||||
print(" ❌ PIL (Pillow): NOT AVAILABLE")
|
||||
self.logger.error(" ❌ PIL (Pillow): NOT AVAILABLE")
|
||||
|
||||
# Check sqlite3
|
||||
try:
|
||||
import sqlite3
|
||||
dependencies['sqlite3'] = True
|
||||
print(f" ✅ sqlite3: {sqlite3.sqlite_version}")
|
||||
self.logger.info(f" ✅ sqlite3: {sqlite3.sqlite_version}")
|
||||
except ImportError:
|
||||
print(" ❌ sqlite3: NOT AVAILABLE")
|
||||
self.logger.error(" ❌ sqlite3: NOT AVAILABLE")
|
||||
|
||||
all_deps_ok = all(dependencies.values())
|
||||
|
||||
@@ -256,7 +259,7 @@ class GalleryDiagnostics:
|
||||
|
||||
def create_test_image_link(self, prompt_id: int, test_image_path: str = None) -> Dict[str, Any]:
|
||||
"""Create a test image link to verify the system works."""
|
||||
print(f"\n🧪 Creating test image link for prompt {prompt_id}...")
|
||||
self.logger.info(f"\n🧪 Creating test image link for prompt {prompt_id}...")
|
||||
|
||||
try:
|
||||
# Import database operations
|
||||
@@ -289,7 +292,7 @@ class GalleryDiagnostics:
|
||||
metadata=test_metadata
|
||||
)
|
||||
|
||||
print(f" ✅ Test image linked with ID: {image_id}")
|
||||
self.logger.info(f" ✅ Test image linked with ID: {image_id}")
|
||||
|
||||
return {
|
||||
'status': 'ok',
|
||||
|
||||
+30
-27
@@ -13,6 +13,7 @@ from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
from .metadata_extractor import ComfyUIMetadataExtractor
|
||||
from .logging_config import get_logger
|
||||
|
||||
|
||||
class ImageGenerationHandler(FileSystemEventHandler):
|
||||
@@ -30,11 +31,12 @@ class ImageGenerationHandler(FileSystemEventHandler):
|
||||
self.prompt_tracker = prompt_tracker
|
||||
self.metadata_extractor = ComfyUIMetadataExtractor()
|
||||
self.processing_delay = 2.0 # Wait 2 seconds before processing
|
||||
self.logger = get_logger('prompt_manager.image_monitor')
|
||||
|
||||
def on_created(self, event):
|
||||
"""Handle file creation events."""
|
||||
if not event.is_directory and self.is_image_file(event.src_path):
|
||||
print(f"[PromptManager] New image detected: {event.src_path}")
|
||||
self.logger.info(f"New image detected: {event.src_path}")
|
||||
# Small delay to ensure file is fully written
|
||||
threading.Timer(
|
||||
self.processing_delay,
|
||||
@@ -49,24 +51,24 @@ class ImageGenerationHandler(FileSystemEventHandler):
|
||||
def process_new_image(self, image_path: str):
|
||||
"""Process a newly created image file."""
|
||||
try:
|
||||
print(f"[PromptManager] Processing image: {image_path}")
|
||||
self.logger.info(f"Processing image: {image_path}")
|
||||
|
||||
if not os.path.exists(image_path):
|
||||
print(f"[PromptManager] Image file no longer exists: {image_path}")
|
||||
self.logger.warning(f"Image file no longer exists: {image_path}")
|
||||
return
|
||||
|
||||
# Get current prompt context first
|
||||
current_prompt = self.prompt_tracker.get_current_prompt()
|
||||
print(f"[PromptManager] Current prompt context: {current_prompt['id'] if current_prompt else 'None'}")
|
||||
self.logger.debug(f"Current prompt context: {current_prompt['id'] if current_prompt else 'None'}")
|
||||
|
||||
if not current_prompt:
|
||||
print(f"[PromptManager] No active prompt context for image: {image_path}")
|
||||
self.logger.info(f"No active prompt context for image: {image_path}")
|
||||
# Fallback: try to link to the most recent prompt in database
|
||||
current_prompt = self._get_fallback_prompt()
|
||||
if current_prompt:
|
||||
print(f"[PromptManager] Using fallback prompt: {current_prompt['id']}")
|
||||
self.logger.info(f"Using fallback prompt: {current_prompt['id']}")
|
||||
else:
|
||||
print(f"[PromptManager] No fallback prompt available, skipping image")
|
||||
self.logger.warning(f"No fallback prompt available, skipping image")
|
||||
return
|
||||
else:
|
||||
# Extend the timeout for this prompt since we're still getting images
|
||||
@@ -76,24 +78,24 @@ class ImageGenerationHandler(FileSystemEventHandler):
|
||||
# Extract ComfyUI metadata
|
||||
try:
|
||||
metadata = self.metadata_extractor.extract_metadata(image_path)
|
||||
print(f"[PromptManager] Extracted metadata: {bool(metadata)}")
|
||||
self.logger.debug(f"Extracted metadata: {bool(metadata)}")
|
||||
except Exception as meta_error:
|
||||
print(f"[PromptManager] Metadata extraction failed: {meta_error}")
|
||||
self.logger.warning(f"Metadata extraction failed: {meta_error}")
|
||||
metadata = None
|
||||
|
||||
if metadata:
|
||||
print(f"[PromptManager] Linking image with full metadata to prompt {current_prompt['id']}")
|
||||
self.logger.info(f"Linking image with full metadata to prompt {current_prompt['id']}")
|
||||
self.link_image_to_prompt(image_path, current_prompt, metadata)
|
||||
else:
|
||||
print(f"[PromptManager] Linking image with basic info to prompt {current_prompt['id']}")
|
||||
self.logger.info(f"Linking image with basic info to prompt {current_prompt['id']}")
|
||||
# Link with basic file info even without metadata
|
||||
basic_metadata = self.get_basic_file_info(image_path)
|
||||
self.link_image_to_prompt(image_path, current_prompt, {'file_info': basic_metadata})
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error processing image {image_path}: {e}")
|
||||
self.logger.error(f"Error processing image {image_path}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.logger.error(traceback.format_exc())
|
||||
|
||||
def get_basic_file_info(self, image_path: str) -> Dict[str, Any]:
|
||||
"""Get basic file information when metadata extraction fails."""
|
||||
@@ -117,7 +119,7 @@ class ImageGenerationHandler(FileSystemEventHandler):
|
||||
|
||||
return file_info
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error getting file info: {e}")
|
||||
self.logger.error(f"Error getting file info: {e}")
|
||||
return {}
|
||||
|
||||
def _get_fallback_prompt(self) -> Optional[Dict[str, Any]]:
|
||||
@@ -133,7 +135,7 @@ class ImageGenerationHandler(FileSystemEventHandler):
|
||||
'fallback': True
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error getting fallback prompt: {e}")
|
||||
self.logger.error(f"Error getting fallback prompt: {e}")
|
||||
return None
|
||||
|
||||
def link_image_to_prompt(self, image_path: str, prompt_context: Dict, metadata: Dict):
|
||||
@@ -145,9 +147,9 @@ class ImageGenerationHandler(FileSystemEventHandler):
|
||||
metadata=metadata
|
||||
)
|
||||
fallback_note = " (fallback)" if prompt_context.get('fallback') else ""
|
||||
print(f"[PromptManager] Successfully linked image {image_id} to prompt {prompt_context['id']}{fallback_note}")
|
||||
self.logger.info(f"Successfully linked image {image_id} to prompt {prompt_context['id']}{fallback_note}")
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Failed to link image to prompt: {e}")
|
||||
self.logger.error(f"Failed to link image to prompt: {e}")
|
||||
|
||||
|
||||
class ImageMonitor:
|
||||
@@ -166,6 +168,7 @@ class ImageMonitor:
|
||||
self.observer = None
|
||||
self.handler = None
|
||||
self.monitored_directories = []
|
||||
self.logger = get_logger('prompt_manager.image_monitor')
|
||||
|
||||
def start_monitoring(self, output_directories: Optional[list] = None):
|
||||
"""
|
||||
@@ -175,7 +178,7 @@ class ImageMonitor:
|
||||
output_directories: List of directories to monitor. If None, auto-detect.
|
||||
"""
|
||||
if self.observer:
|
||||
print("[PromptManager] Image monitoring already running")
|
||||
self.logger.warning("Image monitoring already running")
|
||||
return
|
||||
|
||||
# Auto-detect ComfyUI output directory if none provided
|
||||
@@ -183,7 +186,7 @@ class ImageMonitor:
|
||||
output_directories = self.detect_comfyui_output_dirs()
|
||||
|
||||
if not output_directories:
|
||||
print("[PromptManager] No output directories found to monitor")
|
||||
self.logger.warning("No output directories found to monitor")
|
||||
return
|
||||
|
||||
# Create event handler
|
||||
@@ -196,15 +199,15 @@ class ImageMonitor:
|
||||
if os.path.exists(output_dir):
|
||||
self.observer.schedule(self.handler, output_dir, recursive=True)
|
||||
self.monitored_directories.append(output_dir)
|
||||
print(f"[PromptManager] Monitoring directory: {output_dir}")
|
||||
self.logger.info(f"Monitoring directory: {output_dir}")
|
||||
else:
|
||||
print(f"[PromptManager] Directory does not exist: {output_dir}")
|
||||
self.logger.warning(f"Directory does not exist: {output_dir}")
|
||||
|
||||
if self.monitored_directories:
|
||||
self.observer.start()
|
||||
print(f"[PromptManager] Image monitoring started for {len(self.monitored_directories)} directories")
|
||||
self.logger.info(f"Image monitoring started for {len(self.monitored_directories)} directories")
|
||||
else:
|
||||
print("[PromptManager] No valid directories to monitor")
|
||||
self.logger.warning("No valid directories to monitor")
|
||||
|
||||
def stop_monitoring(self):
|
||||
"""Stop the image monitoring system."""
|
||||
@@ -214,7 +217,7 @@ class ImageMonitor:
|
||||
self.observer = None
|
||||
self.handler = None
|
||||
self.monitored_directories = []
|
||||
print("[PromptManager] Image monitoring stopped")
|
||||
self.logger.info("Image monitoring stopped")
|
||||
|
||||
def detect_comfyui_output_dirs(self) -> list:
|
||||
"""Auto-detect ComfyUI output directories."""
|
||||
@@ -226,9 +229,9 @@ class ImageMonitor:
|
||||
output_dir = folder_paths.get_output_directory()
|
||||
if output_dir and os.path.exists(output_dir):
|
||||
potential_dirs.append(output_dir)
|
||||
print(f"[PromptManager] Detected ComfyUI output directory: {output_dir}")
|
||||
self.logger.info(f"Detected ComfyUI output directory: {output_dir}")
|
||||
except ImportError:
|
||||
print("[PromptManager] ComfyUI folder_paths not available, using fallback detection")
|
||||
self.logger.debug("ComfyUI folder_paths not available, using fallback detection")
|
||||
|
||||
# Fallback: Look for common ComfyUI directory structures
|
||||
fallback_paths = [
|
||||
@@ -243,7 +246,7 @@ class ImageMonitor:
|
||||
abs_path = os.path.abspath(path)
|
||||
if os.path.exists(abs_path) and abs_path not in potential_dirs:
|
||||
potential_dirs.append(abs_path)
|
||||
print(f"[PromptManager] Found output directory: {abs_path}")
|
||||
self.logger.debug(f"Found output directory: {abs_path}")
|
||||
|
||||
return potential_dirs
|
||||
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
"""
|
||||
Logging configuration for ComfyUI_PromptManager.
|
||||
|
||||
This module provides centralized logging configuration with support for:
|
||||
- Multiple log levels (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
- File rotation to prevent logs from growing too large
|
||||
- Console and file output
|
||||
- Structured logging with timestamps and context
|
||||
- Log viewer API integration
|
||||
"""
|
||||
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional, List
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class PromptManagerLogger:
|
||||
"""
|
||||
Centralized logging system for PromptManager.
|
||||
|
||||
Features:
|
||||
- Configurable log levels
|
||||
- File rotation with size limits
|
||||
- Structured JSON logging for API consumption
|
||||
- Thread-safe operations
|
||||
- Memory buffer for recent logs
|
||||
"""
|
||||
|
||||
_instance = None
|
||||
_lock = threading.Lock()
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
with cls._lock:
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
if hasattr(self, '_initialized'):
|
||||
return
|
||||
|
||||
self._initialized = True
|
||||
self.log_dir = Path(__file__).parent.parent / "logs"
|
||||
self.log_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Configuration
|
||||
self.config = {
|
||||
'level': 'INFO',
|
||||
'max_file_size': 10 * 1024 * 1024, # 10MB
|
||||
'backup_count': 5,
|
||||
'console_logging': True,
|
||||
'file_logging': True,
|
||||
'buffer_size': 1000 # Keep last 1000 log entries in memory
|
||||
}
|
||||
|
||||
# Memory buffer for recent logs (for web viewer)
|
||||
self._log_buffer = []
|
||||
self._buffer_lock = threading.Lock()
|
||||
|
||||
# Initialize loggers
|
||||
self._setup_loggers()
|
||||
|
||||
def _setup_loggers(self):
|
||||
"""Set up all loggers with appropriate handlers."""
|
||||
# Main logger
|
||||
self.logger = logging.getLogger('prompt_manager')
|
||||
self.logger.setLevel(getattr(logging, self.config['level']))
|
||||
|
||||
# Clear existing handlers
|
||||
for handler in self.logger.handlers[:]:
|
||||
self.logger.removeHandler(handler)
|
||||
|
||||
# Custom formatter
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Console handler
|
||||
if self.config['console_logging']:
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(formatter)
|
||||
self.logger.addHandler(console_handler)
|
||||
|
||||
# File handler with rotation
|
||||
if self.config['file_logging']:
|
||||
log_file = self.log_dir / "prompt_manager.log"
|
||||
file_handler = logging.handlers.RotatingFileHandler(
|
||||
log_file,
|
||||
maxBytes=self.config['max_file_size'],
|
||||
backupCount=self.config['backup_count']
|
||||
)
|
||||
file_handler.setFormatter(formatter)
|
||||
self.logger.addHandler(file_handler)
|
||||
|
||||
# Memory handler for web viewer
|
||||
memory_handler = MemoryBufferHandler(self)
|
||||
memory_handler.setFormatter(formatter)
|
||||
self.logger.addHandler(memory_handler)
|
||||
|
||||
# Component-specific loggers
|
||||
self._setup_component_loggers()
|
||||
|
||||
def _setup_component_loggers(self):
|
||||
"""Set up loggers for specific components."""
|
||||
components = [
|
||||
'prompt_manager.database',
|
||||
'prompt_manager.api',
|
||||
'prompt_manager.image_monitor',
|
||||
'prompt_manager.prompt_tracker',
|
||||
'prompt_manager.web_ui'
|
||||
]
|
||||
|
||||
for component in components:
|
||||
comp_logger = logging.getLogger(component)
|
||||
comp_logger.setLevel(getattr(logging, self.config['level']))
|
||||
# Child loggers inherit handlers from parent
|
||||
|
||||
def get_logger(self, name: str = 'prompt_manager') -> logging.Logger:
|
||||
"""Get a logger instance for a specific component."""
|
||||
return logging.getLogger(name)
|
||||
|
||||
def add_to_buffer(self, record: logging.LogRecord, formatted_message: str):
|
||||
"""Add a log entry to the memory buffer for web viewer."""
|
||||
with self._buffer_lock:
|
||||
log_entry = {
|
||||
'timestamp': datetime.fromtimestamp(record.created).isoformat(),
|
||||
'level': record.levelname,
|
||||
'logger': record.name,
|
||||
'message': record.getMessage(),
|
||||
'formatted': formatted_message,
|
||||
'module': record.module,
|
||||
'filename': record.filename,
|
||||
'lineno': record.lineno,
|
||||
'thread': record.thread,
|
||||
'thread_name': record.threadName if hasattr(record, 'threadName') else '',
|
||||
'process': record.process
|
||||
}
|
||||
|
||||
self._log_buffer.append(log_entry)
|
||||
|
||||
# Maintain buffer size limit
|
||||
if len(self._log_buffer) > self.config['buffer_size']:
|
||||
self._log_buffer = self._log_buffer[-self.config['buffer_size']:]
|
||||
|
||||
def get_recent_logs(self, limit: int = 100, level: Optional[str] = None) -> List[Dict[str, Any]]:
|
||||
"""Get recent log entries from memory buffer."""
|
||||
with self._buffer_lock:
|
||||
logs = self._log_buffer[:]
|
||||
|
||||
# Filter by level if specified
|
||||
if level:
|
||||
level_num = getattr(logging, level.upper(), None)
|
||||
if level_num:
|
||||
logs = [log for log in logs if getattr(logging, log['level']) >= level_num]
|
||||
|
||||
# Return most recent first
|
||||
return list(reversed(logs[-limit:]))
|
||||
|
||||
def get_log_files(self) -> List[Dict[str, Any]]:
|
||||
"""Get information about available log files."""
|
||||
log_files = []
|
||||
|
||||
for log_file in self.log_dir.glob("*.log*"):
|
||||
try:
|
||||
stat = log_file.stat()
|
||||
log_files.append({
|
||||
'filename': log_file.name,
|
||||
'path': str(log_file),
|
||||
'size': stat.st_size,
|
||||
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
|
||||
'is_main': log_file.name == 'prompt_manager.log'
|
||||
})
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting info for log file {log_file}: {e}")
|
||||
|
||||
# Sort by modification time, newest first
|
||||
log_files.sort(key=lambda x: x['modified'], reverse=True)
|
||||
return log_files
|
||||
|
||||
def read_log_file(self, filename: str, lines: int = 100) -> List[str]:
|
||||
"""Read lines from a specific log file."""
|
||||
log_file = self.log_dir / filename
|
||||
|
||||
if not log_file.exists() or not log_file.is_file():
|
||||
raise FileNotFoundError(f"Log file not found: {filename}")
|
||||
|
||||
# Security check - ensure file is in log directory
|
||||
if not str(log_file.resolve()).startswith(str(self.log_dir.resolve())):
|
||||
raise ValueError("Invalid log file path")
|
||||
|
||||
try:
|
||||
with open(log_file, 'r', encoding='utf-8') as f:
|
||||
all_lines = f.readlines()
|
||||
return all_lines[-lines:] if lines > 0 else all_lines
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error reading log file {filename}: {e}")
|
||||
raise
|
||||
|
||||
def truncate_logs(self) -> Dict[str, Any]:
|
||||
"""Truncate all log files."""
|
||||
results = {
|
||||
'truncated': [],
|
||||
'errors': []
|
||||
}
|
||||
|
||||
for log_file in self.log_dir.glob("prompt_manager.log*"):
|
||||
try:
|
||||
if log_file.name == 'prompt_manager.log':
|
||||
# For main log file, just clear it
|
||||
with open(log_file, 'w') as f:
|
||||
f.write("")
|
||||
else:
|
||||
# For rotated files, delete them
|
||||
log_file.unlink()
|
||||
|
||||
results['truncated'].append(log_file.name)
|
||||
self.logger.info(f"Truncated log file: {log_file.name}")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to truncate {log_file.name}: {str(e)}"
|
||||
results['errors'].append(error_msg)
|
||||
self.logger.error(error_msg)
|
||||
|
||||
# Clear memory buffer
|
||||
with self._buffer_lock:
|
||||
self._log_buffer.clear()
|
||||
|
||||
return results
|
||||
|
||||
def update_config(self, new_config: Dict[str, Any]):
|
||||
"""Update logging configuration."""
|
||||
self.config.update(new_config)
|
||||
|
||||
# Reconfigure loggers if level changed
|
||||
if 'level' in new_config:
|
||||
level = getattr(logging, new_config['level'].upper(), logging.INFO)
|
||||
self.logger.setLevel(level)
|
||||
|
||||
# Update all component loggers
|
||||
for logger_name in logging.Logger.manager.loggerDict:
|
||||
if logger_name.startswith('prompt_manager'):
|
||||
logger = logging.getLogger(logger_name)
|
||||
logger.setLevel(level)
|
||||
|
||||
# Re-setup loggers if handlers changed
|
||||
if any(key in new_config for key in ['console_logging', 'file_logging', 'max_file_size', 'backup_count']):
|
||||
self._setup_loggers()
|
||||
|
||||
self.logger.info(f"Updated logging configuration: {new_config}")
|
||||
|
||||
def get_config(self) -> Dict[str, Any]:
|
||||
"""Get current logging configuration."""
|
||||
return self.config.copy()
|
||||
|
||||
def get_log_stats(self) -> Dict[str, Any]:
|
||||
"""Get logging statistics."""
|
||||
with self._buffer_lock:
|
||||
buffer_count = len(self._log_buffer)
|
||||
level_counts = {}
|
||||
for entry in self._log_buffer:
|
||||
level = entry['level']
|
||||
level_counts[level] = level_counts.get(level, 0) + 1
|
||||
|
||||
log_files = self.get_log_files()
|
||||
total_log_size = sum(f['size'] for f in log_files)
|
||||
|
||||
return {
|
||||
'buffer_count': buffer_count,
|
||||
'level_counts': level_counts,
|
||||
'log_files_count': len(log_files),
|
||||
'total_log_size': total_log_size,
|
||||
'log_directory': str(self.log_dir),
|
||||
'current_level': self.config['level']
|
||||
}
|
||||
|
||||
|
||||
class MemoryBufferHandler(logging.Handler):
|
||||
"""Custom logging handler that stores logs in memory for web viewer."""
|
||||
|
||||
def __init__(self, logger_manager: PromptManagerLogger):
|
||||
super().__init__()
|
||||
self.logger_manager = logger_manager
|
||||
|
||||
def emit(self, record: logging.LogRecord):
|
||||
"""Handle a log record by adding it to the memory buffer."""
|
||||
try:
|
||||
formatted_message = self.format(record)
|
||||
self.logger_manager.add_to_buffer(record, formatted_message)
|
||||
except Exception:
|
||||
# Don't let logging errors break the application
|
||||
pass
|
||||
|
||||
|
||||
# Global logger instance
|
||||
_logger_manager = None
|
||||
|
||||
def get_logger_manager() -> PromptManagerLogger:
|
||||
"""Get the global logger manager instance."""
|
||||
global _logger_manager
|
||||
if _logger_manager is None:
|
||||
_logger_manager = PromptManagerLogger()
|
||||
return _logger_manager
|
||||
|
||||
def get_logger(name: str = 'prompt_manager') -> logging.Logger:
|
||||
"""Convenience function to get a logger."""
|
||||
return get_logger_manager().get_logger(name)
|
||||
|
||||
# Initialize logging on import
|
||||
get_logger_manager()
|
||||
@@ -9,13 +9,15 @@ from typing import Optional, Dict, Any
|
||||
from PIL import Image
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
|
||||
from .logging_config import get_logger
|
||||
|
||||
|
||||
class ComfyUIMetadataExtractor:
|
||||
"""Extracts ComfyUI metadata from generated images."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the metadata extractor."""
|
||||
pass
|
||||
self.logger = get_logger('prompt_manager.metadata_extractor')
|
||||
|
||||
def extract_metadata(self, image_path: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -48,7 +50,7 @@ class ComfyUIMetadataExtractor:
|
||||
metadata['text_encoder_nodes'] = text_encoder_nodes
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[PromptManager] Failed to parse workflow JSON: {e}")
|
||||
self.logger.warning(f"Failed to parse workflow JSON: {e}")
|
||||
|
||||
# Look for prompt data
|
||||
if 'prompt' in image.text:
|
||||
@@ -56,7 +58,7 @@ class ComfyUIMetadataExtractor:
|
||||
prompt_data = json.loads(image.text['prompt'])
|
||||
metadata['prompt'] = prompt_data
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[PromptManager] Failed to parse prompt JSON: {e}")
|
||||
self.logger.warning(f"Failed to parse prompt JSON: {e}")
|
||||
|
||||
# Extract other common metadata fields
|
||||
metadata_fields = [
|
||||
@@ -76,7 +78,7 @@ class ComfyUIMetadataExtractor:
|
||||
return metadata if any(key != 'file_info' for key in metadata.keys()) else None
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error extracting metadata from {image_path}: {e}")
|
||||
self.logger.error(f"Error extracting metadata from {image_path}: {e}")
|
||||
return None
|
||||
|
||||
def get_file_info(self, image_path: str, image: Image.Image) -> Dict[str, Any]:
|
||||
@@ -101,7 +103,7 @@ class ComfyUIMetadataExtractor:
|
||||
'modified_time': stat.st_mtime
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error getting file info: {e}")
|
||||
self.logger.error(f"Error getting file info: {e}")
|
||||
return {}
|
||||
|
||||
def find_text_encoder_nodes(self, workflow_data: Dict) -> list:
|
||||
|
||||
+52
-21
@@ -10,6 +10,16 @@ import hashlib
|
||||
from typing import Optional, Dict, Any
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# Import logging system
|
||||
try:
|
||||
from .logging_config import get_logger
|
||||
except ImportError:
|
||||
import sys
|
||||
import os
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, current_dir)
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
|
||||
class PromptTracker:
|
||||
"""Thread-safe tracking of current prompt executions."""
|
||||
@@ -21,6 +31,9 @@ class PromptTracker:
|
||||
Args:
|
||||
db_manager: Database manager instance
|
||||
"""
|
||||
self.logger = get_logger('prompt_manager.prompt_tracker')
|
||||
self.logger.info("Initializing PromptTracker")
|
||||
|
||||
self.db_manager = db_manager
|
||||
self._local = threading.local()
|
||||
self.active_prompts = {} # Global tracking for multiple threads
|
||||
@@ -31,6 +44,7 @@ class PromptTracker:
|
||||
# Start cleanup thread
|
||||
self.cleanup_thread = threading.Thread(target=self._cleanup_expired_prompts, daemon=True)
|
||||
self.cleanup_thread.start()
|
||||
self.logger.debug("PromptTracker initialization completed")
|
||||
|
||||
def set_current_prompt(self, prompt_text: str, additional_data: Optional[Dict[str, Any]] = None) -> str:
|
||||
"""
|
||||
@@ -61,13 +75,13 @@ class PromptTracker:
|
||||
|
||||
if existing_prompt:
|
||||
prompt_id = existing_prompt['id']
|
||||
print(f"[PromptManager] Found existing prompt ID: {prompt_id}")
|
||||
self.logger.debug(f"Found existing prompt ID: {prompt_id}")
|
||||
else:
|
||||
# Generate a temporary ID for tracking (prompt should be saved by PromptManager)
|
||||
prompt_id = f"temp_{int(time.time())}"
|
||||
print(f"[PromptManager] Using temporary ID for tracking: {prompt_id}")
|
||||
self.logger.debug(f"Using temporary ID for tracking: {prompt_id}")
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error finding prompt: {e}")
|
||||
self.logger.error(f"Error finding prompt: {e}")
|
||||
prompt_id = f"temp_{int(time.time())}"
|
||||
|
||||
# Create execution context
|
||||
@@ -87,8 +101,8 @@ class PromptTracker:
|
||||
with self.lock:
|
||||
self.active_prompts[execution_id] = execution_context
|
||||
|
||||
print(f"[PromptManager] Set current prompt: {execution_id} -> {prompt_text[:50]}... (thread: {threading.current_thread().ident})")
|
||||
print(f"[PromptManager] Active prompts count: {len(self.active_prompts)}")
|
||||
self.logger.debug(f"Set current prompt: {execution_id} -> {prompt_text[:50]}... (thread: {threading.current_thread().ident})")
|
||||
self.logger.debug(f"Active prompts count: {len(self.active_prompts)}")
|
||||
return execution_id
|
||||
|
||||
def get_current_prompt(self) -> Optional[Dict[str, Any]]:
|
||||
@@ -105,42 +119,42 @@ class PromptTracker:
|
||||
if time.time() - current['timestamp'] < self.prompt_timeout:
|
||||
return current
|
||||
else:
|
||||
print(f"[PromptManager] Current prompt expired: {current['execution_id']}")
|
||||
self.logger.debug(f"Current prompt expired: {current['execution_id']}")
|
||||
self.clear_current_prompt()
|
||||
|
||||
# Fallback: try to find recent prompt from global tracking
|
||||
# This is crucial for image monitoring which runs in different threads
|
||||
recent_prompt = self._find_recent_prompt()
|
||||
if recent_prompt:
|
||||
print(f"[PromptManager] Using recent prompt from global tracking: {recent_prompt['execution_id']}")
|
||||
self.logger.debug(f"Using recent prompt from global tracking: {recent_prompt['execution_id']}")
|
||||
return recent_prompt
|
||||
|
||||
print(f"[PromptManager] No prompt context found (thread: {threading.current_thread().ident})")
|
||||
self.logger.debug(f"No prompt context found (thread: {threading.current_thread().ident})")
|
||||
return None
|
||||
|
||||
def _find_recent_prompt(self) -> Optional[Dict[str, Any]]:
|
||||
"""Find the most recent prompt that's still valid."""
|
||||
with self.lock:
|
||||
current_time = time.time()
|
||||
print(f"[PromptManager] Searching for recent prompt among {len(self.active_prompts)} active prompts")
|
||||
self.logger.debug(f"Searching for recent prompt among {len(self.active_prompts)} active prompts")
|
||||
|
||||
recent_prompts = []
|
||||
for exec_id, prompt in self.active_prompts.items():
|
||||
age_seconds = current_time - prompt['timestamp']
|
||||
print(f"[PromptManager] Prompt {exec_id}: age={age_seconds:.1f}s, timeout={self.prompt_timeout}s")
|
||||
self.logger.debug(f"Prompt {exec_id}: age={age_seconds:.1f}s, timeout={self.prompt_timeout}s")
|
||||
|
||||
if age_seconds < self.prompt_timeout:
|
||||
recent_prompts.append(prompt)
|
||||
else:
|
||||
print(f"[PromptManager] Prompt {exec_id} is expired")
|
||||
self.logger.debug(f"Prompt {exec_id} is expired")
|
||||
|
||||
if recent_prompts:
|
||||
# Return the most recent one
|
||||
most_recent = max(recent_prompts, key=lambda p: p['timestamp'])
|
||||
print(f"[PromptManager] Found recent prompt: {most_recent['execution_id']}")
|
||||
self.logger.debug(f"Found recent prompt: {most_recent['execution_id']}")
|
||||
return most_recent
|
||||
else:
|
||||
print(f"[PromptManager] No recent prompts found")
|
||||
self.logger.debug(f"No recent prompts found")
|
||||
|
||||
return None
|
||||
|
||||
@@ -149,7 +163,7 @@ class PromptTracker:
|
||||
current = getattr(self._local, 'current_prompt', None)
|
||||
if current:
|
||||
execution_id = current['execution_id']
|
||||
print(f"[PromptManager] Clearing current prompt: {execution_id} (thread: {threading.current_thread().ident})")
|
||||
self.logger.debug(f"Clearing current prompt: {execution_id} (thread: {threading.current_thread().ident})")
|
||||
|
||||
# Clear from thread-local storage
|
||||
self._local.current_prompt = None
|
||||
@@ -158,12 +172,12 @@ class PromptTracker:
|
||||
with self.lock:
|
||||
removed = self.active_prompts.pop(execution_id, None)
|
||||
if removed:
|
||||
print(f"[PromptManager] Removed prompt from global tracking: {execution_id}")
|
||||
print(f"[PromptManager] Remaining active prompts: {len(self.active_prompts)}")
|
||||
self.logger.debug(f"Removed prompt from global tracking: {execution_id}")
|
||||
self.logger.debug(f"Remaining active prompts: {len(self.active_prompts)}")
|
||||
else:
|
||||
print(f"[PromptManager] Prompt {execution_id} was not in global tracking")
|
||||
self.logger.debug(f"Prompt {execution_id} was not in global tracking")
|
||||
else:
|
||||
print(f"[PromptManager] No current prompt to clear (thread: {threading.current_thread().ident})")
|
||||
self.logger.debug(f"No current prompt to clear (thread: {threading.current_thread().ident})")
|
||||
|
||||
def extend_prompt_timeout(self, execution_id: str, additional_seconds: int = 60):
|
||||
"""
|
||||
@@ -176,7 +190,7 @@ class PromptTracker:
|
||||
with self.lock:
|
||||
if execution_id in self.active_prompts:
|
||||
self.active_prompts[execution_id]['timestamp'] = time.time()
|
||||
print(f"[PromptManager] Extended timeout for prompt: {execution_id}")
|
||||
self.logger.debug(f"Extended timeout for prompt: {execution_id}")
|
||||
|
||||
def generate_execution_id(self) -> str:
|
||||
"""Generate a unique execution ID."""
|
||||
@@ -198,12 +212,12 @@ class PromptTracker:
|
||||
self.active_prompts.pop(exec_id, None)
|
||||
|
||||
if expired_ids:
|
||||
print(f"[PromptManager] Cleaned up {len(expired_ids)} expired prompts")
|
||||
self.logger.info(f"Cleaned up {len(expired_ids)} expired prompts")
|
||||
|
||||
time.sleep(self.cleanup_interval)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PromptManager] Error in cleanup thread: {e}")
|
||||
self.logger.error(f"Error in cleanup thread: {e}")
|
||||
time.sleep(60) # Wait a minute before retrying
|
||||
|
||||
def get_active_prompts(self) -> Dict[str, Dict[str, Any]]:
|
||||
@@ -216,6 +230,23 @@ class PromptTracker:
|
||||
with self.lock:
|
||||
return self.active_prompts.copy()
|
||||
|
||||
def clear_all_active_prompts(self) -> int:
|
||||
"""
|
||||
Clear all active prompts (useful before scanning to avoid conflicts).
|
||||
|
||||
Returns:
|
||||
Number of prompts that were cleared
|
||||
"""
|
||||
with self.lock:
|
||||
cleared_count = len(self.active_prompts)
|
||||
self.active_prompts.clear()
|
||||
|
||||
# Clear thread-local storage as well
|
||||
self._local.current_prompt = None
|
||||
|
||||
self.logger.info(f"Cleared {cleared_count} active prompts")
|
||||
return cleared_count
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get tracker status information.
|
||||
|
||||
+446
-13
@@ -192,6 +192,10 @@
|
||||
class="px-4 py-2 bg-pink-600 hover:bg-pink-700 text-white text-sm font-medium rounded-lg transition-colors">
|
||||
📸 Scan Images
|
||||
</button>
|
||||
<button id="logsBtn"
|
||||
class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium rounded-lg transition-colors">
|
||||
📋 View Logs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -602,6 +606,131 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs Modal -->
|
||||
<div id="logsModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
|
||||
<div class="bg-gray-800 rounded-xl max-w-6xl w-full max-h-[90vh] overflow-hidden mx-4 border border-gray-700">
|
||||
<div class="p-6 border-b border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-xl font-semibold text-gray-100">📋 System Logs</h3>
|
||||
<button onclick="window.admin.closeLogs()" class="p-2 rounded-lg hover:bg-gray-700 transition-colors">
|
||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Controls -->
|
||||
<div class="p-6 border-b border-gray-700">
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-4 mb-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">Log Level</label>
|
||||
<select id="logLevel" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
|
||||
<option value="">All Levels</option>
|
||||
<option value="DEBUG">Debug</option>
|
||||
<option value="INFO">Info</option>
|
||||
<option value="WARNING">Warning</option>
|
||||
<option value="ERROR">Error</option>
|
||||
<option value="CRITICAL">Critical</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">Limit</label>
|
||||
<select id="logLimit" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
|
||||
<option value="50">Last 50</option>
|
||||
<option value="100" selected>Last 100</option>
|
||||
<option value="200">Last 200</option>
|
||||
<option value="500">Last 500</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button id="refreshLogsBtn" class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
|
||||
🔄 Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button id="downloadLogsBtn" class="w-full px-4 py-2 bg-green-600 hover:bg-green-700 text-white font-medium rounded-lg transition-colors">
|
||||
📥 Download
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button id="clearLogsBtn" class="w-full px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors">
|
||||
🗑️ Clear Logs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Stats -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-4">
|
||||
<div class="bg-gray-700 rounded-lg p-3">
|
||||
<div class="text-xs text-gray-400">Total Logs</div>
|
||||
<div class="text-lg font-semibold text-blue-400" id="logStatsTotal">-</div>
|
||||
</div>
|
||||
<div class="bg-gray-700 rounded-lg p-3">
|
||||
<div class="text-xs text-gray-400">Errors</div>
|
||||
<div class="text-lg font-semibold text-red-400" id="logStatsErrors">-</div>
|
||||
</div>
|
||||
<div class="bg-gray-700 rounded-lg p-3">
|
||||
<div class="text-xs text-gray-400">Warnings</div>
|
||||
<div class="text-lg font-semibold text-yellow-400" id="logStatsWarnings">-</div>
|
||||
</div>
|
||||
<div class="bg-gray-700 rounded-lg p-3">
|
||||
<div class="text-xs text-gray-400">Current Level</div>
|
||||
<div class="text-lg font-semibold text-green-400" id="logStatsLevel">-</div>
|
||||
</div>
|
||||
<div class="bg-gray-700 rounded-lg p-3">
|
||||
<div class="text-xs text-gray-400">Log Size</div>
|
||||
<div class="text-lg font-semibold text-purple-400" id="logStatsSize">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Settings -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">Set Log Level</label>
|
||||
<select id="setLogLevel" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
|
||||
<option value="DEBUG">Debug</option>
|
||||
<option value="INFO" selected>Info</option>
|
||||
<option value="WARNING">Warning</option>
|
||||
<option value="ERROR">Error</option>
|
||||
<option value="CRITICAL">Critical</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<label class="flex items-center space-x-2 text-gray-300">
|
||||
<input type="checkbox" id="consoleLogging" checked class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500">
|
||||
<span class="text-sm">Console Logging</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button id="updateLogConfigBtn" class="w-full px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors">
|
||||
💾 Update Config
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Viewer -->
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<div class="h-96 overflow-y-auto custom-scrollbar bg-gray-900 font-mono text-sm">
|
||||
<div id="logsContainer" class="p-4 space-y-1">
|
||||
<div class="text-center py-8 text-gray-500">
|
||||
Click "Refresh" to load logs
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Files -->
|
||||
<div class="p-6 border-t border-gray-700">
|
||||
<h4 class="text-lg font-medium text-gray-100 mb-4">Log Files</h4>
|
||||
<div id="logFilesList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
<!-- Log files will be inserted here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
class PromptAdmin {
|
||||
constructor() {
|
||||
@@ -646,6 +775,7 @@
|
||||
document.getElementById("backupBtn").addEventListener("click", () => this.backupDatabase());
|
||||
document.getElementById("restoreBtn").addEventListener("click", () => this.showRestoreModal());
|
||||
document.getElementById("scanBtn").addEventListener("click", () => this.showScanModal());
|
||||
document.getElementById("logsBtn").addEventListener("click", () => this.showLogsModal());
|
||||
|
||||
// Modals
|
||||
this.bindModalEvents();
|
||||
@@ -687,6 +817,12 @@
|
||||
document.getElementById("cancelScan").addEventListener("click", () => this.hideModal("scanModal"));
|
||||
document.getElementById("quickBackupBtn").addEventListener("click", () => this.quickBackup());
|
||||
|
||||
// Logs modal
|
||||
document.getElementById("refreshLogsBtn").addEventListener("click", () => this.refreshLogs());
|
||||
document.getElementById("downloadLogsBtn").addEventListener("click", () => this.downloadLogs());
|
||||
document.getElementById("clearLogsBtn").addEventListener("click", () => this.clearLogs());
|
||||
document.getElementById("updateLogConfigBtn").addEventListener("click", () => this.updateLogConfig());
|
||||
|
||||
// Close modals on backdrop click
|
||||
document.querySelectorAll("[id$='Modal']").forEach((modal) => {
|
||||
modal.addEventListener("click", (e) => {
|
||||
@@ -806,8 +942,7 @@
|
||||
if (data.success) {
|
||||
this.prompts = data.results;
|
||||
this.renderPrompts();
|
||||
// Update stats based on loaded data
|
||||
this.updateLocalStats();
|
||||
// Don't update stats from local data as it's only a subset of prompts
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -819,8 +954,12 @@
|
||||
}
|
||||
|
||||
updateLocalStats() {
|
||||
// Fallback stats calculation from loaded data
|
||||
if (this.prompts.length > 0) {
|
||||
// Only update local stats if we haven't loaded proper statistics yet
|
||||
// This prevents overwriting the correct total counts from the API
|
||||
const currentTotal = document.getElementById("totalPrompts").textContent;
|
||||
|
||||
// Only update if the current total is still the default "-" or if we have more prompts locally than the API count
|
||||
if (currentTotal === "-" || (this.prompts.length > parseInt(currentTotal) && this.prompts.length > 0)) {
|
||||
document.getElementById("totalPrompts").textContent = this.prompts.length;
|
||||
|
||||
const uniqueCategories = new Set(this.prompts.map(p => p.category).filter(Boolean));
|
||||
@@ -1092,8 +1231,10 @@
|
||||
this.prompts[promptIndex].rating = rating;
|
||||
}
|
||||
|
||||
// Update stats
|
||||
this.updateLocalStats();
|
||||
// Refresh stats
|
||||
setTimeout(() => {
|
||||
this.loadStatistics();
|
||||
}, 500);
|
||||
} else {
|
||||
// Revert UI change on API failure
|
||||
const originalPrompt = this.prompts.find(p => p.id === promptId);
|
||||
@@ -1175,6 +1316,11 @@
|
||||
|
||||
if (response.ok) {
|
||||
this.showNotification("Prompt updated", "success");
|
||||
// Refresh the page to show updated content
|
||||
setTimeout(() => {
|
||||
this.loadStatistics();
|
||||
setTimeout(() => window.location.reload(), 500);
|
||||
}, 1000);
|
||||
} else {
|
||||
throw new Error("Update failed");
|
||||
}
|
||||
@@ -1211,7 +1357,11 @@
|
||||
|
||||
if (response.ok) {
|
||||
this.showNotification("Prompt deleted", "success");
|
||||
this.search();
|
||||
// Refresh stats and reload page
|
||||
setTimeout(() => {
|
||||
this.loadStatistics();
|
||||
setTimeout(() => window.location.reload(), 500);
|
||||
}, 1000);
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification("Failed to delete prompt", "error");
|
||||
@@ -1290,7 +1440,11 @@
|
||||
|
||||
if (response.ok) {
|
||||
this.showNotification(`${this.selectedPrompts.size} prompts deleted`, "success");
|
||||
this.search();
|
||||
// Refresh stats and reload page
|
||||
setTimeout(() => {
|
||||
this.loadStatistics();
|
||||
setTimeout(() => window.location.reload(), 500);
|
||||
}, 1000);
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification("Failed to delete prompts", "error");
|
||||
@@ -2136,7 +2290,7 @@
|
||||
data.found
|
||||
);
|
||||
} else if (data.type === 'complete') {
|
||||
this.completeScan(data.processed, data.found, data.added);
|
||||
this.completeScan(data.processed, data.found, data.added, data.linked);
|
||||
return;
|
||||
} else if (data.type === 'error') {
|
||||
throw new Error(data.message);
|
||||
@@ -2161,18 +2315,297 @@
|
||||
document.getElementById("scanFound").textContent = `${found} prompts found`;
|
||||
}
|
||||
|
||||
completeScan(processed, found, added) {
|
||||
completeScan(processed, found, added, linked = 0) {
|
||||
this.updateScanProgress(100, "Scan completed!", processed, found);
|
||||
document.getElementById("startScan").disabled = false;
|
||||
document.getElementById("startScan").textContent = "Start New Scan";
|
||||
|
||||
// Show detailed notification with all counts
|
||||
const linkedText = linked > 0 ? `, linked ${linked} images to existing prompts` : '';
|
||||
this.showNotification(
|
||||
`Scan completed! Processed ${processed} files, found ${found} prompts, added ${added} new prompts to database.`,
|
||||
`Scan completed! Processed ${processed} files, found ${found} prompts, added ${added} new prompts to database${linkedText}.`,
|
||||
"success"
|
||||
);
|
||||
|
||||
// Refresh the data
|
||||
this.loadInitialData();
|
||||
// Auto-close modal after a short delay to let user see the completion message
|
||||
setTimeout(() => {
|
||||
this.hideModal("scanModal");
|
||||
|
||||
// Refresh the statistics immediately
|
||||
this.loadStatistics();
|
||||
|
||||
// Also reload the page after a short delay to ensure everything is fully updated
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Logs functionality
|
||||
showLogsModal() {
|
||||
this.showModal("logsModal");
|
||||
this.loadLogStats();
|
||||
this.loadLogFiles();
|
||||
this.loadLogConfig();
|
||||
}
|
||||
|
||||
closeLogs() {
|
||||
this.hideModal("logsModal");
|
||||
}
|
||||
|
||||
async loadLogStats() {
|
||||
try {
|
||||
const response = await fetch('/prompt_manager/logs/stats');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const stats = data.stats;
|
||||
document.getElementById("logStatsTotal").textContent = stats.buffer_count || 0;
|
||||
document.getElementById("logStatsErrors").textContent = stats.level_counts?.ERROR || 0;
|
||||
document.getElementById("logStatsWarnings").textContent = stats.level_counts?.WARNING || 0;
|
||||
document.getElementById("logStatsLevel").textContent = stats.current_level || "INFO";
|
||||
document.getElementById("logStatsSize").textContent = this.formatBytes(stats.total_log_size || 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load log stats:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async loadLogConfig() {
|
||||
try {
|
||||
const response = await fetch('/prompt_manager/logs/config');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const config = data.config;
|
||||
document.getElementById("setLogLevel").value = config.level || "INFO";
|
||||
document.getElementById("consoleLogging").checked = config.console_logging !== false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load log config:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async loadLogFiles() {
|
||||
try {
|
||||
const response = await fetch('/prompt_manager/logs/files');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const container = document.getElementById("logFilesList");
|
||||
|
||||
if (data.files.length === 0) {
|
||||
container.innerHTML = '<div class="col-span-full text-center text-gray-500">No log files found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = data.files.map(file => `
|
||||
<div class="bg-gray-700 rounded-lg p-3">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-sm font-medium text-gray-200">${file.filename}</span>
|
||||
${file.is_main ? '<span class="bg-blue-500 text-xs px-2 py-1 rounded">Active</span>' : ''}
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 mb-2">
|
||||
Size: ${this.formatBytes(file.size)} | Modified: ${new Date(file.modified).toLocaleString()}
|
||||
</div>
|
||||
<button onclick="window.admin.downloadLogFile('${file.filename}')"
|
||||
class="w-full px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-xs rounded transition-colors">
|
||||
📥 Download
|
||||
</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load log files:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async refreshLogs() {
|
||||
const level = document.getElementById("logLevel").value;
|
||||
const limit = parseInt(document.getElementById("logLimit").value);
|
||||
|
||||
try {
|
||||
const url = new URL('/prompt_manager/logs', window.location.origin);
|
||||
if (level) url.searchParams.set('level', level);
|
||||
url.searchParams.set('limit', limit.toString());
|
||||
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.displayLogs(data.logs);
|
||||
this.showNotification(`Loaded ${data.logs.length} log entries`, "success");
|
||||
} else {
|
||||
this.showNotification(`Failed to load logs: ${data.error}`, "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to refresh logs:", error);
|
||||
this.showNotification("Failed to refresh logs", "error");
|
||||
}
|
||||
}
|
||||
|
||||
displayLogs(logs) {
|
||||
const container = document.getElementById("logsContainer");
|
||||
|
||||
if (logs.length === 0) {
|
||||
container.innerHTML = '<div class="text-center py-8 text-gray-500">No logs found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = logs.map(log => {
|
||||
const levelColors = {
|
||||
DEBUG: 'text-gray-400',
|
||||
INFO: 'text-blue-400',
|
||||
WARNING: 'text-yellow-400',
|
||||
ERROR: 'text-red-400',
|
||||
CRITICAL: 'text-red-600'
|
||||
};
|
||||
|
||||
const levelColor = levelColors[log.level] || 'text-gray-400';
|
||||
const timestamp = new Date(log.timestamp).toLocaleString();
|
||||
|
||||
return `
|
||||
<div class="border-l-2 border-gray-600 pl-3 py-1 hover:bg-gray-800 transition-colors">
|
||||
<div class="flex items-start space-x-2 text-sm">
|
||||
<span class="text-gray-500 text-xs font-mono w-24 flex-shrink-0">${timestamp.split(' ')[1]}</span>
|
||||
<span class="${levelColor} font-semibold w-16 flex-shrink-0">${log.level}</span>
|
||||
<span class="text-purple-400 text-xs w-32 flex-shrink-0">${log.logger}</span>
|
||||
<span class="text-gray-300 flex-1">${log.message}</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 ml-44">
|
||||
${log.filename}:${log.lineno}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// Auto-scroll to bottom
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
async downloadLogs() {
|
||||
const level = document.getElementById("logLevel").value;
|
||||
const limit = parseInt(document.getElementById("logLimit").value);
|
||||
|
||||
try {
|
||||
const url = new URL('/prompt_manager/logs', window.location.origin);
|
||||
if (level) url.searchParams.set('level', level);
|
||||
url.searchParams.set('limit', limit.toString());
|
||||
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const logText = data.logs.map(log =>
|
||||
`${log.timestamp} [${log.level}] ${log.logger}: ${log.message} (${log.filename}:${log.lineno})`
|
||||
).join('\n');
|
||||
|
||||
const blob = new Blob([logText], { type: 'text/plain' });
|
||||
const url2 = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url2;
|
||||
a.download = `prompt_manager_logs_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.txt`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url2);
|
||||
|
||||
this.showNotification("Logs downloaded successfully", "success");
|
||||
} else {
|
||||
this.showNotification(`Failed to download logs: ${data.error}`, "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to download logs:", error);
|
||||
this.showNotification("Failed to download logs", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async downloadLogFile(filename) {
|
||||
try {
|
||||
const response = await fetch(`/prompt_manager/logs/download/${encodeURIComponent(filename)}`);
|
||||
|
||||
if (response.ok) {
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
this.showNotification(`Downloaded ${filename}`, "success");
|
||||
} else {
|
||||
this.showNotification(`Failed to download ${filename}`, "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to download log file:", error);
|
||||
this.showNotification("Failed to download log file", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async clearLogs() {
|
||||
if (!confirm("Are you sure you want to clear all log files? This cannot be undone.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/prompt_manager/logs/truncate', {
|
||||
method: 'POST'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification(data.message, "success");
|
||||
this.refreshLogs();
|
||||
this.loadLogStats();
|
||||
this.loadLogFiles();
|
||||
} else {
|
||||
this.showNotification(`Failed to clear logs: ${data.error}`, "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to clear logs:", error);
|
||||
this.showNotification("Failed to clear logs", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async updateLogConfig() {
|
||||
const level = document.getElementById("setLogLevel").value;
|
||||
const consoleLogging = document.getElementById("consoleLogging").checked;
|
||||
|
||||
try {
|
||||
const response = await fetch('/prompt_manager/logs/config', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
level: level,
|
||||
console_logging: consoleLogging
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification("Log configuration updated", "success");
|
||||
this.loadLogStats();
|
||||
} else {
|
||||
this.showNotification(`Failed to update config: ${data.error}`, "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to update log config:", error);
|
||||
this.showNotification("Failed to update log configuration", "error");
|
||||
}
|
||||
}
|
||||
|
||||
formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user