import json import random import logging from models import db, Character, Checkpoint, Preset from services.prompts import build_prompt, _dedup_tags from services.workflow import _prepare_workflow, _get_default_checkpoint from services.job_queue import _enqueue_job, _make_finalize from services.sync import _resolve_preset_entity, _resolve_preset_fields logger = logging.getLogger('gaze') def generate_from_preset(preset, overrides=None, save_category='presets'): """Execute preset-based generation. Args: preset: Preset ORM object overrides: optional dict with keys: checkpoint, extra_positive, extra_negative, seed, width, height, action save_category: upload sub-directory ('presets' or 'generator') Returns: job dict from _enqueue_job() """ if overrides is None: overrides = {} action = overrides.get('action', 'preview') extra_positive = overrides.get('extra_positive', '').strip() extra_negative = overrides.get('extra_negative', '').strip() data = preset.data # Resolve entities char_cfg = data.get('character', {}) character = _resolve_preset_entity('character', char_cfg.get('character_id')) if not character: character = Character.query.order_by(db.func.random()).first() outfit_cfg = data.get('outfit', {}) action_cfg = data.get('action', {}) style_cfg = data.get('style', {}) scene_cfg = data.get('scene', {}) detailer_cfg = data.get('detailer', {}) look_cfg = data.get('look', {}) ckpt_cfg = data.get('checkpoint', {}) outfit = _resolve_preset_entity('outfit', outfit_cfg.get('outfit_id')) action_obj = _resolve_preset_entity('action', action_cfg.get('action_id')) style_obj = _resolve_preset_entity('style', style_cfg.get('style_id')) scene_obj = _resolve_preset_entity('scene', scene_cfg.get('scene_id')) detailer_obj = _resolve_preset_entity('detailer', detailer_cfg.get('detailer_id')) look_obj = _resolve_preset_entity('look', look_cfg.get('look_id')) # Build sidecar metadata with resolved entity slugs resolved_meta = { 'preset_slug': preset.slug, 'preset_name': preset.name, 'character_slug': character.slug if character else None, 'outfit_slug': outfit.slug if outfit else None, 'action_slug': action_obj.slug if action_obj else None, 'style_slug': style_obj.slug if style_obj else None, 'scene_slug': scene_obj.slug if scene_obj else None, 'detailer_slug': detailer_obj.slug if detailer_obj else None, 'look_slug': look_obj.slug if look_obj else None, } # Checkpoint: override > preset config > default checkpoint_override = overrides.get('checkpoint', '').strip() if overrides.get('checkpoint') else '' if checkpoint_override: ckpt_obj = Checkpoint.query.filter_by(checkpoint_path=checkpoint_override).first() ckpt_path = checkpoint_override ckpt_data = ckpt_obj.data if ckpt_obj else None else: preset_ckpt = ckpt_cfg.get('checkpoint_path') if preset_ckpt == 'random': ckpt_obj = Checkpoint.query.order_by(db.func.random()).first() ckpt_path = ckpt_obj.checkpoint_path if ckpt_obj else None ckpt_data = ckpt_obj.data if ckpt_obj else None elif preset_ckpt: ckpt_obj = Checkpoint.query.filter_by(checkpoint_path=preset_ckpt).first() ckpt_path = preset_ckpt ckpt_data = ckpt_obj.data if ckpt_obj else None else: ckpt_path, ckpt_data = _get_default_checkpoint() resolved_meta['checkpoint_path'] = ckpt_path # Resolve selected fields from preset toggles selected_fields = _resolve_preset_fields(data) # Check suppress_wardrobe: preset override > action default suppress_wardrobe = False preset_suppress = action_cfg.get('suppress_wardrobe') if preset_suppress == 'random': suppress_wardrobe = random.choice([True, False]) elif preset_suppress is not None: suppress_wardrobe = bool(preset_suppress) elif action_obj: suppress_wardrobe = action_obj.data.get('suppress_wardrobe', False) if suppress_wardrobe: selected_fields = [f for f in selected_fields if not f.startswith('wardrobe::')] # Build combined data for prompt building active_wardrobe = char_cfg.get('fields', {}).get('wardrobe', {}).get('outfit', 'default') wardrobe_source = outfit.data.get('wardrobe', {}) if outfit else None if wardrobe_source is None: wardrobe_source = character.get_active_wardrobe() if character else {} if suppress_wardrobe: wardrobe_source = {} combined_data = { 'character_id': character.character_id if character else 'unknown', 'identity': character.data.get('identity', {}) if character else {}, 'defaults': character.data.get('defaults', {}) if character else {}, 'wardrobe': wardrobe_source, 'styles': character.data.get('styles', {}) if character else {}, 'lora': (look_obj.data.get('lora', {}) if look_obj else (character.data.get('lora', {}) if character else {})), } # Build extras prompt from secondary resources extras_parts = [] if action_obj: action_fields = action_cfg.get('fields', {}) from utils import _BODY_GROUP_KEYS for key in _BODY_GROUP_KEYS: val_cfg = action_fields.get(key, True) if val_cfg == 'random': val_cfg = random.choice([True, False]) if val_cfg: val = action_obj.data.get('action', {}).get(key, '') if val: extras_parts.append(val) if action_cfg.get('use_lora', True): trg = action_obj.data.get('lora', {}).get('lora_triggers', '') if trg: extras_parts.append(trg) if style_obj: s = style_obj.data.get('style', {}) if s.get('artist_name'): extras_parts.append(f"by {s['artist_name']}") if s.get('artistic_style'): extras_parts.append(s['artistic_style']) if style_cfg.get('use_lora', True): trg = style_obj.data.get('lora', {}).get('lora_triggers', '') if trg: extras_parts.append(trg) if scene_obj: scene_fields = scene_cfg.get('fields', {}) for key in ['background', 'foreground', 'furniture', 'colors', 'lighting', 'theme']: val_cfg = scene_fields.get(key, True) if val_cfg == 'random': val_cfg = random.choice([True, False]) if val_cfg: val = scene_obj.data.get('scene', {}).get(key, '') if val: extras_parts.append(val) if scene_cfg.get('use_lora', True): trg = scene_obj.data.get('lora', {}).get('lora_triggers', '') if trg: extras_parts.append(trg) if detailer_obj: prompt_val = detailer_obj.data.get('prompt', '') if isinstance(prompt_val, list): extras_parts.extend(p for p in prompt_val if p) elif prompt_val: extras_parts.append(prompt_val) if detailer_cfg.get('use_lora', True): trg = detailer_obj.data.get('lora', {}).get('lora_triggers', '') if trg: extras_parts.append(trg) with open('comfy_workflow.json', 'r') as f: workflow = json.load(f) prompts = build_prompt(combined_data, selected_fields, default_fields=None, active_outfit=active_wardrobe) if extras_parts: extra_str = ', '.join(filter(None, extras_parts)) prompts['main'] = _dedup_tags(f"{prompts['main']}, {extra_str}" if prompts['main'] else extra_str) if extra_positive: prompts["main"] = f"{prompts['main']}, {extra_positive}" # Parse optional seed fixed_seed = overrides.get('seed') if fixed_seed is not None: fixed_seed = int(fixed_seed) # Resolution: override > preset config > workflow default res_cfg = data.get('resolution', {}) override_width = overrides.get('width') override_height = overrides.get('height') if override_width and override_height: gen_width = int(override_width) gen_height = int(override_height) elif res_cfg.get('random', False): _RES_OPTIONS = [ (1024, 1024), (1152, 896), (896, 1152), (1344, 768), (768, 1344), (1280, 800), (800, 1280), ] gen_width, gen_height = random.choice(_RES_OPTIONS) else: gen_width = res_cfg.get('width') or None gen_height = res_cfg.get('height') or None workflow = _prepare_workflow( workflow, character, prompts, checkpoint=ckpt_path, checkpoint_data=ckpt_data, custom_negative=extra_negative or None, outfit=outfit if outfit_cfg.get('use_lora', True) else None, action=action_obj if action_cfg.get('use_lora', True) else None, style=style_obj if style_cfg.get('use_lora', True) else None, scene=scene_obj if scene_cfg.get('use_lora', True) else None, detailer=detailer_obj if detailer_cfg.get('use_lora', True) else None, look=look_obj, fixed_seed=fixed_seed, width=gen_width, height=gen_height, ) label = f"Preset: {preset.name} – {action}" db_model = Preset if save_category == 'presets' else None job = _enqueue_job(label, workflow, _make_finalize(save_category, preset.slug, db_model, action, metadata=resolved_meta)) return job