Add extra prompts, endless generation, random character default, and small fixes
- Add extra positive/negative prompt textareas to all 9 detail pages with session persistence - Add Endless generation button to all detail pages (continuous preview generation until stopped) - Default character selector to "Random Character" on all secondary detail pages - Fix queue clear endpoint (remove spurious auth check) - Refactor app.py into routes/ and services/ modules - Update CLAUDE.md with new architecture documentation - Various data file updates and cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
592
routes/looks.py
Normal file
592
routes/looks.py
Normal file
@@ -0,0 +1,592 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import logging
|
||||
|
||||
from flask import render_template, request, redirect, url_for, flash, session, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
from models import db, Character, Look, Action, Checkpoint, Settings, Outfit
|
||||
from services.workflow import _prepare_workflow, _get_default_checkpoint
|
||||
from services.job_queue import _enqueue_job, _make_finalize
|
||||
from services.prompts import build_prompt, _resolve_character, _ensure_character_fields, _append_background, _dedup_tags
|
||||
from services.sync import sync_looks
|
||||
from services.file_io import get_available_loras, _count_look_assignments
|
||||
from services.llm import load_prompt, call_llm
|
||||
from utils import allowed_file
|
||||
|
||||
logger = logging.getLogger('gaze')
|
||||
|
||||
|
||||
def _ensure_look_lora_prefix(lora_name):
|
||||
"""Ensure look LoRA paths have the correct 'Illustrious/Looks/' prefix."""
|
||||
if not lora_name:
|
||||
return lora_name
|
||||
|
||||
if not lora_name.startswith('Illustrious/Looks/'):
|
||||
# Add the prefix if missing
|
||||
if lora_name.startswith('Illustrious/'):
|
||||
# Has Illustrious but wrong subfolder - replace
|
||||
parts = lora_name.split('/', 1)
|
||||
if len(parts) > 1:
|
||||
lora_name = 'Illustrious/Looks/' + parts[1]
|
||||
else:
|
||||
lora_name = 'Illustrious/Looks/' + lora_name
|
||||
else:
|
||||
# No prefix at all - add it
|
||||
lora_name = 'Illustrious/Looks/' + lora_name
|
||||
|
||||
return lora_name
|
||||
|
||||
|
||||
def _fix_look_lora_data(lora_data):
|
||||
"""Fix look LoRA data to ensure correct prefix."""
|
||||
if not lora_data:
|
||||
return lora_data
|
||||
|
||||
lora_name = lora_data.get('lora_name', '')
|
||||
if lora_name:
|
||||
lora_data = lora_data.copy() # Avoid mutating original
|
||||
lora_data['lora_name'] = _ensure_look_lora_prefix(lora_name)
|
||||
|
||||
return lora_data
|
||||
|
||||
|
||||
def register_routes(app):
|
||||
|
||||
@app.route('/looks')
|
||||
def looks_index():
|
||||
looks = Look.query.order_by(Look.name).all()
|
||||
look_assignments = _count_look_assignments()
|
||||
return render_template('looks/index.html', looks=looks, look_assignments=look_assignments)
|
||||
|
||||
@app.route('/looks/rescan', methods=['POST'])
|
||||
def rescan_looks():
|
||||
sync_looks()
|
||||
flash('Database synced with look files.')
|
||||
return redirect(url_for('looks_index'))
|
||||
|
||||
@app.route('/look/<path:slug>')
|
||||
def look_detail(slug):
|
||||
look = Look.query.filter_by(slug=slug).first_or_404()
|
||||
characters = Character.query.order_by(Character.name).all()
|
||||
|
||||
# Pre-select the linked characters if set (supports multi-character assignment)
|
||||
preferences = session.get(f'prefs_look_{slug}')
|
||||
preview_image = session.get(f'preview_look_{slug}')
|
||||
|
||||
# Get linked character IDs (new character_ids JSON field)
|
||||
linked_character_ids = look.character_ids or []
|
||||
# Fallback to legacy character_id if character_ids is empty
|
||||
if not linked_character_ids and look.character_id:
|
||||
linked_character_ids = [look.character_id]
|
||||
|
||||
# Session-selected character for preview (single selection for generation)
|
||||
selected_character = session.get(f'char_look_{slug}', linked_character_ids[0] if linked_character_ids else '')
|
||||
|
||||
# FIX: Add existing_previews scanning (matching other resource routes)
|
||||
upload_folder = app.config['UPLOAD_FOLDER']
|
||||
preview_dir = os.path.join(upload_folder, 'looks', slug)
|
||||
existing_previews = []
|
||||
if os.path.isdir(preview_dir):
|
||||
for f in os.listdir(preview_dir):
|
||||
if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
|
||||
existing_previews.append(f'looks/{slug}/{f}')
|
||||
existing_previews.sort()
|
||||
|
||||
extra_positive = session.get(f'extra_pos_look_{slug}', '')
|
||||
extra_negative = session.get(f'extra_neg_look_{slug}', '')
|
||||
|
||||
return render_template('looks/detail.html', look=look, characters=characters,
|
||||
preferences=preferences, preview_image=preview_image,
|
||||
selected_character=selected_character,
|
||||
linked_character_ids=linked_character_ids,
|
||||
existing_previews=existing_previews,
|
||||
extra_positive=extra_positive, extra_negative=extra_negative)
|
||||
|
||||
@app.route('/look/<path:slug>/edit', methods=['GET', 'POST'])
|
||||
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('characters')
|
||||
|
||||
if request.method == 'POST':
|
||||
look.name = request.form.get('look_name', look.name)
|
||||
|
||||
# Handle multiple character IDs from checkboxes
|
||||
character_ids = request.form.getlist('character_ids')
|
||||
look.character_ids = character_ids if character_ids else []
|
||||
|
||||
# Also update legacy character_id field for backward compatibility
|
||||
if character_ids:
|
||||
look.character_id = character_ids[0]
|
||||
else:
|
||||
look.character_id = None
|
||||
|
||||
new_data = look.data.copy()
|
||||
new_data['look_name'] = look.name
|
||||
new_data['character_id'] = look.character_id
|
||||
|
||||
new_data['positive'] = request.form.get('positive', '')
|
||||
new_data['negative'] = request.form.get('negative', '')
|
||||
|
||||
lora_name = request.form.get('lora_lora_name', '')
|
||||
lora_weight = float(request.form.get('lora_lora_weight', 1.0) or 1.0)
|
||||
lora_triggers = request.form.get('lora_lora_triggers', '')
|
||||
new_data['lora'] = {'lora_name': lora_name, 'lora_weight': lora_weight, 'lora_triggers': lora_triggers}
|
||||
for bound in ['lora_weight_min', 'lora_weight_max']:
|
||||
val_str = request.form.get(f'lora_{bound}', '').strip()
|
||||
if val_str:
|
||||
try:
|
||||
new_data['lora'][bound] = float(val_str)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
tags_raw = request.form.get('tags', '')
|
||||
new_data['tags'] = [t.strip() for t in tags_raw.split(',') if t.strip()]
|
||||
|
||||
look.data = new_data
|
||||
flag_modified(look, 'data')
|
||||
db.session.commit()
|
||||
|
||||
if look.filename:
|
||||
file_path = os.path.join(app.config['LOOKS_DIR'], look.filename)
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(new_data, f, indent=2)
|
||||
|
||||
flash(f'Look "{look.name}" updated!')
|
||||
return redirect(url_for('look_detail', slug=look.slug))
|
||||
|
||||
return render_template('looks/edit.html', look=look, characters=characters, loras=loras)
|
||||
|
||||
@app.route('/look/<path:slug>/upload', methods=['POST'])
|
||||
def upload_look_image(slug):
|
||||
look = Look.query.filter_by(slug=slug).first_or_404()
|
||||
if 'image' not in request.files:
|
||||
flash('No file selected')
|
||||
return redirect(url_for('look_detail', slug=slug))
|
||||
file = request.files['image']
|
||||
if file and allowed_file(file.filename):
|
||||
filename = secure_filename(file.filename)
|
||||
look_folder = os.path.join(app.config['UPLOAD_FOLDER'], f'looks/{slug}')
|
||||
os.makedirs(look_folder, exist_ok=True)
|
||||
file_path = os.path.join(look_folder, filename)
|
||||
file.save(file_path)
|
||||
look.image_path = f'looks/{slug}/{filename}'
|
||||
db.session.commit()
|
||||
return redirect(url_for('look_detail', slug=slug))
|
||||
|
||||
@app.route('/look/<path:slug>/generate', methods=['POST'])
|
||||
def generate_look_image(slug):
|
||||
look = Look.query.filter_by(slug=slug).first_or_404()
|
||||
|
||||
try:
|
||||
action = request.form.get('action', 'preview')
|
||||
selected_fields = request.form.getlist('include_field')
|
||||
|
||||
character_slug = request.form.get('character_slug', '')
|
||||
character = None
|
||||
|
||||
# Only load a character when the user explicitly selects one
|
||||
character = _resolve_character(character_slug)
|
||||
if character_slug == '__random__' and character:
|
||||
character_slug = character.slug
|
||||
elif character_slug and not character:
|
||||
# fallback: try matching by character_id
|
||||
character = Character.query.filter_by(character_id=character_slug).first()
|
||||
# No fallback to look.character_id — looks are self-contained
|
||||
|
||||
# Get additional prompts
|
||||
extra_positive = request.form.get('extra_positive', '').strip()
|
||||
extra_negative = request.form.get('extra_negative', '').strip()
|
||||
|
||||
session[f'prefs_look_{slug}'] = selected_fields
|
||||
session[f'char_look_{slug}'] = character_slug
|
||||
session[f'extra_pos_look_{slug}'] = extra_positive
|
||||
session[f'extra_neg_look_{slug}'] = extra_negative
|
||||
session.modified = True
|
||||
|
||||
lora_triggers = look.data.get('lora', {}).get('lora_triggers', '')
|
||||
look_positive = look.data.get('positive', '')
|
||||
|
||||
with open('comfy_workflow.json', 'r') as f:
|
||||
workflow = json.load(f)
|
||||
|
||||
if character:
|
||||
# Merge character identity with look LoRA and positive prompt
|
||||
combined_data = {
|
||||
'character_id': character.character_id,
|
||||
'identity': character.data.get('identity', {}),
|
||||
'defaults': character.data.get('defaults', {}),
|
||||
'wardrobe': character.data.get('wardrobe', {}).get(character.active_outfit or 'default',
|
||||
character.data.get('wardrobe', {}).get('default', {})),
|
||||
'styles': character.data.get('styles', {}),
|
||||
'lora': _fix_look_lora_data(look.data.get('lora', {})),
|
||||
'tags': look.data.get('tags', [])
|
||||
}
|
||||
_ensure_character_fields(character, selected_fields,
|
||||
include_wardrobe=False, include_defaults=True)
|
||||
prompts = build_prompt(combined_data, selected_fields, character.default_fields)
|
||||
# Append look-specific triggers and positive
|
||||
extra = ', '.join(filter(None, [lora_triggers, look_positive]))
|
||||
if extra:
|
||||
prompts['main'] = _dedup_tags(f"{prompts['main']}, {extra}" if prompts['main'] else extra)
|
||||
primary_color = character.data.get('styles', {}).get('primary_color', '')
|
||||
bg = f"{primary_color} simple background" if primary_color else "simple background"
|
||||
else:
|
||||
# Look is self-contained: build prompt from its own positive and triggers only
|
||||
main = _dedup_tags(', '.join(filter(None, ['(solo:1.2)', lora_triggers, look_positive])))
|
||||
prompts = {'main': main, 'face': '', 'hand': ''}
|
||||
bg = "simple background"
|
||||
|
||||
prompts['main'] = _dedup_tags(f"{prompts['main']}, {bg}" if prompts['main'] else bg)
|
||||
|
||||
if extra_positive:
|
||||
prompts["main"] = f"{prompts['main']}, {extra_positive}"
|
||||
|
||||
# Parse optional seed
|
||||
seed_val = request.form.get('seed', '').strip()
|
||||
fixed_seed = int(seed_val) if seed_val else None
|
||||
|
||||
ckpt_path, ckpt_data = _get_default_checkpoint()
|
||||
workflow = _prepare_workflow(workflow, character, prompts, custom_negative=extra_negative or None, checkpoint=ckpt_path,
|
||||
checkpoint_data=ckpt_data, look=look, fixed_seed=fixed_seed)
|
||||
|
||||
char_label = character.name if character else 'no character'
|
||||
label = f"Look: {look.name} ({char_label}) – {action}"
|
||||
job = _enqueue_job(label, workflow, _make_finalize('looks', slug, Look, action))
|
||||
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return {'status': 'queued', 'job_id': job['id']}
|
||||
return redirect(url_for('look_detail', slug=slug))
|
||||
|
||||
except Exception as 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)}")
|
||||
return redirect(url_for('look_detail', slug=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 = 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!')
|
||||
else:
|
||||
flash('No valid preview image selected.', 'error')
|
||||
return redirect(url_for('look_detail', slug=slug))
|
||||
|
||||
@app.route('/look/<path:slug>/save_defaults', methods=['POST'])
|
||||
def save_look_defaults(slug):
|
||||
look = Look.query.filter_by(slug=slug).first_or_404()
|
||||
look.default_fields = request.form.getlist('include_field')
|
||||
db.session.commit()
|
||||
flash('Default prompt selection saved!')
|
||||
return redirect(url_for('look_detail', slug=slug))
|
||||
|
||||
@app.route('/look/<path:slug>/generate_character', methods=['POST'])
|
||||
def generate_character_from_look(slug):
|
||||
"""Generate a character JSON using a look as the base."""
|
||||
look = Look.query.filter_by(slug=slug).first_or_404()
|
||||
|
||||
# Get or validate inputs
|
||||
character_name = request.form.get('character_name', look.name)
|
||||
use_llm = request.form.get('use_llm') == 'on'
|
||||
|
||||
# Auto-generate slug
|
||||
character_slug = re.sub(r'[^a-zA-Z0-9]+', '_', character_name.lower()).strip('_')
|
||||
character_slug = re.sub(r'[^a-zA-Z0-9_]', '', character_slug)
|
||||
|
||||
# Find available filename
|
||||
base_slug = character_slug
|
||||
counter = 1
|
||||
while os.path.exists(os.path.join(app.config['CHARACTERS_DIR'], f"{character_slug}.json")):
|
||||
character_slug = f"{base_slug}_{counter}"
|
||||
counter += 1
|
||||
|
||||
if use_llm:
|
||||
# Use LLM to generate character from look context
|
||||
system_prompt = load_prompt('character_system.txt')
|
||||
if not system_prompt:
|
||||
flash('Character system prompt file not found.', 'error')
|
||||
return redirect(url_for('look_detail', slug=slug))
|
||||
|
||||
prompt = f"""Generate a character based on this look description:
|
||||
|
||||
Look Name: {look.name}
|
||||
Positive Prompt: {look.data.get('positive', '')}
|
||||
Negative Prompt: {look.data.get('negative', '')}
|
||||
Tags: {', '.join(look.data.get('tags', []))}
|
||||
LoRA Triggers: {look.data.get('lora', {}).get('lora_triggers', '')}
|
||||
|
||||
Create a complete character JSON with identity, styles, and appropriate wardrobe fields.
|
||||
The character should match the visual style described in the look.
|
||||
|
||||
Character Name: {character_name}
|
||||
Character ID: {character_slug}"""
|
||||
|
||||
try:
|
||||
llm_response = call_llm(prompt, system_prompt)
|
||||
# Clean response (remove markdown if present)
|
||||
clean_json = llm_response.replace('```json', '').replace('```', '').strip()
|
||||
character_data = json.loads(clean_json)
|
||||
|
||||
# Enforce IDs
|
||||
character_data['character_id'] = character_slug
|
||||
character_data['character_name'] = character_name
|
||||
|
||||
# Ensure the character inherits the look's LoRA with correct path
|
||||
lora_data = _fix_look_lora_data(look.data.get('lora', {}).copy())
|
||||
character_data['lora'] = lora_data
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"LLM character generation error: {e}")
|
||||
flash(f'Failed to generate character with AI: {e}', 'error')
|
||||
return redirect(url_for('look_detail', slug=slug))
|
||||
else:
|
||||
# Create minimal character template
|
||||
lora_data = _fix_look_lora_data(look.data.get('lora', {}).copy())
|
||||
|
||||
character_data = {
|
||||
"character_id": character_slug,
|
||||
"character_name": character_name,
|
||||
"identity": {
|
||||
"base_specs": lora_data.get('lora_triggers', ''),
|
||||
"hair": "",
|
||||
"eyes": "",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"extra": ""
|
||||
},
|
||||
"defaults": {
|
||||
"expression": "",
|
||||
"pose": "",
|
||||
"scene": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"full_body": "",
|
||||
"headwear": "",
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"legwear": "",
|
||||
"footwear": "",
|
||||
"hands": "",
|
||||
"accessories": ""
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "",
|
||||
"primary_color": "",
|
||||
"secondary_color": "",
|
||||
"tertiary_color": ""
|
||||
},
|
||||
"lora": lora_data,
|
||||
"tags": look.data.get('tags', [])
|
||||
}
|
||||
|
||||
# Save character JSON
|
||||
char_path = os.path.join(app.config['CHARACTERS_DIR'], f"{character_slug}.json")
|
||||
try:
|
||||
with open(char_path, 'w') as f:
|
||||
json.dump(character_data, f, indent=2)
|
||||
except Exception as e:
|
||||
flash(f'Failed to save character file: {e}', 'error')
|
||||
return redirect(url_for('look_detail', slug=slug))
|
||||
|
||||
# Create DB entry
|
||||
character = Character(
|
||||
character_id=character_slug,
|
||||
slug=character_slug,
|
||||
name=character_name,
|
||||
data=character_data
|
||||
)
|
||||
db.session.add(character)
|
||||
db.session.commit()
|
||||
|
||||
# Link the look to this character
|
||||
look.character_id = character_slug
|
||||
db.session.commit()
|
||||
|
||||
flash(f'Character "{character_name}" created from look!', 'success')
|
||||
return redirect(url_for('detail', slug=character_slug))
|
||||
|
||||
@app.route('/look/<path:slug>/save_json', methods=['POST'])
|
||||
def save_look_json(slug):
|
||||
look = Look.query.filter_by(slug=slug).first_or_404()
|
||||
try:
|
||||
new_data = json.loads(request.form.get('json_data', ''))
|
||||
except (ValueError, TypeError) as e:
|
||||
return {'success': False, 'error': f'Invalid JSON: {e}'}, 400
|
||||
look.data = new_data
|
||||
look.character_id = new_data.get('character_id', look.character_id)
|
||||
flag_modified(look, 'data')
|
||||
db.session.commit()
|
||||
if look.filename:
|
||||
file_path = os.path.join(app.config['LOOKS_DIR'], look.filename)
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(new_data, f, indent=2)
|
||||
return {'success': True}
|
||||
|
||||
@app.route('/look/create', methods=['GET', 'POST'])
|
||||
def create_look():
|
||||
characters = Character.query.order_by(Character.name).all()
|
||||
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(' ', '_'))
|
||||
filename = f'{look_id}.json'
|
||||
file_path = os.path.join(app.config['LOOKS_DIR'], filename)
|
||||
|
||||
character_id = request.form.get('character_id', '') or None
|
||||
lora_name = request.form.get('lora_lora_name', '')
|
||||
lora_weight = float(request.form.get('lora_lora_weight', 1.0) or 1.0)
|
||||
lora_triggers = request.form.get('lora_lora_triggers', '')
|
||||
positive = request.form.get('positive', '')
|
||||
negative = request.form.get('negative', '')
|
||||
tags = [t.strip() for t in request.form.get('tags', '').split(',') if t.strip()]
|
||||
|
||||
data = {
|
||||
'look_id': look_id,
|
||||
'look_name': name,
|
||||
'character_id': character_id,
|
||||
'positive': positive,
|
||||
'negative': negative,
|
||||
'lora': {'lora_name': lora_name, 'lora_weight': lora_weight, 'lora_triggers': lora_triggers},
|
||||
'tags': tags
|
||||
}
|
||||
|
||||
os.makedirs(app.config['LOOKS_DIR'], exist_ok=True)
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
slug = re.sub(r'[^a-zA-Z0-9_]', '', look_id)
|
||||
new_look = Look(look_id=look_id, slug=slug, filename=filename, name=name,
|
||||
character_id=character_id, data=data)
|
||||
db.session.add(new_look)
|
||||
db.session.commit()
|
||||
|
||||
flash(f'Look "{name}" created!')
|
||||
return redirect(url_for('look_detail', slug=slug))
|
||||
|
||||
return render_template('looks/create.html', characters=characters, loras=loras)
|
||||
|
||||
@app.route('/get_missing_looks')
|
||||
def get_missing_looks():
|
||||
missing = Look.query.filter((Look.image_path == None) | (Look.image_path == '')).order_by(Look.name).all()
|
||||
return {'missing': [{'slug': l.slug, 'name': l.name} for l in missing]}
|
||||
|
||||
@app.route('/clear_all_look_covers', methods=['POST'])
|
||||
def clear_all_look_covers():
|
||||
looks = Look.query.all()
|
||||
for look in looks:
|
||||
look.image_path = None
|
||||
db.session.commit()
|
||||
return {'success': True}
|
||||
|
||||
@app.route('/looks/bulk_create', methods=['POST'])
|
||||
def bulk_create_looks_from_loras():
|
||||
_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'))
|
||||
|
||||
overwrite = request.form.get('overwrite') == 'true'
|
||||
created_count = 0
|
||||
skipped_count = 0
|
||||
overwritten_count = 0
|
||||
|
||||
system_prompt = load_prompt('look_system.txt')
|
||||
if not system_prompt:
|
||||
flash('Look system prompt file not found.', 'error')
|
||||
return redirect(url_for('looks_index'))
|
||||
|
||||
for filename in os.listdir(lora_dir):
|
||||
if not filename.endswith('.safetensors'):
|
||||
continue
|
||||
|
||||
name_base = filename.rsplit('.', 1)[0]
|
||||
look_id = re.sub(r'[^a-zA-Z0-9_]', '_', name_base.lower())
|
||||
look_name = re.sub(r'[^a-zA-Z0-9]+', ' ', name_base).title()
|
||||
|
||||
json_filename = f"{look_id}.json"
|
||||
json_path = os.path.join(app.config['LOOKS_DIR'], json_filename)
|
||||
|
||||
is_existing = os.path.exists(json_path)
|
||||
if is_existing and not overwrite:
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
html_filename = f"{name_base}.html"
|
||||
html_path = os.path.join(lora_dir, html_filename)
|
||||
html_content = ""
|
||||
if os.path.exists(html_path):
|
||||
try:
|
||||
with open(html_path, 'r', encoding='utf-8', errors='ignore') as hf:
|
||||
html_raw = hf.read()
|
||||
clean_html = re.sub(r'<script[^>]*>.*?</script>', '', html_raw, flags=re.DOTALL)
|
||||
clean_html = re.sub(r'<style[^>]*>.*?</style>', '', clean_html, flags=re.DOTALL)
|
||||
clean_html = re.sub(r'<img[^>]*>', '', clean_html)
|
||||
clean_html = re.sub(r'<[^>]+>', ' ', clean_html)
|
||||
html_content = ' '.join(clean_html.split())
|
||||
except Exception as e:
|
||||
print(f"Error reading HTML {html_filename}: {e}")
|
||||
|
||||
try:
|
||||
print(f"Asking LLM to describe look: {look_name}")
|
||||
prompt = f"Create a look profile for a character appearance LoRA based on the filename: '{filename}'"
|
||||
if html_content:
|
||||
prompt += f"\n\nHere is descriptive text extracted from an associated HTML file:\n###\n{html_content[:3000]}\n###"
|
||||
|
||||
llm_response = call_llm(prompt, system_prompt)
|
||||
clean_json = llm_response.replace('```json', '').replace('```', '').strip()
|
||||
look_data = json.loads(clean_json)
|
||||
|
||||
look_data['look_id'] = look_id
|
||||
look_data['look_name'] = look_name
|
||||
|
||||
if 'lora' not in look_data:
|
||||
look_data['lora'] = {}
|
||||
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:
|
||||
look_data['lora']['lora_weight'] = 0.8
|
||||
if look_data['lora'].get('lora_weight_min') is None:
|
||||
look_data['lora']['lora_weight_min'] = 0.7
|
||||
if look_data['lora'].get('lora_weight_max') is None:
|
||||
look_data['lora']['lora_weight_max'] = 1.0
|
||||
|
||||
os.makedirs(app.config['LOOKS_DIR'], exist_ok=True)
|
||||
with open(json_path, 'w') as f:
|
||||
json.dump(look_data, f, indent=2)
|
||||
|
||||
if is_existing:
|
||||
overwritten_count += 1
|
||||
else:
|
||||
created_count += 1
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error creating look for {filename}: {e}")
|
||||
|
||||
if created_count > 0 or overwritten_count > 0:
|
||||
sync_looks()
|
||||
msg = f'Successfully processed looks: {created_count} created, {overwritten_count} overwritten.'
|
||||
if skipped_count > 0:
|
||||
msg += f' (Skipped {skipped_count} existing)'
|
||||
flash(msg)
|
||||
else:
|
||||
flash(f'No looks created or overwritten. {skipped_count} existing entries found.')
|
||||
|
||||
return redirect(url_for('looks_index'))
|
||||
Reference in New Issue
Block a user