Code review fixes: wardrobe migration, response validation, path traversal guard, deduplication

- Migrate 11 character JSONs from old wardrobe keys to _BODY_GROUP_KEYS format
- Add is_favourite/is_nsfw columns to Preset model
- Add HTTP response validation and timeouts to ComfyUI client
- Add path traversal protection on replace cover route
- Deduplicate services/mcp.py (4 functions → 2 generic + 2 wrappers)
- Extract apply_library_filters() and clean_html_text() shared helpers
- Add named constants for 17 ComfyUI workflow node IDs
- Fix bare except clauses in services/llm.py
- Fix tags schema in ensure_default_outfit() (list → dict)
- Convert f-string logging to lazy % formatting
- Add 5-minute polling timeout to frontend waitForJob()
- Improve migration error handling (non-duplicate errors log at WARNING)
- Update CLAUDE.md to reflect all changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Aodhan Collins
2026-03-22 00:31:27 +00:00
parent 55ff58aba6
commit 29a6723b25
37 changed files with 464 additions and 539 deletions

View File

@@ -15,8 +15,8 @@ from services.prompts import build_prompt, _resolve_character, _ensure_character
from services.sync import sync_actions
from services.file_io import get_available_loras
from services.llm import load_prompt, call_llm
from utils import allowed_file, _LORA_DEFAULTS
from routes.shared import register_common_routes
from utils import allowed_file, _LORA_DEFAULTS, clean_html_text
from routes.shared import register_common_routes, apply_library_filters
logger = logging.getLogger('gaze')
@@ -26,17 +26,8 @@ def register_routes(app):
@app.route('/actions')
def actions_index():
query = Action.query
fav = request.args.get('favourite')
nsfw = request.args.get('nsfw', 'all')
if fav == 'on':
query = query.filter_by(is_favourite=True)
if nsfw == 'sfw':
query = query.filter_by(is_nsfw=False)
elif nsfw == 'nsfw':
query = query.filter_by(is_nsfw=True)
actions = query.order_by(Action.is_favourite.desc(), Action.name).all()
return render_template('actions/index.html', actions=actions, favourite_filter=fav or '', nsfw_filter=nsfw)
actions, fav, nsfw = apply_library_filters(Action.query, Action)
return render_template('actions/index.html', actions=actions, favourite_filter=fav, nsfw_filter=nsfw)
@app.route('/actions/rescan', methods=['POST'])
def rescan_actions():
@@ -228,9 +219,9 @@ def register_routes(app):
selected_fields.append(f'identity::{key}')
# Add wardrobe fields (unless suppressed)
if not suppress_wardrobe:
from utils import _WARDROBE_KEYS
from utils import _BODY_GROUP_KEYS
wardrobe = character.get_active_wardrobe()
for key in _WARDROBE_KEYS:
for key in _BODY_GROUP_KEYS:
if wardrobe.get(key):
selected_fields.append(f'wardrobe::{key}')
@@ -302,9 +293,9 @@ def register_routes(app):
# Wardrobe (active outfit) — skip if suppressed
if not suppress_wardrobe:
from utils import _WARDROBE_KEYS
from utils import _BODY_GROUP_KEYS
wardrobe = extra_char.get_active_wardrobe()
for key in _WARDROBE_KEYS:
for key in _BODY_GROUP_KEYS:
val = wardrobe.get(key)
if val:
extra_parts.append(val)
@@ -389,11 +380,7 @@ def register_routes(app):
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())
html_content = clean_html_text(html_raw)
except Exception:
pass

View File

