Files
character-browser/templates/presets/index.html
Aodhan Collins 29a6723b25 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>
2026-03-22 00:31:27 +00:00

82 lines
4.3 KiB
HTML

{% extends "layout.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Preset Library</h2>
<div class="d-flex gap-1 align-items-center">
<a href="{{ url_for('create_preset') }}" class="btn btn-sm btn-success">Create New Preset</a>
<form action="{{ url_for('rescan_presets') }}" method="post" class="d-contents">
<button type="submit" class="btn btn-sm btn-outline-primary btn-icon" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Rescan preset files from disk"><img src="{{ url_for('static', filename='icons/refresh.png') }}"></button>
</form>
</div>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ 'danger' if category == 'error' else 'success' }} alert-dismissible fade show">{{ message }}<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Filters -->
<form method="get" class="mb-3 d-flex gap-3 align-items-center">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="favourite" value="on" id="favFilter" {% if favourite_filter == 'on' %}checked{% endif %} onchange="this.form.submit()">
<label class="form-check-label small" for="favFilter">&#9733; Favourites</label>
</div>
<select name="nsfw" class="form-select form-select-sm" style="width:auto;" onchange="this.form.submit()">
<option value="all" {% if nsfw_filter == 'all' %}selected{% endif %}>All ratings</option>
<option value="sfw" {% if nsfw_filter == 'sfw' %}selected{% endif %}>SFW only</option>
<option value="nsfw" {% if nsfw_filter == 'nsfw' %}selected{% endif %}>NSFW only</option>
</select>
</form>
<div class="row row-cols-2 row-cols-sm-3 row-cols-md-4 row-cols-lg-5 row-cols-xl-6 g-3">
{% for preset in presets %}
<div class="col">
<div class="card h-100 character-card" onclick="window.location.href='{{ url_for('preset_detail', slug=preset.slug) }}'">
<div class="img-container">
{% if preset.image_path %}
<img src="{{ url_for('static', filename='uploads/' + preset.image_path) }}" alt="{{ preset.name }}">
{% else %}
{% set fallback = random_gen_image('presets', preset.slug) %}
{% if fallback %}
<img src="{{ url_for('static', filename='uploads/' + fallback) }}" alt="{{ preset.name }}" class="fallback-cover">
{% else %}
<span class="text-muted">No Image</span>
{% endif %}
{% endif %}
</div>
<div class="card-body p-2">
<h6 class="card-title text-center mb-1">{{ preset.name }}</h6>
<p class="card-text small text-center text-muted mb-0">
{% set parts = [] %}
{% if preset.data.character and preset.data.character.character_id %}
{% set _ = parts.append(preset.data.character.character_id | replace('_', ' ') | title) %}
{% endif %}
{% if preset.data.action and preset.data.action.action_id %}
{% set _ = parts.append('+ action') %}
{% endif %}
{% if preset.data.scene and preset.data.scene.scene_id %}
{% set _ = parts.append('+ scene') %}
{% endif %}
{{ parts | join(' · ') }}
</p>
</div>
<div class="card-footer d-flex justify-content-between align-items-center p-1">
<small class="text-muted">preset</small>
<div class="d-flex gap-1">
<a href="{{ url_for('edit_preset', slug=preset.slug) }}" class="btn btn-xs btn-outline-secondary" onclick="event.stopPropagation()">Edit</a>
</div>
</div>
</div>
</div>
{% else %}
<div class="col-12">
<p class="text-muted">No presets found. <a href="{{ url_for('create_preset') }}">Create your first preset</a> or add JSON files to <code>data/presets/</code> and rescan.</p>
</div>
{% endfor %}
</div>
{% endblock %}