Updated generation pages.

This commit is contained in:
Aodhan Collins
2026-03-15 17:45:17 +00:00
parent 79bbf669e2
commit d756ea1d0e
30 changed files with 2033 additions and 189 deletions

View File

@@ -12,6 +12,8 @@ def register_routes(app):
from routes import looks
from routes import presets
from routes import generator
from routes import quick
from routes import multi_char
from routes import gallery
from routes import strengths
from routes import transfer
@@ -28,6 +30,8 @@ def register_routes(app):
looks.register_routes(app)
presets.register_routes(app)
generator.register_routes(app)
quick.register_routes(app)
multi_char.register_routes(app)
gallery.register_routes(app)
strengths.register_routes(app)
transfer.register_routes(app)

View File

@@ -213,11 +213,12 @@ def register_routes(app):
combined_data['participants'] = action_obj.data.get('participants', {}) # Add participants
# Aggregate pose-related fields into 'pose'
pose_fields = ['full_body', 'arms', 'hands', 'torso', 'pelvis', 'legs', 'feet']
pose_fields = ['base', 'upper_body', 'lower_body', 'hands', 'feet']
pose_parts = [action_data.get(k) for k in pose_fields if action_data.get(k)]
# Aggregate expression-related fields into 'expression'
expression_parts = [action_data.get(k) for k in ['head', 'eyes'] if action_data.get(k)]
expression_parts = [action_data.get('head', '')]
expression_parts = [p for p in expression_parts if p]
combined_data['defaults'] = {
'pose': ", ".join(pose_parts),
@@ -245,12 +246,13 @@ def register_routes(app):
# Fallback to sensible defaults if still empty (no checkboxes and no action defaults)
selected_fields = ['special::name', 'defaults::pose', 'defaults::expression']
# Add identity fields
for key in ['base_specs', 'hair', 'eyes']:
for key in ['base', 'head']:
if character.data.get('identity', {}).get(key):
selected_fields.append(f'identity::{key}')
# Add wardrobe fields
from utils import _WARDROBE_KEYS
wardrobe = character.get_active_wardrobe()
for key in ['full_body', 'headwear', 'top', 'bottom', 'legwear', 'footwear', 'hands', 'gloves', 'accessories']:
for key in _WARDROBE_KEYS:
if wardrobe.get(key):
selected_fields.append(f'wardrobe::{key}')
@@ -261,11 +263,12 @@ def register_routes(app):
action_data = action_obj.data.get('action', {})
# Aggregate pose-related fields into 'pose'
pose_fields = ['full_body', 'arms', 'hands', 'torso', 'pelvis', 'legs', 'feet']
pose_fields = ['base', 'upper_body', 'lower_body', 'hands', 'feet']
pose_parts = [action_data.get(k) for k in pose_fields if action_data.get(k)]
# Aggregate expression-related fields into 'expression'
expression_parts = [action_data.get(k) for k in ['head', 'eyes'] if action_data.get(k)]
expression_parts = [action_data.get('head', '')]
expression_parts = [p for p in expression_parts if p]
combined_data = {
'character_id': action_obj.action_id,
@@ -312,7 +315,7 @@ def register_routes(app):
# Identity
ident = extra_char.data.get('identity', {})
for key in ['base_specs', 'hair', 'eyes', 'extra']:
for key in ['base', 'head', 'additional']:
val = ident.get(key)
if val:
# Remove 1girl/solo
@@ -320,8 +323,9 @@ def register_routes(app):
extra_parts.append(val)
# Wardrobe (active outfit)
from utils import _WARDROBE_KEYS
wardrobe = extra_char.get_active_wardrobe()
for key in ['top', 'headwear', 'legwear', 'footwear', 'accessories']:
for key in _WARDROBE_KEYS:
val = wardrobe.get(key)
if val:
extra_parts.append(val)
@@ -531,8 +535,8 @@ def register_routes(app):
"action_id": safe_slug,
"action_name": name,
"action": {
"full_body": "", "head": "", "eyes": "", "arms": "", "hands": "",
"torso": "", "pelvis": "", "legs": "", "feet": "", "additional": ""
"base": "", "head": "", "upper_body": "", "lower_body": "",
"hands": "", "feet": "", "additional": ""
},
"lora": {"lora_name": "", "lora_weight": 1.0, "lora_triggers": ""},
"tags": []

View File

@@ -295,14 +295,13 @@ Create an outfit JSON with wardrobe fields appropriate for this character."""
# Ensure required fields
if 'wardrobe' not in outfit_data:
outfit_data['wardrobe'] = {
"full_body": "",
"headwear": "",
"top": "",
"bottom": "",
"legwear": "",
"footwear": "",
"base": "",
"head": "",
"upper_body": "",
"lower_body": "",
"hands": "",
"accessories": ""
"feet": "",
"additional": ""
}
if 'lora' not in outfit_data:
outfit_data['lora'] = {
@@ -392,16 +391,13 @@ Do NOT include a wardrobe section - the outfit is handled separately."""
"character_id": safe_slug,
"character_name": name,
"identity": {
"base_specs": prompt,
"hair": "",
"eyes": "",
"base": prompt,
"head": "",
"upper_body": "",
"lower_body": "",
"hands": "",
"arms": "",
"torso": "",
"pelvis": "",
"legs": "",
"feet": "",
"extra": ""
"additional": ""
},
"defaults": {
"expression": "",
@@ -631,8 +627,8 @@ Do NOT include a wardrobe section - the outfit is handled separately."""
# Create new outfit (copy from default as template)
default_outfit = wardrobe.get('default', {
'headwear': '', 'top': '', 'legwear': '',
'footwear': '', 'hands': '', 'accessories': ''
'base': '', 'head': '', 'upper_body': '', 'lower_body': '',
'hands': '', 'feet': '', 'additional': ''
})
wardrobe[safe_name] = default_outfit.copy()

View File

@@ -31,12 +31,12 @@ def register_routes(app):
combined_data = character.data.copy()
combined_data['character_id'] = character.character_id
selected_fields = []
for key in ['base_specs', 'hair', 'eyes']:
for key in ['base', 'head']:
if character.data.get('identity', {}).get(key):
selected_fields.append(f'identity::{key}')
selected_fields.append('special::name')
wardrobe = character.get_active_wardrobe()
for key in ['full_body', 'top', 'bottom']:
for key in ['base', 'upper_body', 'lower_body']:
if wardrobe.get(key):
selected_fields.append(f'wardrobe::{key}')
prompts = build_prompt(combined_data, selected_fields, None, active_outfit=character.active_outfit)

View File

@@ -45,7 +45,7 @@ def register_routes(app):
else:
# Auto-include essential character fields (minimal set for batch/default generation)
selected_fields = []
for key in ['base_specs', 'hair', 'eyes']:
for key in ['base', 'head']:
if character.data.get('identity', {}).get(key):
selected_fields.append(f'identity::{key}')
selected_fields.append('special::name')

View File

@@ -6,6 +6,7 @@ from services.prompts import build_prompt, build_extras_prompt
from services.workflow import _prepare_workflow, _get_default_checkpoint
from services.job_queue import _enqueue_job, _make_finalize
from services.file_io import get_available_checkpoints
from services.comfyui import get_loaded_checkpoint
logger = logging.getLogger('gaze')
@@ -25,6 +26,12 @@ def register_routes(app):
if not checkpoints:
checkpoints = ["Noob/oneObsession_v19Atypical.safetensors"]
# Default to whatever is currently loaded in ComfyUI, then settings default
selected_ckpt = get_loaded_checkpoint()
if not selected_ckpt:
default_path, _ = _get_default_checkpoint()
selected_ckpt = default_path
if request.method == 'POST':
char_slug = request.form.get('character')
checkpoint = request.form.get('checkpoint')
@@ -63,9 +70,17 @@ def register_routes(app):
if extras:
combined = f"{combined}, {extras}"
if custom_positive:
combined = f"{combined}, {custom_positive}"
combined = f"{custom_positive}, {combined}"
prompts["main"] = combined
# Apply face/hand prompt overrides if provided
override_face = request.form.get('override_face_prompt', '').strip()
override_hand = request.form.get('override_hand_prompt', '').strip()
if override_face:
prompts["face"] = override_face
if override_hand:
prompts["hand"] = override_hand
# Parse optional seed
seed_val = request.form.get('seed', '').strip()
fixed_seed = int(seed_val) if seed_val else None
@@ -103,7 +118,7 @@ def register_routes(app):
return render_template('generator.html', characters=characters, checkpoints=checkpoints,
actions=actions, outfits=outfits, scenes=scenes,
styles=styles, detailers=detailers)
styles=styles, detailers=detailers, selected_ckpt=selected_ckpt)
@app.route('/generator/preview_prompt', methods=['POST'])
def generator_preview_prompt():
@@ -134,6 +149,6 @@ def register_routes(app):
if extras:
combined = f"{combined}, {extras}"
if custom_positive:
combined = f"{combined}, {custom_positive}"
combined = f"{custom_positive}, {combined}"
return {'prompt': combined}
return {'prompt': combined, 'face': prompts['face'], 'hand': prompts['hand']}

View File

@@ -356,16 +356,13 @@ Character ID: {character_slug}"""
"character_id": character_slug,
"character_name": character_name,
"identity": {
"base_specs": lora_data.get('lora_triggers', ''),
"hair": "",
"eyes": "",
"base": lora_data.get('lora_triggers', ''),
"head": "",
"upper_body": "",
"lower_body": "",
"hands": "",
"arms": "",
"torso": "",
"pelvis": "",
"legs": "",
"feet": "",
"extra": ""
"additional": ""
},
"defaults": {
"expression": "",
@@ -373,14 +370,13 @@ Character ID: {character_slug}"""
"scene": ""
},
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "",
"bottom": "",
"legwear": "",
"footwear": "",
"base": "",
"head": "",
"upper_body": "",
"lower_body": "",
"hands": "",
"accessories": ""
"feet": "",
"additional": ""
},
"styles": {
"aesthetic": "",

153
routes/multi_char.py Normal file
View File

@@ -0,0 +1,153 @@
import json
import logging
from flask import render_template, request, session
from models import Character, Action, Style, Scene, Detailer, Checkpoint
from services.prompts import build_multi_prompt, build_extras_prompt, _resolve_character
from services.workflow import _prepare_workflow
from services.job_queue import _enqueue_job, _make_finalize
from services.file_io import get_available_checkpoints
logger = logging.getLogger('gaze')
def register_routes(app):
@app.route('/multi', methods=['GET', 'POST'])
def multi_char():
characters = Character.query.order_by(Character.name).all()
checkpoints = get_available_checkpoints()
actions = Action.query.order_by(Action.name).all()
scenes = Scene.query.order_by(Scene.name).all()
styles = Style.query.order_by(Style.name).all()
detailers = Detailer.query.order_by(Detailer.name).all()
if not checkpoints:
checkpoints = ["Noob/oneObsession_v19Atypical.safetensors"]
if request.method == 'POST':
char_a_slug = request.form.get('char_a')
char_b_slug = request.form.get('char_b')
checkpoint = request.form.get('checkpoint')
custom_positive = request.form.get('positive_prompt', '')
custom_negative = request.form.get('negative_prompt', '')
action_slugs = request.form.getlist('action_slugs')
scene_slugs = request.form.getlist('scene_slugs')
style_slugs = request.form.getlist('style_slugs')
detailer_slugs = request.form.getlist('detailer_slugs')
width = request.form.get('width') or 1024
height = request.form.get('height') or 1024
char_a = _resolve_character(char_a_slug)
char_b = _resolve_character(char_b_slug)
if not char_a or not char_b:
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return {'error': 'Both characters must be selected'}, 400
return render_template('multi_char.html', characters=characters, checkpoints=checkpoints,
actions=actions, scenes=scenes, styles=styles, detailers=detailers)
sel_actions = Action.query.filter(Action.slug.in_(action_slugs)).all() if action_slugs else []
sel_scenes = Scene.query.filter(Scene.slug.in_(scene_slugs)).all() if scene_slugs else []
sel_styles = Style.query.filter(Style.slug.in_(style_slugs)).all() if style_slugs else []
sel_detailers = Detailer.query.filter(Detailer.slug.in_(detailer_slugs)).all() if detailer_slugs else []
try:
with open('comfy_workflow.json', 'r') as f:
workflow = json.load(f)
# Build extras from mix-and-match selections
extras = build_extras_prompt(sel_actions, [], sel_scenes, sel_styles, sel_detailers)
if custom_positive:
extras = f"{custom_positive}, {extras}" if extras else custom_positive
# Build multi-character prompt
prompts = build_multi_prompt(char_a, char_b, extras_prompt=extras)
# Apply prompt overrides if provided
override_main = request.form.get('override_prompt', '').strip()
if override_main:
prompts['main'] = override_main
for key in ('char_a_main', 'char_b_main', 'char_a_face', 'char_b_face'):
override = request.form.get(f'override_{key}', '').strip()
if override:
prompts[key] = override
# Parse optional seed
seed_val = request.form.get('seed', '').strip()
fixed_seed = int(seed_val) if seed_val else None
# Prepare workflow with both character LoRAs
ckpt_obj = Checkpoint.query.filter_by(checkpoint_path=checkpoint).first() if checkpoint else None
workflow = _prepare_workflow(
workflow, char_a, prompts, checkpoint, custom_negative,
action=sel_actions[0] if sel_actions else None,
style=sel_styles[0] if sel_styles else None,
detailer=sel_detailers[0] if sel_detailers else None,
scene=sel_scenes[0] if sel_scenes else None,
width=width,
height=height,
checkpoint_data=ckpt_obj.data if ckpt_obj else None,
fixed_seed=fixed_seed,
character_b=char_b,
)
label = f"Multi: {char_a.name} + {char_b.name}"
slug_pair = f"{char_a.slug}_{char_b.slug}"
_finalize = _make_finalize('multi', slug_pair)
job = _enqueue_job(label, workflow, _finalize)
# Save to session
session['multi_char_a'] = char_a.slug
session['multi_char_b'] = char_b.slug
session.modified = True
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return {'status': 'queued', 'job_id': job['id']}
except Exception as e:
logger.exception("Multi-char generation error: %s", e)
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return {'error': str(e)}, 500
return render_template('multi_char.html', characters=characters, checkpoints=checkpoints,
actions=actions, scenes=scenes, styles=styles, detailers=detailers,
selected_char_a=session.get('multi_char_a', ''),
selected_char_b=session.get('multi_char_b', ''))
@app.route('/multi/preview_prompt', methods=['POST'])
def multi_preview_prompt():
char_a_slug = request.form.get('char_a')
char_b_slug = request.form.get('char_b')
if not char_a_slug or not char_b_slug:
return {'error': 'Both characters must be selected'}, 400
char_a = _resolve_character(char_a_slug)
char_b = _resolve_character(char_b_slug)
if not char_a or not char_b:
return {'error': 'Character not found'}, 404
action_slugs = request.form.getlist('action_slugs')
scene_slugs = request.form.getlist('scene_slugs')
style_slugs = request.form.getlist('style_slugs')
detailer_slugs = request.form.getlist('detailer_slugs')
custom_positive = request.form.get('positive_prompt', '')
sel_actions = Action.query.filter(Action.slug.in_(action_slugs)).all() if action_slugs else []
sel_scenes = Scene.query.filter(Scene.slug.in_(scene_slugs)).all() if scene_slugs else []
sel_styles = Style.query.filter(Style.slug.in_(style_slugs)).all() if style_slugs else []
sel_detailers = Detailer.query.filter(Detailer.slug.in_(detailer_slugs)).all() if detailer_slugs else []
extras = build_extras_prompt(sel_actions, [], sel_scenes, sel_styles, sel_detailers)
if custom_positive:
extras = f"{custom_positive}, {extras}" if extras else custom_positive
prompts = build_multi_prompt(char_a, char_b, extras_prompt=extras)
return {
'prompt': prompts['main'],
'hand': prompts['hand'],
'char_a_main': prompts['char_a_main'],
'char_a_face': prompts['char_a_face'],
'char_b_main': prompts['char_b_main'],
'char_b_face': prompts['char_b_face'],
}

View File

@@ -336,11 +336,12 @@ def register_routes(app):
# No explicit field selection (e.g. batch generation) — build a selection
# that includes identity + wardrobe + name + lora triggers, but NOT character
# defaults (expression, pose, scene), so outfit covers stay generic.
for key in ['base_specs', 'hair', 'eyes', 'hands', 'arms', 'torso', 'pelvis', 'legs', 'feet', 'extra']:
from utils import _IDENTITY_KEYS, _WARDROBE_KEYS
for key in _IDENTITY_KEYS:
if character.data.get('identity', {}).get(key):
selected_fields.append(f'identity::{key}')
outfit_wardrobe = outfit.data.get('wardrobe', {})
for key in ['full_body', 'headwear', 'top', 'bottom', 'legwear', 'footwear', 'hands', 'gloves', 'accessories']:
for key in _WARDROBE_KEYS:
if outfit_wardrobe.get(key):
selected_fields.append(f'wardrobe::{key}')
selected_fields.append('special::name')
@@ -456,14 +457,13 @@ def register_routes(app):
# Ensure required fields exist
if 'wardrobe' not in outfit_data:
outfit_data['wardrobe'] = {
"full_body": "",
"headwear": "",
"top": "",
"bottom": "",
"legwear": "",
"footwear": "",
"base": "",
"head": "",
"upper_body": "",
"lower_body": "",
"hands": "",
"accessories": ""
"feet": "",
"additional": ""
}
if 'lora' not in outfit_data:
outfit_data['lora'] = {
@@ -484,14 +484,13 @@ def register_routes(app):
"outfit_id": safe_slug,
"outfit_name": name,
"wardrobe": {
"full_body": "",
"headwear": "",
"top": "",
"bottom": "",
"legwear": "",
"footwear": "",
"base": "",
"head": "",
"upper_body": "",
"lower_body": "",
"hands": "",
"accessories": ""
"feet": "",
"additional": ""
},
"lora": {
"lora_name": "",

View File

@@ -70,18 +70,24 @@ def register_routes(app):
detailer_obj = _resolve_preset_entity('detailer', detailer_cfg.get('detailer_id'))
look_obj = _resolve_preset_entity('look', look_cfg.get('look_id'))
# Checkpoint: preset override or session default
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
# Checkpoint: form override > preset config > session default
checkpoint_override = request.form.get('checkpoint_override', '').strip()
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:
ckpt_path, ckpt_data = _get_default_checkpoint()
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()
# Resolve selected fields from preset toggles
selected_fields = _resolve_preset_fields(data)
@@ -107,7 +113,8 @@ def register_routes(app):
extras_parts = []
if action_obj:
action_fields = action_cfg.get('fields', {})
for key in ['full_body', 'additional', 'head', 'eyes', 'arms', 'hands']:
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])
@@ -172,6 +179,23 @@ def register_routes(app):
seed_val = request.form.get('seed', '').strip()
fixed_seed = int(seed_val) if seed_val else None
# Resolution: form override > preset config > workflow default
res_cfg = data.get('resolution', {})
form_width = request.form.get('width', '').strip()
form_height = request.form.get('height', '').strip()
if form_width and form_height:
gen_width = int(form_width)
gen_height = int(form_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,
@@ -183,6 +207,8 @@ def register_routes(app):
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}"
@@ -258,13 +284,13 @@ def register_routes(app):
'use_lora': request.form.get('char_use_lora') == 'on',
'fields': {
'identity': {k: _tog(request.form.get(f'id_{k}', 'true'))
for k in ['base_specs', 'hair', 'eyes', 'hands', 'arms', 'torso', 'pelvis', 'legs', 'feet', 'extra']},
for k in ['base', 'head', 'upper_body', 'lower_body', 'hands', 'feet', 'additional']},
'defaults': {k: _tog(request.form.get(f'def_{k}', 'false'))
for k in ['expression', 'pose', 'scene']},
'wardrobe': {
'outfit': request.form.get('wardrobe_outfit', 'default') or 'default',
'fields': {k: _tog(request.form.get(f'wd_{k}', 'true'))
for k in ['full_body', 'headwear', 'top', 'bottom', 'legwear', 'footwear', 'hands', 'gloves', 'accessories']},
for k in ['base', 'head', 'upper_body', 'lower_body', 'hands', 'feet', 'additional']},
},
},
},
@@ -273,7 +299,7 @@ def register_routes(app):
'action': {'action_id': _entity_id(request.form.get('action_id')),
'use_lora': request.form.get('action_use_lora') == 'on',
'fields': {k: _tog(request.form.get(f'act_{k}', 'true'))
for k in ['full_body', 'additional', 'head', 'eyes', 'arms', 'hands']}},
for k in ['base', 'head', 'upper_body', 'lower_body', 'hands', 'feet', 'additional']}},
'style': {'style_id': _entity_id(request.form.get('style_id')),
'use_lora': request.form.get('style_use_lora') == 'on'},
'scene': {'scene_id': _entity_id(request.form.get('scene_id')),
@@ -284,6 +310,11 @@ def register_routes(app):
'use_lora': request.form.get('detailer_use_lora') == 'on'},
'look': {'look_id': _entity_id(request.form.get('look_id'))},
'checkpoint': {'checkpoint_path': _entity_id(request.form.get('checkpoint_path'))},
'resolution': {
'width': int(request.form.get('res_width', 1024)),
'height': int(request.form.get('res_height', 1024)),
'random': request.form.get('res_random') == 'on',
},
'tags': [t.strip() for t in request.form.get('tags', '').split(',') if t.strip()],
}
@@ -399,20 +430,21 @@ def register_routes(app):
preset_data = {
'character': {'character_id': 'random', 'use_lora': True,
'fields': {
'identity': {k: True for k in ['base_specs', 'hair', 'eyes', 'hands', 'arms', 'torso', 'pelvis', 'legs', 'feet', 'extra']},
'identity': {k: True for k in ['base', 'head', 'upper_body', 'lower_body', 'hands', 'feet', 'additional']},
'defaults': {k: False for k in ['expression', 'pose', 'scene']},
'wardrobe': {'outfit': 'default',
'fields': {k: True for k in ['full_body', 'headwear', 'top', 'bottom', 'legwear', 'footwear', 'hands', 'gloves', 'accessories']}},
'fields': {k: True for k in ['base', 'head', 'upper_body', 'lower_body', 'hands', 'feet', 'additional']}},
}},
'outfit': {'outfit_id': None, 'use_lora': True},
'action': {'action_id': None, 'use_lora': True,
'fields': {k: True for k in ['full_body', 'additional', 'head', 'eyes', 'arms', 'hands']}},
'fields': {k: True for k in ['base', 'head', 'upper_body', 'lower_body', 'hands', 'feet', 'additional']}},
'style': {'style_id': None, 'use_lora': True},
'scene': {'scene_id': None, 'use_lora': True,
'fields': {k: True for k in ['background', 'foreground', 'furniture', 'colors', 'lighting', 'theme']}},
'detailer': {'detailer_id': None, 'use_lora': True},
'look': {'look_id': None},
'checkpoint': {'checkpoint_path': None},
'resolution': {'width': 1024, 'height': 1024, 'random': False},
'tags': [],
}

25
routes/quick.py Normal file
View File

@@ -0,0 +1,25 @@
import logging
from flask import render_template
from models import Preset
from services.file_io import get_available_checkpoints
from services.comfyui import get_loaded_checkpoint
from services.workflow import _get_default_checkpoint
logger = logging.getLogger('gaze')
def register_routes(app):
@app.route('/quick')
def quick_generator():
presets = Preset.query.order_by(Preset.name).all()
checkpoints = get_available_checkpoints()
# Default to whatever is currently loaded in ComfyUI, then settings default
selected_ckpt = get_loaded_checkpoint()
if not selected_ckpt:
default_path, _ = _get_default_checkpoint()
selected_ckpt = default_path
return render_template('quick.html', presets=presets,
checkpoints=checkpoints, selected_ckpt=selected_ckpt)

View File

@@ -202,7 +202,7 @@ def register_routes(app):
else:
# Auto-include essential character fields (minimal set for batch/default generation)
selected_fields = []
for key in ['base_specs', 'hair', 'eyes']:
for key in ['base', 'head']:
if character.data.get('identity', {}).get(key):
selected_fields.append(f'identity::{key}')
selected_fields.append('special::name')

View File

@@ -78,11 +78,11 @@ def register_routes(app):
if character:
identity = character.data.get('identity', {})
defaults = character.data.get('defaults', {})
char_parts = [v for v in [identity.get('base_specs'), identity.get('hair'),
identity.get('eyes'), defaults.get('expression')] if v]
face_parts = [v for v in [identity.get('hair'), identity.get('eyes'),
char_parts = [v for v in [identity.get('base'), identity.get('head'),
defaults.get('expression')] if v]
hand_parts = [v for v in [wardrobe.get('hands'), wardrobe.get('gloves')] if v]
face_parts = [v for v in [identity.get('head'),
defaults.get('expression')] if v]
hand_parts = [v for v in [wardrobe.get('hands')] if v]
main_parts = ([outfit_triggers] if outfit_triggers else []) + char_parts + wardrobe_parts + tags
return {
'main': _dedup_tags(', '.join(p for p in main_parts if p)),
@@ -94,17 +94,16 @@ def register_routes(app):
action_data = entity.data.get('action', {})
action_triggers = entity.data.get('lora', {}).get('lora_triggers', '')
tags = entity.data.get('tags', [])
pose_fields = ['full_body', 'arms', 'hands', 'torso', 'pelvis', 'legs', 'feet', 'additional']
pose_parts = [action_data.get(k, '') for k in pose_fields if action_data.get(k)]
expr_parts = [action_data.get(k, '') for k in ['head', 'eyes'] if action_data.get(k)]
from utils import _BODY_GROUP_KEYS
pose_parts = [action_data.get(k, '') for k in _BODY_GROUP_KEYS if action_data.get(k)]
expr_parts = [action_data.get('head', '')] if action_data.get('head') else []
char_parts = []
face_parts = list(expr_parts)
hand_parts = [action_data.get('hands', '')] if action_data.get('hands') else []
if character:
identity = character.data.get('identity', {})
char_parts = [v for v in [identity.get('base_specs'), identity.get('hair'),
identity.get('eyes')] if v]
face_parts = [v for v in [identity.get('hair'), identity.get('eyes')] + expr_parts if v]
char_parts = [v for v in [identity.get('base'), identity.get('head')] if v]
face_parts = [v for v in [identity.get('head')] + expr_parts if v]
main_parts = ([action_triggers] if action_triggers else []) + char_parts + pose_parts + tags
return {
'main': _dedup_tags(', '.join(p for p in main_parts if p)),
@@ -130,15 +129,15 @@ def register_routes(app):
entity_parts = [p for p in [entity_triggers, det_prompt] + tags if p]
char_data_no_lora = _get_character_data_without_lora(character)
base = build_prompt(char_data_no_lora, [], character.default_fields) if char_data_no_lora else {'main': '', 'face': '', 'hand': ''}
base = build_prompt(char_data_no_lora, [], character.default_fields) if char_data_no_lora else {'main': '', 'face': '', 'hand': '', 'feet': ''}
entity_str = ', '.join(entity_parts)
if entity_str:
base['main'] = f"{base['main']}, {entity_str}" if base['main'] else entity_str
if action is not None:
action_data = action.data.get('action', {})
action_parts = [action_data.get(k, '') for k in
['full_body', 'arms', 'hands', 'torso', 'pelvis', 'legs', 'feet', 'additional', 'head', 'eyes']
from utils import _BODY_GROUP_KEYS
action_parts = [action_data.get(k, '') for k in _BODY_GROUP_KEYS
if action_data.get(k)]
action_str = ', '.join(action_parts)
if action_str:

View File

@@ -42,7 +42,7 @@ def register_routes(app):
else:
# Auto-include essential character fields (minimal set for batch/default generation)
selected_fields = []
for key in ['base_specs', 'hair', 'eyes']:
for key in ['base', 'head']:
if character.data.get('identity', {}).get(key):
selected_fields.append(f'identity::{key}')
selected_fields.append('special::name')

View File

@@ -88,8 +88,8 @@ def register_routes(app):
'outfit_id': slug,
'outfit_name': name,
'wardrobe': source_data.get('wardrobe', {
'full_body': '', 'headwear': '', 'top': '', 'bottom': '',
'legwear': '', 'footwear': '', 'hands': '', 'accessories': ''
'base': '', 'head': '', 'upper_body': '', 'lower_body': '',
'hands': '', 'feet': '', 'additional': ''
}),
'lora': source_data.get('lora', {'lora_name': '', 'lora_weight': 0.8, 'lora_triggers': ''}),
'tags': source_data.get('tags', []),
@@ -99,8 +99,8 @@ def register_routes(app):
'action_id': slug,
'action_name': name,
'action': source_data.get('action', {
'full_body': '', 'head': '', 'eyes': '', 'arms': '', 'hands': '',
'torso': '', 'pelvis': '', 'legs': '', 'feet': '', 'additional': ''
'base': '', 'head': '', 'upper_body': '', 'lower_body': '',
'hands': '', 'feet': '', 'additional': ''
}),
'lora': source_data.get('lora', {'lora_name': '', 'lora_weight': 1.0, 'lora_triggers': ''}),
'tags': source_data.get('tags', []),