Refactor UI, settings, and code quality across all categories
- Fix Replace Cover: routes now read preview_path from form POST instead of session (session writes from background threads were lost) - Fix batch generation: submit all jobs immediately, poll all in parallel via Promise.all - Fix label NameError in character generate route - Fix style detail missing characters context - Selected Preview pane: click any image to select it; data-preview-path on all images across all 8 detail templates - Gallery → Library rename across all index page headings and navbar - Settings: add configurable LoRA/checkpoint directories; default checkpoint selector moved from navbar to settings page - Consolidate 6 get_available_*_loras() into single get_available_loras(category) reading from Settings - ComfyUI tooltip shows currently loaded checkpoint name - Remove navbar checkpoint bar - Phase 4 cleanup: remove dead _queue_generation(), add session.modified, standardize log prefixes, rename action_type → action Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
315
app.py
315
app.py
@@ -385,6 +385,7 @@ def inject_default_checkpoint():
|
||||
@app.route('/set_default_checkpoint', methods=['POST'])
|
||||
def set_default_checkpoint():
|
||||
session['default_checkpoint'] = request.form.get('checkpoint_path', '')
|
||||
session.modified = True
|
||||
return {'status': 'ok'}
|
||||
|
||||
|
||||
@@ -401,6 +402,27 @@ def api_status_comfyui():
|
||||
return {'status': 'error'}
|
||||
|
||||
|
||||
@app.route('/api/comfyui/loaded_checkpoint')
|
||||
def api_comfyui_loaded_checkpoint():
|
||||
"""Return the checkpoint name from the most recently completed ComfyUI job."""
|
||||
url = app.config.get('COMFYUI_URL', 'http://127.0.0.1:8188')
|
||||
try:
|
||||
resp = requests.get(f'{url}/history', timeout=3)
|
||||
if not resp.ok:
|
||||
return {'checkpoint': None}
|
||||
history = resp.json()
|
||||
if not history:
|
||||
return {'checkpoint': None}
|
||||
# Sort by timestamp descending, take the most recent job
|
||||
latest = max(history.values(), key=lambda j: j.get('status', {}).get('status_str', ''))
|
||||
# Node "4" is the checkpoint loader in the workflow
|
||||
nodes = latest.get('prompt', [None, None, {}])[2]
|
||||
ckpt_name = nodes.get('4', {}).get('inputs', {}).get('ckpt_name')
|
||||
return {'checkpoint': ckpt_name}
|
||||
except Exception:
|
||||
return {'checkpoint': None}
|
||||
|
||||
|
||||
@app.route('/api/status/mcp')
|
||||
def api_status_mcp():
|
||||
"""Return whether the danbooru-mcp Docker container is running."""
|
||||
@@ -417,80 +439,39 @@ def api_status_mcp():
|
||||
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
||||
|
||||
def get_available_loras():
|
||||
loras = []
|
||||
if os.path.exists(app.config['LORA_DIR']):
|
||||
for f in os.listdir(app.config['LORA_DIR']):
|
||||
if f.endswith('.safetensors'):
|
||||
# Using the format seen in character JSONs
|
||||
loras.append(f"Illustrious/Looks/{f}")
|
||||
return sorted(loras)
|
||||
_LORA_DEFAULTS = {
|
||||
'characters': '/ImageModels/lora/Illustrious/Looks',
|
||||
'outfits': '/ImageModels/lora/Illustrious/Clothing',
|
||||
'actions': '/ImageModels/lora/Illustrious/Poses',
|
||||
'styles': '/ImageModels/lora/Illustrious/Styles',
|
||||
'scenes': '/ImageModels/lora/Illustrious/Backgrounds',
|
||||
'detailers': '/ImageModels/lora/Illustrious/Detailers',
|
||||
}
|
||||
|
||||
def get_available_clothing_loras():
|
||||
"""Get LoRAs from the Clothing directory for outfit LoRAs."""
|
||||
clothing_lora_dir = '/ImageModels/lora/Illustrious/Clothing/'
|
||||
loras = []
|
||||
if os.path.exists(clothing_lora_dir):
|
||||
for f in os.listdir(clothing_lora_dir):
|
||||
if f.endswith('.safetensors'):
|
||||
loras.append(f"Illustrious/Clothing/{f}")
|
||||
return sorted(loras)
|
||||
|
||||
def get_available_action_loras():
|
||||
"""Get LoRAs from the Poses directory for action LoRAs."""
|
||||
poses_lora_dir = '/ImageModels/lora/Illustrious/Poses/'
|
||||
loras = []
|
||||
if os.path.exists(poses_lora_dir):
|
||||
for f in os.listdir(poses_lora_dir):
|
||||
if f.endswith('.safetensors'):
|
||||
loras.append(f"Illustrious/Poses/{f}")
|
||||
return sorted(loras)
|
||||
|
||||
def get_available_style_loras():
|
||||
"""Get LoRAs from the Styles directory for style LoRAs."""
|
||||
styles_lora_dir = '/ImageModels/lora/Illustrious/Styles/'
|
||||
loras = []
|
||||
if os.path.exists(styles_lora_dir):
|
||||
for f in os.listdir(styles_lora_dir):
|
||||
if f.endswith('.safetensors'):
|
||||
loras.append(f"Illustrious/Styles/{f}")
|
||||
return sorted(loras)
|
||||
|
||||
def get_available_detailer_loras():
|
||||
"""Get LoRAs from the Detailers directory for detailer LoRAs."""
|
||||
detailers_lora_dir = '/ImageModels/lora/Illustrious/Detailers/'
|
||||
loras = []
|
||||
if os.path.exists(detailers_lora_dir):
|
||||
for f in os.listdir(detailers_lora_dir):
|
||||
if f.endswith('.safetensors'):
|
||||
loras.append(f"Illustrious/Detailers/{f}")
|
||||
return sorted(loras)
|
||||
|
||||
def get_available_scene_loras():
|
||||
"""Get LoRAs from the Backgrounds directory for scene LoRAs."""
|
||||
backgrounds_lora_dir = '/ImageModels/lora/Illustrious/Backgrounds/'
|
||||
loras = []
|
||||
if os.path.exists(backgrounds_lora_dir):
|
||||
for f in os.listdir(backgrounds_lora_dir):
|
||||
if f.endswith('.safetensors'):
|
||||
loras.append(f"Illustrious/Backgrounds/{f}")
|
||||
return sorted(loras)
|
||||
def get_available_loras(category):
|
||||
"""Return sorted list of LoRA paths for the given category.
|
||||
category: one of 'characters','outfits','actions','styles','scenes','detailers'
|
||||
"""
|
||||
settings = Settings.query.first()
|
||||
lora_dir = (getattr(settings, f'lora_dir_{category}', None) if settings else None) or _LORA_DEFAULTS.get(category, '')
|
||||
if not lora_dir or not os.path.isdir(lora_dir):
|
||||
return []
|
||||
subfolder = os.path.basename(lora_dir.rstrip('/'))
|
||||
return sorted(f"Illustrious/{subfolder}/{f}" for f in os.listdir(lora_dir) if f.endswith('.safetensors'))
|
||||
|
||||
def get_available_checkpoints():
|
||||
settings = Settings.query.first()
|
||||
checkpoint_dirs_str = (settings.checkpoint_dirs if settings else None) or \
|
||||
'/ImageModels/Stable-diffusion/Illustrious,/ImageModels/Stable-diffusion/Noob'
|
||||
checkpoints = []
|
||||
|
||||
# Scan Illustrious
|
||||
if os.path.exists(app.config['ILLUSTRIOUS_MODELS_DIR']):
|
||||
for f in os.listdir(app.config['ILLUSTRIOUS_MODELS_DIR']):
|
||||
for ckpt_dir in checkpoint_dirs_str.split(','):
|
||||
ckpt_dir = ckpt_dir.strip()
|
||||
if not ckpt_dir or not os.path.isdir(ckpt_dir):
|
||||
continue
|
||||
prefix = os.path.basename(ckpt_dir.rstrip('/'))
|
||||
for f in os.listdir(ckpt_dir):
|
||||
if f.endswith('.safetensors') or f.endswith('.ckpt'):
|
||||
checkpoints.append(f"Illustrious/{f}")
|
||||
|
||||
# Scan Noob
|
||||
if os.path.exists(app.config['NOOB_MODELS_DIR']):
|
||||
for f in os.listdir(app.config['NOOB_MODELS_DIR']):
|
||||
if f.endswith('.safetensors') or f.endswith('.ckpt'):
|
||||
checkpoints.append(f"Noob/{f}")
|
||||
|
||||
checkpoints.append(f"{prefix}/{f}")
|
||||
return sorted(checkpoints)
|
||||
|
||||
def allowed_file(filename):
|
||||
@@ -1484,10 +1465,17 @@ def settings():
|
||||
settings.openrouter_model = request.form.get('model')
|
||||
settings.local_base_url = request.form.get('local_base_url')
|
||||
settings.local_model = request.form.get('local_model')
|
||||
settings.lora_dir_characters = request.form.get('lora_dir_characters') or settings.lora_dir_characters
|
||||
settings.lora_dir_outfits = request.form.get('lora_dir_outfits') or settings.lora_dir_outfits
|
||||
settings.lora_dir_actions = request.form.get('lora_dir_actions') or settings.lora_dir_actions
|
||||
settings.lora_dir_styles = request.form.get('lora_dir_styles') or settings.lora_dir_styles
|
||||
settings.lora_dir_scenes = request.form.get('lora_dir_scenes') or settings.lora_dir_scenes
|
||||
settings.lora_dir_detailers = request.form.get('lora_dir_detailers') or settings.lora_dir_detailers
|
||||
settings.checkpoint_dirs = request.form.get('checkpoint_dirs') or settings.checkpoint_dirs
|
||||
db.session.commit()
|
||||
flash('Settings updated successfully!')
|
||||
return redirect(url_for('settings'))
|
||||
|
||||
|
||||
return render_template('settings.html', settings=settings)
|
||||
|
||||
@app.route('/')
|
||||
@@ -1823,7 +1811,7 @@ def create_character():
|
||||
@app.route('/character/<path:slug>/edit', methods=['GET', 'POST'])
|
||||
def edit_character(slug):
|
||||
character = Character.query.filter_by(slug=slug).first_or_404()
|
||||
loras = get_available_loras()
|
||||
loras = get_available_loras('characters')
|
||||
char_looks = Look.query.filter_by(character_id=character.character_id).order_by(Look.name).all()
|
||||
|
||||
if request.method == 'POST':
|
||||
@@ -2080,15 +2068,13 @@ def upload_image(slug):
|
||||
@app.route('/character/<path:slug>/replace_cover_from_preview', methods=['POST'])
|
||||
def replace_cover_from_preview(slug):
|
||||
character = Character.query.filter_by(slug=slug).first_or_404()
|
||||
preview_path = session.get(f'preview_{slug}')
|
||||
|
||||
if preview_path:
|
||||
preview_path = request.form.get('preview_path')
|
||||
if preview_path and os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], preview_path)):
|
||||
character.image_path = preview_path
|
||||
db.session.commit()
|
||||
flash('Cover image updated from preview!')
|
||||
flash('Cover image updated!')
|
||||
else:
|
||||
flash('No preview image available', 'error')
|
||||
|
||||
flash('No valid preview image selected.', 'error')
|
||||
return redirect(url_for('detail', slug=slug))
|
||||
|
||||
def _log_workflow_prompts(label, workflow):
|
||||
@@ -2278,19 +2264,6 @@ def _get_default_checkpoint():
|
||||
return None, None
|
||||
return ckpt.checkpoint_path, ckpt.data or {}
|
||||
|
||||
def _queue_generation(character, action='preview', selected_fields=None, client_id=None):
|
||||
# 1. Load workflow
|
||||
with open('comfy_workflow.json', 'r') as f:
|
||||
workflow = json.load(f)
|
||||
|
||||
# 2. Build prompts with active outfit
|
||||
prompts = build_prompt(character.data, selected_fields, character.default_fields, character.active_outfit)
|
||||
|
||||
# 3. Prepare workflow
|
||||
ckpt_path, ckpt_data = _get_default_checkpoint()
|
||||
workflow = _prepare_workflow(workflow, character, prompts, checkpoint=ckpt_path, checkpoint_data=ckpt_data)
|
||||
|
||||
return queue_prompt(workflow, client_id=client_id)
|
||||
@app.route('/get_missing_characters')
|
||||
def get_missing_characters():
|
||||
missing = Character.query.filter((Character.image_path == None) | (Character.image_path == '')).all()
|
||||
@@ -2345,7 +2318,8 @@ def generate_image(slug):
|
||||
|
||||
# Save preferences
|
||||
session[f'prefs_{slug}'] = selected_fields
|
||||
|
||||
session.modified = True
|
||||
|
||||
# Build workflow
|
||||
with open('comfy_workflow.json', 'r') as f:
|
||||
workflow = json.load(f)
|
||||
@@ -2353,6 +2327,7 @@ def generate_image(slug):
|
||||
ckpt_path, ckpt_data = _get_default_checkpoint()
|
||||
workflow = _prepare_workflow(workflow, character, prompts, checkpoint=ckpt_path, checkpoint_data=ckpt_data)
|
||||
|
||||
label = f"{character.name} – {action}"
|
||||
job = _enqueue_job(label, workflow, _make_finalize('characters', slug, Character, action))
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return {'status': 'queued', 'job_id': job['id']}
|
||||
@@ -2428,7 +2403,9 @@ def rescan_outfits():
|
||||
|
||||
@app.route('/outfits/bulk_create', methods=['POST'])
|
||||
def bulk_create_outfits_from_loras():
|
||||
clothing_lora_dir = '/ImageModels/lora/Illustrious/Clothing/'
|
||||
_s = Settings.query.first()
|
||||
clothing_lora_dir = ((_s.lora_dir_outfits if _s else None) or '/ImageModels/lora/Illustrious/Clothing').rstrip('/')
|
||||
_lora_subfolder = os.path.basename(clothing_lora_dir)
|
||||
if not os.path.exists(clothing_lora_dir):
|
||||
flash('Clothing LoRA directory not found.', 'error')
|
||||
return redirect(url_for('outfits_index'))
|
||||
@@ -2489,7 +2466,7 @@ def bulk_create_outfits_from_loras():
|
||||
|
||||
if 'lora' not in outfit_data:
|
||||
outfit_data['lora'] = {}
|
||||
outfit_data['lora']['lora_name'] = f"Illustrious/Clothing/{filename}"
|
||||
outfit_data['lora']['lora_name'] = f"Illustrious/{_lora_subfolder}/{filename}"
|
||||
if not outfit_data['lora'].get('lora_triggers'):
|
||||
outfit_data['lora']['lora_triggers'] = name_base
|
||||
if outfit_data['lora'].get('lora_weight') is None:
|
||||
@@ -2548,7 +2525,7 @@ def outfit_detail(slug):
|
||||
@app.route('/outfit/<path:slug>/edit', methods=['GET', 'POST'])
|
||||
def edit_outfit(slug):
|
||||
outfit = Outfit.query.filter_by(slug=slug).first_or_404()
|
||||
loras = get_available_clothing_loras() # Use clothing LoRAs for outfits
|
||||
loras = get_available_loras('outfits') # Use clothing LoRAs for outfits
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
@@ -2667,7 +2644,8 @@ def generate_outfit_image(slug):
|
||||
# Save preferences
|
||||
session[f'prefs_outfit_{slug}'] = selected_fields
|
||||
session[f'char_outfit_{slug}'] = character_slug
|
||||
|
||||
session.modified = True
|
||||
|
||||
# Build combined data for prompt building
|
||||
if character:
|
||||
# Combine character identity/defaults with outfit wardrobe
|
||||
@@ -2743,15 +2721,13 @@ def generate_outfit_image(slug):
|
||||
@app.route('/outfit/<path:slug>/replace_cover_from_preview', methods=['POST'])
|
||||
def replace_outfit_cover_from_preview(slug):
|
||||
outfit = Outfit.query.filter_by(slug=slug).first_or_404()
|
||||
preview_path = session.get(f'preview_outfit_{slug}')
|
||||
|
||||
if preview_path:
|
||||
preview_path = request.form.get('preview_path')
|
||||
if preview_path and os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], preview_path)):
|
||||
outfit.image_path = preview_path
|
||||
db.session.commit()
|
||||
flash('Cover image updated from preview!')
|
||||
flash('Cover image updated!')
|
||||
else:
|
||||
flash('No preview image available', 'error')
|
||||
|
||||
flash('No valid preview image selected.', 'error')
|
||||
return redirect(url_for('outfit_detail', slug=slug))
|
||||
|
||||
@app.route('/outfit/create', methods=['GET', 'POST'])
|
||||
@@ -2988,7 +2964,7 @@ def action_detail(slug):
|
||||
@app.route('/action/<path:slug>/edit', methods=['GET', 'POST'])
|
||||
def edit_action(slug):
|
||||
action = Action.query.filter_by(slug=slug).first_or_404()
|
||||
loras = get_available_action_loras()
|
||||
loras = get_available_loras('actions')
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
@@ -3091,7 +3067,7 @@ def generate_action_image(slug):
|
||||
|
||||
try:
|
||||
# Get action type
|
||||
action_type = request.form.get('action', 'preview')
|
||||
action = request.form.get('action', 'preview')
|
||||
|
||||
# Get selected fields
|
||||
selected_fields = request.form.getlist('include_field')
|
||||
@@ -3107,7 +3083,8 @@ def generate_action_image(slug):
|
||||
# Save preferences
|
||||
session[f'char_action_{slug}'] = character_slug
|
||||
session[f'prefs_action_{slug}'] = selected_fields
|
||||
|
||||
session.modified = True
|
||||
|
||||
# Build combined data for prompt building
|
||||
if character:
|
||||
# Combine character identity/wardrobe with action details
|
||||
@@ -3245,8 +3222,8 @@ def generate_action_image(slug):
|
||||
workflow = _prepare_workflow(workflow, character, prompts, action=action_obj, checkpoint=ckpt_path, checkpoint_data=ckpt_data)
|
||||
|
||||
char_label = character.name if character else 'no character'
|
||||
label = f"Action: {action_obj.name} ({char_label}) – {action_type}"
|
||||
job = _enqueue_job(label, workflow, _make_finalize('actions', slug, Action, action_type))
|
||||
label = f"Action: {action_obj.name} ({char_label}) – {action}"
|
||||
job = _enqueue_job(label, workflow, _make_finalize('actions', slug, Action, action))
|
||||
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return {'status': 'queued', 'job_id': job['id']}
|
||||
@@ -3263,15 +3240,13 @@ def generate_action_image(slug):
|
||||
@app.route('/action/<path:slug>/replace_cover_from_preview', methods=['POST'])
|
||||
def replace_action_cover_from_preview(slug):
|
||||
action = Action.query.filter_by(slug=slug).first_or_404()
|
||||
preview_path = session.get(f'preview_action_{slug}')
|
||||
|
||||
if preview_path:
|
||||
preview_path = request.form.get('preview_path')
|
||||
if preview_path and os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], preview_path)):
|
||||
action.image_path = preview_path
|
||||
db.session.commit()
|
||||
flash('Cover image updated from preview!')
|
||||
flash('Cover image updated!')
|
||||
else:
|
||||
flash('No preview image available', 'error')
|
||||
|
||||
flash('No valid preview image selected.', 'error')
|
||||
return redirect(url_for('action_detail', slug=slug))
|
||||
|
||||
@app.route('/action/<path:slug>/save_defaults', methods=['POST'])
|
||||
@@ -3285,7 +3260,9 @@ def save_action_defaults(slug):
|
||||
|
||||
@app.route('/actions/bulk_create', methods=['POST'])
|
||||
def bulk_create_actions_from_loras():
|
||||
actions_lora_dir = '/ImageModels/lora/Illustrious/Poses/'
|
||||
_s = Settings.query.first()
|
||||
actions_lora_dir = ((_s.lora_dir_actions if _s else None) or '/ImageModels/lora/Illustrious/Poses').rstrip('/')
|
||||
_lora_subfolder = os.path.basename(actions_lora_dir)
|
||||
if not os.path.exists(actions_lora_dir):
|
||||
flash('Actions LoRA directory not found.', 'error')
|
||||
return redirect(url_for('actions_index'))
|
||||
@@ -3348,7 +3325,7 @@ def bulk_create_actions_from_loras():
|
||||
|
||||
# Update lora dict safely
|
||||
if 'lora' not in action_data: action_data['lora'] = {}
|
||||
action_data['lora']['lora_name'] = f"Illustrious/Poses/{filename}"
|
||||
action_data['lora']['lora_name'] = f"Illustrious/{_lora_subfolder}/{filename}"
|
||||
|
||||
# Fallbacks if LLM failed to extract metadata
|
||||
if not action_data['lora'].get('lora_triggers'):
|
||||
@@ -3554,7 +3531,7 @@ def style_detail(slug):
|
||||
@app.route('/style/<path:slug>/edit', methods=['GET', 'POST'])
|
||||
def edit_style(slug):
|
||||
style = Style.query.filter_by(slug=slug).first_or_404()
|
||||
loras = get_available_style_loras()
|
||||
loras = get_available_loras('styles')
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
@@ -3718,7 +3695,8 @@ def generate_style_image(slug):
|
||||
# Save preferences
|
||||
session[f'char_style_{slug}'] = character_slug
|
||||
session[f'prefs_style_{slug}'] = selected_fields
|
||||
|
||||
session.modified = True
|
||||
|
||||
# Build workflow using helper (returns workflow dict, not prompt_response)
|
||||
workflow = _build_style_workflow(style_obj, character, selected_fields)
|
||||
|
||||
@@ -3750,15 +3728,13 @@ def save_style_defaults(slug):
|
||||
@app.route('/style/<path:slug>/replace_cover_from_preview', methods=['POST'])
|
||||
def replace_style_cover_from_preview(slug):
|
||||
style = Style.query.filter_by(slug=slug).first_or_404()
|
||||
preview_path = session.get(f'preview_style_{slug}')
|
||||
|
||||
if preview_path:
|
||||
preview_path = request.form.get('preview_path')
|
||||
if preview_path and os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], preview_path)):
|
||||
style.image_path = preview_path
|
||||
db.session.commit()
|
||||
flash('Cover image updated from preview!')
|
||||
flash('Cover image updated!')
|
||||
else:
|
||||
flash('No preview image available', 'error')
|
||||
|
||||
flash('No valid preview image selected.', 'error')
|
||||
return redirect(url_for('style_detail', slug=slug))
|
||||
|
||||
@app.route('/get_missing_styles')
|
||||
@@ -3819,7 +3795,9 @@ def generate_missing_styles():
|
||||
|
||||
@app.route('/styles/bulk_create', methods=['POST'])
|
||||
def bulk_create_styles_from_loras():
|
||||
styles_lora_dir = '/ImageModels/lora/Illustrious/Styles/'
|
||||
_s = Settings.query.first()
|
||||
styles_lora_dir = ((_s.lora_dir_styles if _s else None) or '/ImageModels/lora/Illustrious/Styles').rstrip('/')
|
||||
_lora_subfolder = os.path.basename(styles_lora_dir)
|
||||
if not os.path.exists(styles_lora_dir):
|
||||
flash('Styles LoRA directory not found.', 'error')
|
||||
return redirect(url_for('styles_index'))
|
||||
@@ -3877,7 +3855,7 @@ def bulk_create_styles_from_loras():
|
||||
style_data['style_name'] = style_name
|
||||
|
||||
if 'lora' not in style_data: style_data['lora'] = {}
|
||||
style_data['lora']['lora_name'] = f"Illustrious/Styles/{filename}"
|
||||
style_data['lora']['lora_name'] = f"Illustrious/{_lora_subfolder}/{filename}"
|
||||
|
||||
if not style_data['lora'].get('lora_triggers'):
|
||||
style_data['lora']['lora_triggers'] = name_base
|
||||
@@ -4059,7 +4037,7 @@ def scene_detail(slug):
|
||||
@app.route('/scene/<path:slug>/edit', methods=['GET', 'POST'])
|
||||
def edit_scene(slug):
|
||||
scene = Scene.query.filter_by(slug=slug).first_or_404()
|
||||
loras = get_available_scene_loras()
|
||||
loras = get_available_loras('scenes')
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
@@ -4253,7 +4231,8 @@ def generate_scene_image(slug):
|
||||
# Save preferences
|
||||
session[f'char_scene_{slug}'] = character_slug
|
||||
session[f'prefs_scene_{slug}'] = selected_fields
|
||||
|
||||
session.modified = True
|
||||
|
||||
# Build workflow using helper
|
||||
workflow = _queue_scene_generation(scene_obj, character, selected_fields)
|
||||
|
||||
@@ -4285,20 +4264,20 @@ def save_scene_defaults(slug):
|
||||
@app.route('/scene/<path:slug>/replace_cover_from_preview', methods=['POST'])
|
||||
def replace_scene_cover_from_preview(slug):
|
||||
scene = Scene.query.filter_by(slug=slug).first_or_404()
|
||||
preview_path = session.get(f'preview_scene_{slug}')
|
||||
|
||||
if preview_path:
|
||||
preview_path = request.form.get('preview_path')
|
||||
if preview_path and os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], preview_path)):
|
||||
scene.image_path = preview_path
|
||||
db.session.commit()
|
||||
flash('Cover image updated from preview!')
|
||||
flash('Cover image updated!')
|
||||
else:
|
||||
flash('No preview image available', 'error')
|
||||
|
||||
flash('No valid preview image selected.', 'error')
|
||||
return redirect(url_for('scene_detail', slug=slug))
|
||||
|
||||
@app.route('/scenes/bulk_create', methods=['POST'])
|
||||
def bulk_create_scenes_from_loras():
|
||||
backgrounds_lora_dir = '/ImageModels/lora/Illustrious/Backgrounds/'
|
||||
_s = Settings.query.first()
|
||||
backgrounds_lora_dir = ((_s.lora_dir_scenes if _s else None) or '/ImageModels/lora/Illustrious/Backgrounds').rstrip('/')
|
||||
_lora_subfolder = os.path.basename(backgrounds_lora_dir)
|
||||
if not os.path.exists(backgrounds_lora_dir):
|
||||
flash('Backgrounds LoRA directory not found.', 'error')
|
||||
return redirect(url_for('scenes_index'))
|
||||
@@ -4360,7 +4339,7 @@ def bulk_create_scenes_from_loras():
|
||||
scene_data['scene_name'] = scene_name
|
||||
|
||||
if 'lora' not in scene_data: scene_data['lora'] = {}
|
||||
scene_data['lora']['lora_name'] = f"Illustrious/Backgrounds/{filename}"
|
||||
scene_data['lora']['lora_name'] = f"Illustrious/{_lora_subfolder}/{filename}"
|
||||
|
||||
if not scene_data['lora'].get('lora_triggers'):
|
||||
scene_data['lora']['lora_triggers'] = name_base
|
||||
@@ -4554,7 +4533,7 @@ def detailer_detail(slug):
|
||||
@app.route('/detailer/<path:slug>/edit', methods=['GET', 'POST'])
|
||||
def edit_detailer(slug):
|
||||
detailer = Detailer.query.filter_by(slug=slug).first_or_404()
|
||||
loras = get_available_detailer_loras()
|
||||
loras = get_available_loras('detailers')
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
@@ -4712,8 +4691,8 @@ def generate_detailer_image(slug):
|
||||
|
||||
try:
|
||||
# Get action type
|
||||
action_type = request.form.get('action', 'preview')
|
||||
|
||||
action = request.form.get('action', 'preview')
|
||||
|
||||
# Get selected fields
|
||||
selected_fields = request.form.getlist('include_field')
|
||||
|
||||
@@ -4737,13 +4716,14 @@ def generate_detailer_image(slug):
|
||||
session[f'extra_pos_detailer_{slug}'] = extra_positive
|
||||
session[f'extra_neg_detailer_{slug}'] = extra_negative
|
||||
session[f'prefs_detailer_{slug}'] = selected_fields
|
||||
session.modified = True
|
||||
|
||||
# Build workflow using helper
|
||||
workflow = _queue_detailer_generation(detailer_obj, character, selected_fields, action=action_obj, extra_positive=extra_positive, extra_negative=extra_negative)
|
||||
|
||||
char_label = character.name if character else 'no character'
|
||||
label = f"Detailer: {detailer_obj.name} ({char_label}) – {action_type}"
|
||||
job = _enqueue_job(label, workflow, _make_finalize('detailers', slug, Detailer, action_type))
|
||||
label = f"Detailer: {detailer_obj.name} ({char_label}) – {action}"
|
||||
job = _enqueue_job(label, workflow, _make_finalize('detailers', slug, Detailer, action))
|
||||
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return {'status': 'queued', 'job_id': job['id']}
|
||||
@@ -4769,15 +4749,13 @@ def save_detailer_defaults(slug):
|
||||
@app.route('/detailer/<path:slug>/replace_cover_from_preview', methods=['POST'])
|
||||
def replace_detailer_cover_from_preview(slug):
|
||||
detailer = Detailer.query.filter_by(slug=slug).first_or_404()
|
||||
preview_path = session.get(f'preview_detailer_{slug}')
|
||||
|
||||
if preview_path:
|
||||
preview_path = request.form.get('preview_path')
|
||||
if preview_path and os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], preview_path)):
|
||||
detailer.image_path = preview_path
|
||||
db.session.commit()
|
||||
flash('Cover image updated from preview!')
|
||||
flash('Cover image updated!')
|
||||
else:
|
||||
flash('No preview image available', 'error')
|
||||
|
||||
flash('No valid preview image selected.', 'error')
|
||||
return redirect(url_for('detailer_detail', slug=slug))
|
||||
|
||||
@app.route('/detailer/<path:slug>/save_json', methods=['POST'])
|
||||
@@ -4798,7 +4776,9 @@ def save_detailer_json(slug):
|
||||
|
||||
@app.route('/detailers/bulk_create', methods=['POST'])
|
||||
def bulk_create_detailers_from_loras():
|
||||
detailers_lora_dir = '/ImageModels/lora/Illustrious/Detailers/'
|
||||
_s = Settings.query.first()
|
||||
detailers_lora_dir = ((_s.lora_dir_detailers if _s else None) or '/ImageModels/lora/Illustrious/Detailers').rstrip('/')
|
||||
_lora_subfolder = os.path.basename(detailers_lora_dir)
|
||||
if not os.path.exists(detailers_lora_dir):
|
||||
flash('Detailers LoRA directory not found.', 'error')
|
||||
return redirect(url_for('detailers_index'))
|
||||
@@ -4856,7 +4836,7 @@ def bulk_create_detailers_from_loras():
|
||||
detailer_data['detailer_name'] = detailer_name
|
||||
|
||||
if 'lora' not in detailer_data: detailer_data['lora'] = {}
|
||||
detailer_data['lora']['lora_name'] = f"Illustrious/Detailers/{filename}"
|
||||
detailer_data['lora']['lora_name'] = f"Illustrious/{_lora_subfolder}/{filename}"
|
||||
|
||||
if not detailer_data['lora'].get('lora_triggers'):
|
||||
detailer_data['lora']['lora_triggers'] = name_base
|
||||
@@ -5092,6 +5072,7 @@ def generate_checkpoint_image(slug):
|
||||
character_slug = character.slug
|
||||
|
||||
session[f'char_checkpoint_{slug}'] = character_slug
|
||||
session.modified = True
|
||||
workflow = _build_checkpoint_workflow(ckpt, character)
|
||||
|
||||
char_label = character.name if character else 'random'
|
||||
@@ -5102,7 +5083,7 @@ def generate_checkpoint_image(slug):
|
||||
return {'status': 'queued', 'job_id': job['id']}
|
||||
return redirect(url_for('checkpoint_detail', slug=slug))
|
||||
except Exception as e:
|
||||
print(f"Checkpoint generation error: {e}")
|
||||
print(f"Generation error: {e}")
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return {'error': str(e)}, 500
|
||||
flash(f"Error during generation: {str(e)}")
|
||||
@@ -5111,13 +5092,13 @@ def generate_checkpoint_image(slug):
|
||||
@app.route('/checkpoint/<path:slug>/replace_cover_from_preview', methods=['POST'])
|
||||
def replace_checkpoint_cover_from_preview(slug):
|
||||
ckpt = Checkpoint.query.filter_by(slug=slug).first_or_404()
|
||||
preview_path = session.get(f'preview_checkpoint_{slug}')
|
||||
if preview_path:
|
||||
preview_path = request.form.get('preview_path')
|
||||
if preview_path and os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], preview_path)):
|
||||
ckpt.image_path = preview_path
|
||||
db.session.commit()
|
||||
flash('Cover image updated from preview!')
|
||||
flash('Cover image updated!')
|
||||
else:
|
||||
flash('No preview image available', 'error')
|
||||
flash('No valid preview image selected.', 'error')
|
||||
return redirect(url_for('checkpoint_detail', slug=slug))
|
||||
|
||||
@app.route('/checkpoint/<path:slug>/save_json', methods=['POST'])
|
||||
@@ -5281,7 +5262,7 @@ def look_detail(slug):
|
||||
def edit_look(slug):
|
||||
look = Look.query.filter_by(slug=slug).first_or_404()
|
||||
characters = Character.query.order_by(Character.name).all()
|
||||
loras = get_available_loras()
|
||||
loras = get_available_loras('characters')
|
||||
|
||||
if request.method == 'POST':
|
||||
look.name = request.form.get('look_name', look.name)
|
||||
@@ -5363,6 +5344,7 @@ def generate_look_image(slug):
|
||||
|
||||
session[f'prefs_look_{slug}'] = selected_fields
|
||||
session[f'char_look_{slug}'] = character_slug
|
||||
session.modified = True
|
||||
|
||||
lora_triggers = look.data.get('lora', {}).get('lora_triggers', '')
|
||||
look_positive = look.data.get('positive', '')
|
||||
@@ -5412,7 +5394,7 @@ def generate_look_image(slug):
|
||||
return redirect(url_for('look_detail', slug=slug))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Look generation error: {e}")
|
||||
print(f"Generation error: {e}")
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return {'error': str(e)}, 500
|
||||
flash(f"Error during generation: {str(e)}")
|
||||
@@ -5421,13 +5403,13 @@ def generate_look_image(slug):
|
||||
@app.route('/look/<path:slug>/replace_cover_from_preview', methods=['POST'])
|
||||
def replace_look_cover_from_preview(slug):
|
||||
look = Look.query.filter_by(slug=slug).first_or_404()
|
||||
preview_path = session.get(f'preview_look_{slug}')
|
||||
if preview_path:
|
||||
preview_path = request.form.get('preview_path')
|
||||
if preview_path and os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], preview_path)):
|
||||
look.image_path = preview_path
|
||||
db.session.commit()
|
||||
flash('Cover image updated from preview!')
|
||||
flash('Cover image updated!')
|
||||
else:
|
||||
flash('No preview image available', 'error')
|
||||
flash('No valid preview image selected.', 'error')
|
||||
return redirect(url_for('look_detail', slug=slug))
|
||||
|
||||
@app.route('/look/<path:slug>/save_defaults', methods=['POST'])
|
||||
@@ -5458,7 +5440,7 @@ def save_look_json(slug):
|
||||
@app.route('/look/create', methods=['GET', 'POST'])
|
||||
def create_look():
|
||||
characters = Character.query.order_by(Character.name).all()
|
||||
loras = get_available_loras()
|
||||
loras = get_available_loras('characters')
|
||||
if request.method == 'POST':
|
||||
name = request.form.get('name', '').strip()
|
||||
look_id = re.sub(r'[^a-zA-Z0-9_]', '_', name.lower().replace(' ', '_'))
|
||||
@@ -5513,7 +5495,9 @@ def clear_all_look_covers():
|
||||
|
||||
@app.route('/looks/bulk_create', methods=['POST'])
|
||||
def bulk_create_looks_from_loras():
|
||||
lora_dir = app.config['LORA_DIR']
|
||||
_s = Settings.query.first()
|
||||
lora_dir = ((_s.lora_dir_characters if _s else None) or app.config['LORA_DIR']).rstrip('/')
|
||||
_lora_subfolder = os.path.basename(lora_dir)
|
||||
if not os.path.exists(lora_dir):
|
||||
flash('Looks LoRA directory not found.', 'error')
|
||||
return redirect(url_for('looks_index'))
|
||||
@@ -5574,7 +5558,7 @@ def bulk_create_looks_from_loras():
|
||||
|
||||
if 'lora' not in look_data:
|
||||
look_data['lora'] = {}
|
||||
look_data['lora']['lora_name'] = f"Illustrious/Looks/{filename}"
|
||||
look_data['lora']['lora_name'] = f"Illustrious/{_lora_subfolder}/{filename}"
|
||||
if not look_data['lora'].get('lora_triggers'):
|
||||
look_data['lora']['lora_triggers'] = name_base
|
||||
if look_data['lora'].get('lora_weight') is None:
|
||||
@@ -6386,7 +6370,14 @@ if __name__ == '__main__':
|
||||
columns_to_add = [
|
||||
('llm_provider', "VARCHAR(50) DEFAULT 'openrouter'"),
|
||||
('local_base_url', "VARCHAR(255)"),
|
||||
('local_model', "VARCHAR(100)")
|
||||
('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'"),
|
||||
]
|
||||
for col_name, col_type in columns_to_add:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user