diff --git a/.gitignore b/.gitignore index 1e22578..15a5c92 100644 --- a/.gitignore +++ b/.gitignore @@ -154,6 +154,6 @@ dmypy.json # Cython debug symbols cython_debug/ -settings.local.ini -*code-workspace \ No newline at end of file +*code-workspace +settings.user.ini diff --git a/docker-compose.yml b/docker-compose.yml index c0d58b9..af46cdd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,4 +8,9 @@ services: - "8080:8080" volumes: - ${HOST_OUTPUT_DIR}:/app/output - restart: unless-stopped \ No newline at end of file + - replicate-flux-lora-settings:/app/settings + restart: unless-stopped + + +volumes: + replicate-flux-lora-settings: \ No newline at end of file diff --git a/settings.ini b/settings.ini index 201841e..1a82419 100644 --- a/settings.ini +++ b/settings.ini @@ -5,7 +5,7 @@ num_outputs = 1 lora_scale = 1 num_inference_steps = 28 guidance_scale = 3.5 -output_format = png +output_format = output_quality = 80 disable_safety_checker = true width = 1024 @@ -14,4 +14,4 @@ seed = -1 output_folder = /path/to/default/output/folder replicate_model = models = {"user_added": []} - +prompt = diff --git a/src/config.py b/src/config.py index e10caac..ea20b8f 100644 --- a/src/config.py +++ b/src/config.py @@ -1,29 +1,80 @@ import configparser import os +from typing import Any, Type + +from loguru import logger + +DOCKERIZED = os.environ.get("DOCKER_CONTAINER", "False").lower() == "true" +CONFIG_DIR = "/app/settings" if DOCKERIZED else "." +DEFAULT_CONFIG_FILE = os.path.join(CONFIG_DIR, "settings.ini") +USER_CONFIG_FILE = os.path.join(CONFIG_DIR, "settings.user.ini") + +logger.info( + f"Configuration files: DEFAULT={DEFAULT_CONFIG_FILE}, USER={USER_CONFIG_FILE}" +) config = configparser.ConfigParser() -config.read(["settings.ini", "settings.local.ini"]) +config.read([DEFAULT_CONFIG_FILE, USER_CONFIG_FILE]) +logger.info("Configuration files loaded") def get_api_key(): - return os.environ.get("REPLICATE_API_KEY") or config.get( + api_key = os.environ.get("REPLICATE_API_KEY") or config.get( "secrets", "REPLICATE_API_KEY", fallback=None ) + if api_key: + logger.info("API key retrieved successfully") + else: + logger.warning("No API key found") + return api_key -def get_setting(section, key, fallback=None): +def get_setting( + section: str, key: str, fallback: Any = None, value_type: Type[Any] = str +) -> Any: + logger.info( + f"Attempting to get setting: section={section}, key={key}, fallback={fallback}, value_type={value_type}" + ) try: - return config.get(section, key) - except (configparser.NoSectionError, configparser.NoOptionError): + value = config.get(section, key) + logger.debug(f"Raw value retrieved: {value}") + if value_type == int: + result = int(value) + elif value_type == float: + result = float(value) + elif value_type == bool: + result = value.lower() in ("true", "yes", "1", "on") + else: + result = value + logger.info(f"Setting retrieved successfully: {result}") + return result + except (configparser.NoSectionError, configparser.NoOptionError) as e: + logger.warning(f"Setting not found: {str(e)}. Using fallback value: {fallback}") + return fallback + except ValueError as e: + logger.error( + f"Error converting setting value: {str(e)}. Using fallback value: {fallback}" + ) return fallback def set_setting(section, key, value): + logger.info(f"Setting value: section={section}, key={key}, value={value}") if not config.has_section(section): + logger.info(f"Creating new section: {section}") config.add_section(section) config.set(section, key, str(value)) + logger.info("Value set successfully") def save_settings(): - with open("settings.local.ini", "w") as configfile: - config.write(configfile) + logger.info(f"Saving settings to {USER_CONFIG_FILE}") + try: + with open(USER_CONFIG_FILE, "w") as configfile: + config.write(configfile) + logger.info("Settings saved successfully") + except IOError as e: + logger.error(f"Error saving settings: {str(e)}") + + +logger.info("Config module initialized") diff --git a/src/gui.py b/src/gui.py index f9e67dc..a4bf86b 100644 --- a/src/gui.py +++ b/src/gui.py @@ -1,7 +1,6 @@ import asyncio import json import os -import sys import urllib.parse import zipfile from datetime import datetime @@ -12,23 +11,17 @@ from loguru import logger from nicegui import ui -logger.remove() -logger.add( - sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO" -) -logger.add("gui.log", rotation="10 MB", format="{time} {level} {message}", level="INFO") - DOCKERIZED = os.environ.get("DOCKER_CONTAINER", False) -SETTINGS_LOCAL_FILE = "settings.local.toml" - class Lightbox: def __init__(self): + logger.debug("Initializing Lightbox") with ui.dialog().props("maximized").classes("bg-black") as self.dialog: self.dialog.on_key = self._handle_key self.large_image = ui.image().props("no-spinner fit=scale-down") self.image_list = [] + logger.debug("Lightbox initialized") def add_image( self, @@ -36,32 +29,40 @@ def add_image( orig_url: str, thumb_classes: str = "w-32 h-32 object-cover", ) -> ui.button: + logger.debug(f"Adding image to Lightbox: {orig_url}") self.image_list.append(orig_url) button = ui.button(on_click=lambda: self._open(orig_url)).props( "flat dense square" ) with button: ui.image(thumb_url).classes(thumb_classes) + logger.debug("Image added to Lightbox") return button def _handle_key(self, e) -> None: + logger.debug(f"Handling key press in Lightbox: {e.key}") if not e.action.keydown: return if e.key.escape: + logger.debug("Closing Lightbox dialog") self.dialog.close() image_index = self.image_list.index(self.large_image.source) if e.key.arrow_left and image_index > 0: + logger.debug("Displaying previous image") self._open(self.image_list[image_index - 1]) if e.key.arrow_right and image_index < len(self.image_list) - 1: + logger.debug("Displaying next image") self._open(self.image_list[image_index + 1]) def _open(self, url: str) -> None: + logger.debug(f"Opening image in Lightbox: {url}") self.large_image.set_source(url) self.dialog.open() class ImageGeneratorGUI: def __init__(self, image_generator): + logger.info("Initializing ImageGeneratorGUI") self.image_generator = image_generator self.api_key = get_api_key() or os.environ.get("REPLICATE_API_KEY", "") self.last_generated_images = [] @@ -85,33 +86,43 @@ def __init__(self, image_generator): ] self.user_added_models = {} - self.load_settings() - - self.flux_model = get_setting("default", "flux_model", "dev") - self.aspect_ratio = get_setting("default", "aspect_ratio", "1:1") - self.num_outputs = int(get_setting("default", "num_outputs", "1")) - self.lora_scale = float(get_setting("default", "lora_scale", "1")) - self.num_inference_steps = int( - get_setting("default", "num_inference_steps", "28") + self.prompt = get_setting("default", "prompt", "", str) + + self.flux_model = get_setting("default", "flux_model", "dev", str) + self.aspect_ratio = get_setting("default", "aspect_ratio", "1:1", str) + self.num_outputs = get_setting("default", "num_outputs", "1", int) + self.lora_scale = get_setting("default", "lora_scale", "1", float) + self.num_inference_steps = get_setting( + "default", "num_inference_steps", "28", int ) - self.guidance_scale = float(get_setting("default", "guidance_scale", "3.5")) - self.output_format = get_setting("default", "output_format", "webp") - self.output_quality = int(get_setting("default", "output_quality", "80")) - self.disable_safety_checker = ( - get_setting("default", "disable_safety_checker", "False").lower() == "true" + self.guidance_scale = get_setting("default", "guidance_scale", "3.5", float) + self.output_format = get_setting("default", "output_format", "png") + self.output_quality = get_setting("default", "output_quality", "80", int) + self.disable_safety_checker = get_setting( + "default", "disable_safety_checker", True, bool ) - self.width = int(get_setting("default", "width", "1024")) - self.height = int(get_setting("default", "height", "1024")) - self.seed = int(get_setting("default", "seed", "-1")) - if not self.output_folder: - self.output_folder = ( - str(Path.home() / "Downloads") if not DOCKERIZED else "/app/output" - ) + self.width = get_setting("default", "width", "1024", int) + self.height = get_setting("default", "height", "1024", int) + self.seed = get_setting("default", "seed", "-1", int) + + self.output_folder = ( + "/app/output" + if DOCKERIZED + else get_setting("default", "output_folder", "/Downloads", str) + ) + models_json = get_setting("default", "models", '{"user_added": []}', str) + models = json.loads(models_json) + self.user_added_models = { + model: model for model in models.get("user_added", []) + } + self.model_options = list(self.user_added_models.keys()) + self.replicate_model = get_setting("default", "replicate_model", "", str) logger.info("ImageGeneratorGUI initialized") def setup_custom_styles(self): + logger.debug("Setting up custom styles") ui.add_head_html("""