import os import logging from flask import Flask from flask_session import Session from models import db, Settings, Look app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['UPLOAD_FOLDER'] = 'static/uploads' app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-key-123') app.config['CHARACTERS_DIR'] = 'data/characters' app.config['CLOTHING_DIR'] = 'data/clothing' app.config['ACTIONS_DIR'] = 'data/actions' app.config['STYLES_DIR'] = 'data/styles' app.config['SCENES_DIR'] = 'data/scenes' app.config['DETAILERS_DIR'] = 'data/detailers' app.config['CHECKPOINTS_DIR'] = 'data/checkpoints' app.config['LOOKS_DIR'] = 'data/looks' app.config['PRESETS_DIR'] = 'data/presets' app.config['COMFYUI_URL'] = os.environ.get('COMFYUI_URL', 'http://127.0.0.1:8188') app.config['ILLUSTRIOUS_MODELS_DIR'] = '/ImageModels/Stable-diffusion/Illustrious/' app.config['NOOB_MODELS_DIR'] = '/ImageModels/Stable-diffusion/Noob/' app.config['LORA_DIR'] = '/ImageModels/lora/Illustrious/Looks/' # Server-side session configuration to avoid cookie size limits app.config['SESSION_TYPE'] = 'filesystem' app.config['SESSION_FILE_DIR'] = os.path.join(app.config['UPLOAD_FOLDER'], '../flask_session') app.config['SESSION_PERMANENT'] = False db.init_app(app) Session(app) # --------------------------------------------------------------------------- # Logging # --------------------------------------------------------------------------- log_level_str = os.environ.get('LOG_LEVEL', 'INFO').upper() log_level = getattr(logging, log_level_str, logging.INFO) logging.basicConfig( level=log_level, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', ) logger = logging.getLogger('gaze') logger.setLevel(log_level) # --------------------------------------------------------------------------- # Register all routes # --------------------------------------------------------------------------- from routes import register_routes register_routes(app) # --------------------------------------------------------------------------- # Startup # --------------------------------------------------------------------------- if __name__ == '__main__': from services.mcp import ensure_mcp_server_running, ensure_character_mcp_server_running from services.job_queue import init_queue_worker from services.sync import ( sync_characters, sync_outfits, sync_actions, sync_styles, sync_detailers, sync_scenes, sync_looks, sync_checkpoints, sync_presets, ) ensure_mcp_server_running() ensure_character_mcp_server_running() init_queue_worker(app) with app.app_context(): from sqlalchemy import text os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) db.create_all() # --- Helper for safe column additions --- def _add_column(table, column, col_type): try: db.session.execute(text(f'ALTER TABLE {table} ADD COLUMN {column} {col_type}')) db.session.commit() logger.info("Added %s.%s column", table, column) except Exception as e: db.session.rollback() if 'duplicate column name' not in str(e).lower() and 'already exists' not in str(e).lower(): logger.debug("Migration note (%s.%s): %s", table, column, e) # --- All migrations (grouped before syncs) --- _add_column('character', 'active_outfit', "VARCHAR(100) DEFAULT 'default'") _add_column('action', 'default_fields', 'JSON') _add_column('checkpoint', 'data', 'JSON') _add_column('look', 'character_ids', 'JSON') # Settings columns for col_name, col_type in [ ('llm_provider', "VARCHAR(50) DEFAULT 'openrouter'"), ('local_base_url', 'VARCHAR(255)'), ('local_model', 'VARCHAR(100)'), ('lora_dir_characters', "VARCHAR(500) DEFAULT '/ImageModels/lora/Illustrious/Looks'"), ('lora_dir_outfits', "VARCHAR(500) DEFAULT '/ImageModels/lora/Illustrious/Clothing'"), ('lora_dir_actions', "VARCHAR(500) DEFAULT '/ImageModels/lora/Illustrious/Poses'"), ('lora_dir_styles', "VARCHAR(500) DEFAULT '/ImageModels/lora/Illustrious/Styles'"), ('lora_dir_scenes', "VARCHAR(500) DEFAULT '/ImageModels/lora/Illustrious/Backgrounds'"), ('lora_dir_detailers', "VARCHAR(500) DEFAULT '/ImageModels/lora/Illustrious/Detailers'"), ('checkpoint_dirs', "VARCHAR(1000) DEFAULT '/ImageModels/Stable-diffusion/Illustrious,/ImageModels/Stable-diffusion/Noob'"), ('default_checkpoint', 'VARCHAR(500)'), ('api_key', 'VARCHAR(255)'), ]: _add_column('settings', col_name, col_type) # is_favourite / is_nsfw on all resource tables for tbl in ['character', 'look', 'outfit', 'action', 'style', 'scene', 'detailer', 'checkpoint']: _add_column(tbl, 'is_favourite', 'BOOLEAN DEFAULT 0') _add_column(tbl, 'is_nsfw', 'BOOLEAN DEFAULT 0') # Ensure settings exist if not Settings.query.first(): db.session.add(Settings()) db.session.commit() logger.info("Created default settings") # Log default checkpoint settings = Settings.query.first() if settings and settings.default_checkpoint: logger.info("Default checkpoint: %s", settings.default_checkpoint) else: logger.info("No default checkpoint set in database") # --- Sync all categories --- sync_characters() sync_outfits() sync_actions() sync_styles() sync_detailers() sync_scenes() sync_looks() sync_checkpoints() sync_presets() # --- Post-sync data migration: character_id → character_ids --- try: looks_with_old_field = Look.query.filter(Look.character_id.isnot(None)).all() migrated_count = 0 for look in looks_with_old_field: if not look.character_ids: look.character_ids = [] if look.character_id and look.character_id not in look.character_ids: look.character_ids.append(look.character_id) migrated_count += 1 if migrated_count > 0: db.session.commit() logger.info("Migrated %d looks from character_id to character_ids", migrated_count) except Exception as e: logger.debug("Migration note (character_ids data): %s", e) app.run(debug=True, host='0.0.0.0', port=5000)