@@ -12,7 +12,7 @@ from services.llm import call_character_mcp_tool, call_llm, load_prompt
from services.prompts import build_prompt
from services.sync import sync_characters
from services.workflow import _get_default_checkpoint, _prepare_workflow
from routes.shared import register_common_routes
from routes.shared import register_common_routes, apply_library_filters
logger = logging.getLogger('gaze')
@@ -22,17 +22,8 @@ def register_routes(app):
@app.route('/')
def index():
query = Character.query
fav = request.args.get('favourite')
nsfw = request.args.get('nsfw', 'all')
if fav == 'on':
query = query.filter_by(is_favourite=True)
if nsfw == 'sfw':
query = query.filter_by(is_nsfw=False)
elif nsfw == 'nsfw':
query = query.filter_by(is_nsfw=True)
characters = query.order_by(Character.is_favourite.desc(), Character.name).all()
return render_template('index.html', characters=characters, favourite_filter=fav or '', nsfw_filter=nsfw)
characters, fav, nsfw = apply_library_filters(Character.query, Character)
return render_template('index.html', characters=characters, favourite_filter=fav, nsfw_filter=nsfw)
@app.route('/rescan', methods=['POST'])
def rescan():
@@ -274,16 +265,16 @@ def register_routes(app):
# Fetch reference data from wiki URL if provided
wiki_reference = ''
if wiki_url:
logger.info(f"Fetching character data from URL: {wiki_url}")
logger.info("Fetching character data from URL: %s", wiki_url)
wiki_data = call_character_mcp_tool('get_character_from_url', {
'url': wiki_url,
'name': name,
})
if wiki_data:
wiki_reference = f"\n\nReference data from wiki:\n{wiki_data}\n\nUse this reference to accurately describe the character's appearance, outfit, and features."
logger.info(f"Got wiki reference data ({len(wiki_data)} chars)")
logger.info("Got wiki reference data (%d chars)", len(wiki_data))
else:
logger.warning(f"Failed to fetch wiki data from {wiki_url}")
logger.warning("Failed to fetch wiki data from %s", wiki_url)
# Step 1: Generate or select outfit first
default_outfit_id = 'default'
@@ -352,7 +343,7 @@ Create an outfit JSON with wardrobe fields appropriate for this character."""
db.session.commit()
default_outfit_id = outfit_slug
logger.info(f"Generated outfit: {outfit_name} for character {name}")
logger.info("Generated outfit: %s for character %s", outfit_name, name)
except Exception as e:
logger.exception("Outfit generation error: %s", e)

View File

@@ -14,8 +14,8 @@ from services.prompts import build_prompt, _resolve_character, _ensure_character
from services.sync import sync_checkpoints, _default_checkpoint_data
from services.file_io import get_available_checkpoints
from services.llm import load_prompt, call_llm
from utils import allowed_file
from routes.shared import register_common_routes
from utils import allowed_file, clean_html_text
from routes.shared import register_common_routes, apply_library_filters
logger = logging.getLogger('gaze')
@@ -58,17 +58,8 @@ def register_routes(app):
@app.route('/checkpoints')
def checkpoints_index():
query = Checkpoint.query
fav = request.args.get('favourite')
nsfw = request.args.get('nsfw', 'all')
if fav == 'on':
query = query.filter_by(is_favourite=True)
if nsfw == 'sfw':
query = query.filter_by(is_nsfw=False)
elif nsfw == 'nsfw':
query = query.filter_by(is_nsfw=True)
checkpoints = query.order_by(Checkpoint.is_favourite.desc(), Checkpoint.name).all()
return render_template('checkpoints/index.html', checkpoints=checkpoints, favourite_filter=fav or '', nsfw_filter=nsfw)
checkpoints, fav, nsfw = apply_library_filters(Checkpoint.query, Checkpoint)
return render_template('checkpoints/index.html', checkpoints=checkpoints, favourite_filter=fav, nsfw_filter=nsfw)
@app.route('/checkpoints/rescan', methods=['POST'])
def rescan_checkpoints():
@@ -179,11 +170,7 @@ def register_routes(app):
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())
html_content = clean_html_text(html_raw)
except Exception as e:
logger.error("Error reading HTML for %s: %s", filename, e)

View File

@@ -13,8 +13,8 @@ from services.prompts import build_prompt, _resolve_character, _ensure_character
from services.sync import sync_detailers
from services.file_io import get_available_loras
from services.llm import load_prompt, call_llm
from utils import _WARDROBE_KEYS
from routes.shared import register_common_routes
from utils import _BODY_GROUP_KEYS, clean_html_text
from routes.shared import register_common_routes, apply_library_filters
logger = logging.getLogger('gaze')
@@ -47,7 +47,7 @@ def register_routes(app):
selected_fields.append(f'identity::{key}')
selected_fields.append('special::name')
wardrobe = character.get_active_wardrobe()
for key in _WARDROBE_KEYS:
for key in _BODY_GROUP_KEYS:
if wardrobe.get(key):
selected_fields.append(f'wardrobe::{key}')
selected_fields.extend(['lora::lora_triggers'])
@@ -87,17 +87,8 @@ def register_routes(app):
@app.route('/detailers')
def detailers_index():
query = Detailer.query
fav = request.args.get('favourite')
nsfw = request.args.get('nsfw', 'all')
if fav == 'on':
query = query.filter_by(is_favourite=True)
if nsfw == 'sfw':
query = query.filter_by(is_nsfw=False)
elif nsfw == 'nsfw':
query = query.filter_by(is_nsfw=True)
detailers = query.order_by(Detailer.is_favourite.desc(), Detailer.name).all()
return render_template('detailers/index.html', detailers=detailers, favourite_filter=fav or '', nsfw_filter=nsfw)
detailers, fav, nsfw = apply_library_filters(Detailer.query, Detailer)
return render_template('detailers/index.html', detailers=detailers, favourite_filter=fav, nsfw_filter=nsfw)
@app.route('/detailers/rescan', methods=['POST'])
def rescan_detailers():
@@ -296,11 +287,7 @@ def register_routes(app):
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())
html_content = clean_html_text(html_raw)
except Exception as e:
logger.error("Error reading HTML %s: %s", html_filename, e)

View File

@@ -2,6 +2,7 @@ import json
import os
import re
import logging
from utils import clean_html_text
from flask import render_template, request, redirect, url_for, flash, session
from sqlalchemy.orm.attributes import flag_modified
@@ -13,7 +14,7 @@ from services.prompts import build_prompt, _resolve_character, _ensure_character
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 routes.shared import register_common_routes
from routes.shared import register_common_routes, apply_library_filters
logger = logging.getLogger('gaze')
@@ -57,18 +58,9 @@ def register_routes(app):
@app.route('/looks')
def looks_index():
query = Look.query
fav = request.args.get('favourite')
nsfw = request.args.get('nsfw', 'all')
if fav == 'on':
query = query.filter_by(is_favourite=True)
if nsfw == 'sfw':
query = query.filter_by(is_nsfw=False)
elif nsfw == 'nsfw':
query = query.filter_by(is_nsfw=True)
looks = query.order_by(Look.is_favourite.desc(), Look.name).all()
looks, fav, nsfw = apply_library_filters(Look.query, Look)
look_assignments = _count_look_assignments()
return render_template('looks/index.html', looks=looks, look_assignments=look_assignments, favourite_filter=fav or '', nsfw_filter=nsfw)
return render_template('looks/index.html', looks=looks, look_assignments=look_assignments, favourite_filter=fav, nsfw_filter=nsfw)
@app.route('/looks/rescan', methods=['POST'])
def rescan_looks():
@@ -320,7 +312,7 @@ Character ID: {character_slug}"""
character_data['lora'] = lora_data
except Exception as e:
logger.exception(f"LLM character generation error: {e}")
logger.exception("LLM character generation error: %s", e)
flash(f'Failed to generate character with AI: {e}', 'error')
return redirect(url_for('look_detail', slug=slug))
else:
@@ -494,11 +486,7 @@ Character ID: {character_slug}"""
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())
html_content = clean_html_text(html_raw)
except Exception as e:
logger.error("Error reading HTML %s: %s", html_filename, e)

View File

@@ -13,9 +13,9 @@ from services.job_queue import _enqueue_job, _make_finalize, _enqueue_task
from services.prompts import build_prompt, _resolve_character, _ensure_character_fields, _append_background
from services.sync import sync_outfits
from services.file_io import get_available_loras, _count_outfit_lora_assignments
from utils import allowed_file, _LORA_DEFAULTS
from utils import allowed_file, _LORA_DEFAULTS, clean_html_text
from services.llm import load_prompt, call_llm
from routes.shared import register_common_routes
from routes.shared import register_common_routes, apply_library_filters
logger = logging.getLogger('gaze')
@@ -25,18 +25,9 @@ def register_routes(app):
@app.route('/outfits')
def outfits_index():
query = Outfit.query
fav = request.args.get('favourite')
nsfw = request.args.get('nsfw', 'all')
if fav == 'on':
query = query.filter_by(is_favourite=True)
if nsfw == 'sfw':
query = query.filter_by(is_nsfw=False)
elif nsfw == 'nsfw':
query = query.filter_by(is_nsfw=True)
outfits = query.order_by(Outfit.is_favourite.desc(), Outfit.name).all()
outfits, fav, nsfw = apply_library_filters(Outfit.query, Outfit)
lora_assignments = _count_outfit_lora_assignments()
return render_template('outfits/index.html', outfits=outfits, lora_assignments=lora_assignments, favourite_filter=fav or '', nsfw_filter=nsfw)
return render_template('outfits/index.html', outfits=outfits, lora_assignments=lora_assignments, favourite_filter=fav, nsfw_filter=nsfw)
@app.route('/outfits/rescan', methods=['POST'])
def rescan_outfits():
@@ -90,11 +81,7 @@ def register_routes(app):
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())
html_content = clean_html_text(html_raw)
except Exception:
pass
@@ -313,12 +300,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.
from utils import _IDENTITY_KEYS, _WARDROBE_KEYS
for key in _IDENTITY_KEYS:
from utils import _BODY_GROUP_KEYS
for key in _BODY_GROUP_KEYS:
if character.data.get('identity', {}).get(key):
selected_fields.append(f'identity::{key}')
outfit_wardrobe = outfit.data.get('wardrobe', {})
for key in _WARDROBE_KEYS:
for key in _BODY_GROUP_KEYS:
if outfit_wardrobe.get(key):
selected_fields.append(f'wardrobe::{key}')
selected_fields.append('special::name')

View File

@@ -8,7 +8,7 @@ from sqlalchemy.orm.attributes import flag_modified
from services.sync import sync_presets
from services.generation import generate_from_preset
from services.llm import load_prompt, call_llm
from routes.shared import register_common_routes
from routes.shared import register_common_routes, apply_library_filters
logger = logging.getLogger('gaze')
@@ -18,8 +18,9 @@ def register_routes(app):
@app.route('/presets')
def presets_index():
presets = Preset.query.order_by(Preset.filename).all()
return render_template('presets/index.html', presets=presets)
presets, fav, nsfw = apply_library_filters(Preset.query, Preset)
return render_template('presets/index.html', presets=presets,
favourite_filter=fav, nsfw_filter=nsfw)
@app.route('/preset/<path:slug>')
def preset_detail(slug):

View File

@@ -63,7 +63,7 @@ def register_routes(app):
clean_json = llm_response.replace('```json', '').replace('```', '').strip()
new_data = json.loads(clean_json)
except Exception as e:
logger.exception(f"Regenerate tags LLM error for {category}/{slug}")
logger.exception("Regenerate tags LLM error for %s/%s", category, slug)
return {'error': f'LLM error: {str(e)}'}, 500
# Preserve protected fields from original
@@ -106,7 +106,7 @@ def register_routes(app):
with open(file_path, 'w') as f:
json.dump(new_data, f, indent=2)
except Exception as e:
logger.warning(f"Could not write {file_path}: {e}")
logger.warning("Could not write %s: %s", file_path, e)
migrated += 1
@@ -122,7 +122,7 @@ def register_routes(app):
migrated += 1
db.session.commit()
logger.info(f"Migrated {migrated} resources from list tags to dict tags")
logger.info("Migrated %d resources from list tags to dict tags", migrated)
return {'success': True, 'migrated': migrated}
def _make_regen_task(category, slug, name, system_prompt):

View File

@@ -13,8 +13,8 @@ from services.prompts import build_prompt, _resolve_character, _ensure_character
from services.sync import sync_scenes
from services.file_io import get_available_loras
from services.llm import load_prompt, call_llm
from routes.shared import register_common_routes
from utils import _WARDROBE_KEYS
from routes.shared import register_common_routes, apply_library_filters
from utils import _BODY_GROUP_KEYS, clean_html_text
logger = logging.getLogger('gaze')
@@ -24,17 +24,8 @@ def register_routes(app):
@app.route('/scenes')
def scenes_index():
query = Scene.query
fav = request.args.get('favourite')
nsfw = request.args.get('nsfw', 'all')
if fav == 'on':
query = query.filter_by(is_favourite=True)
if nsfw == 'sfw':
query = query.filter_by(is_nsfw=False)
elif nsfw == 'nsfw':
query = query.filter_by(is_nsfw=True)
scenes = query.order_by(Scene.is_favourite.desc(), Scene.name).all()
return render_template('scenes/index.html', scenes=scenes, favourite_filter=fav or '', nsfw_filter=nsfw)
scenes, fav, nsfw = apply_library_filters(Scene.query, Scene)
return render_template('scenes/index.html', scenes=scenes, favourite_filter=fav, nsfw_filter=nsfw)
@app.route('/scenes/rescan', methods=['POST'])
def rescan_scenes():
@@ -177,7 +168,7 @@ def register_routes(app):
selected_fields.append(f'identity::{key}')
selected_fields.append('special::name')
wardrobe = character.get_active_wardrobe()
for key in _WARDROBE_KEYS:
for key in _BODY_GROUP_KEYS:
if wardrobe.get(key):
selected_fields.append(f'wardrobe::{key}')
selected_fields.extend(['defaults::scene', 'lora::lora_triggers'])
@@ -312,11 +303,7 @@ def register_routes(app):
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())
html_content = clean_html_text(html_raw)
except Exception as e:
logger.error("Error reading HTML %s: %s", html_filename, e)

View File

@@ -62,7 +62,7 @@ def register_routes(app):
db.session.commit()
logger.info("Default checkpoint saved to database: %s", checkpoint_path)
except Exception as e:
logger.error(f"Failed to persist checkpoint to database: {e}")
logger.error("Failed to persist checkpoint to database: %s", e)
db.session.rollback()
# Also persist to comfy_workflow.json for backwards compatibility
@@ -78,7 +78,7 @@ def register_routes(app):
with open(workflow_path, 'w') as f:
json.dump(workflow, f, indent=2)
except Exception as e:
logger.error(f"Failed to persist checkpoint to workflow file: {e}")
logger.error("Failed to persist checkpoint to workflow file: %s", e)
return {'status': 'ok'}

View File

@@ -20,6 +20,23 @@ from utils import allowed_file
logger = logging.getLogger('gaze')
def apply_library_filters(query, model_class):
"""Apply standard favourite/NSFW filters and sorting to a library query.
Returns (items, favourite_filter, nsfw_filter) tuple.
"""
fav = request.args.get('favourite')
nsfw = request.args.get('nsfw', 'all')
if fav == 'on':
query = query.filter_by(is_favourite=True)
if nsfw == 'sfw':
query = query.filter_by(is_nsfw=False)
elif nsfw == 'nsfw':
query = query.filter_by(is_nsfw=True)
items = query.order_by(model_class.is_favourite.desc(), model_class.name).all()
return items, fav or '', nsfw
# ---------------------------------------------------------------------------
# Category configuration registry
# ---------------------------------------------------------------------------
@@ -237,11 +254,16 @@ def _register_replace_cover_route(app, cfg):
def replace_cover(slug):
entity = Model.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(current_app.config['UPLOAD_FOLDER'], preview_path)):
entity.image_path = preview_path
db.session.commit()
flash('Cover image updated!')
if preview_path:
full_path = os.path.realpath(
os.path.join(current_app.config['UPLOAD_FOLDER'], preview_path))
upload_root = os.path.realpath(current_app.config['UPLOAD_FOLDER'])
if full_path.startswith(upload_root + os.sep) and os.path.exists(full_path):
entity.image_path = preview_path
db.session.commit()
flash('Cover image updated!')
else:
flash('Invalid preview path.', 'error')
else:
flash('No valid preview image selected.', 'error')
return redirect(url_for(detail_ep, slug=slug))

View File

@@ -14,8 +14,8 @@ from services.prompts import build_prompt, _resolve_character, _ensure_character
from services.sync import sync_styles
from services.file_io import get_available_loras
from services.llm import load_prompt, call_llm
from routes.shared import register_common_routes
from utils import _WARDROBE_KEYS
from routes.shared import register_common_routes, apply_library_filters
from utils import _BODY_GROUP_KEYS, clean_html_text
logger = logging.getLogger('gaze')
@@ -47,7 +47,7 @@ def register_routes(app):
selected_fields.append(f'identity::{key}')
selected_fields.append('special::name')
wardrobe = character.get_active_wardrobe()
for key in _WARDROBE_KEYS:
for key in _BODY_GROUP_KEYS:
if wardrobe.get(key):
selected_fields.append(f'wardrobe::{key}')
selected_fields.extend(['style::artist_name', 'style::artistic_style', 'lora::lora_triggers'])
@@ -82,17 +82,8 @@ def register_routes(app):
@app.route('/styles')
def styles_index():
query = Style.query
fav = request.args.get('favourite')
nsfw = request.args.get('nsfw', 'all')
if fav == 'on':
query = query.filter_by(is_favourite=True)
if nsfw == 'sfw':
query = query.filter_by(is_nsfw=False)
elif nsfw == 'nsfw':
query = query.filter_by(is_nsfw=True)
styles = query.order_by(Style.is_favourite.desc(), Style.name).all()
return render_template('styles/index.html', styles=styles, favourite_filter=fav or '', nsfw_filter=nsfw)
styles, fav, nsfw = apply_library_filters(Style.query, Style)
return render_template('styles/index.html', styles=styles, favourite_filter=fav, nsfw_filter=nsfw)
@app.route('/styles/rescan', methods=['POST'])
def rescan_styles():
@@ -323,11 +314,7 @@ def register_routes(app):
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())
html_content = clean_html_text(html_raw)
except Exception:
pass

View File

@@ -244,7 +244,7 @@ Generate a complete {target_category.rstrip('s')} profile with all required fiel
new_data[target_name_key] = new_name
except Exception as e:
logger.exception(f"LLM transfer error: {e}")
logger.exception("LLM transfer error: %s", e)
flash(f'Failed to generate {target_category.rstrip("s")} with AI: {e}')
return redirect(url_for('transfer_resource', category=category, slug=slug))
else:
@@ -290,7 +290,7 @@ Generate a complete {target_category.rstrip('s')} profile with all required fiel
lora_moved = True
flash(f'Moved LoRA file to {target_lora_dir}')
except Exception as lora_e:
logger.exception(f"LoRA move error: {lora_e}")
logger.exception("LoRA move error: %s", lora_e)
flash(f'Warning: Failed to move LoRA file: {lora_e}', 'warning')
else:
flash(f'Warning: Source LoRA file not found at {abs_source_path}', 'warning')
@@ -317,7 +317,7 @@ Generate a complete {target_category.rstrip('s')} profile with all required fiel
db.session.delete(resource)
flash(f'Removed original {category.rstrip("s")}: {resource_name}')
except Exception as rm_e:
logger.exception(f"Error removing original: {rm_e}")
logger.exception("Error removing original: %s", rm_e)
flash(f'Warning: Failed to remove original: {rm_e}', 'warning')
db.session.commit()
@@ -325,7 +325,7 @@ Generate a complete {target_category.rstrip('s')} profile with all required fiel
return redirect(url_for(target_config['index_route'], highlight=safe_slug))
except Exception as e:
logger.exception(f"Transfer save error: {e}")
logger.exception("Transfer save error: %s", e)
flash(f'Failed to save transferred {target_category.rstrip("s")}: {e}')
return redirect(url_for('transfer_resource', category=category, slug=slug))