Add Preset Library feature
Presets are saved generation recipes that combine all resource types (character, outfit, action, style, scene, detailer, look, checkpoint) with per-field on/off/random toggles. At generation time, entities marked "random" are picked from the DB and fields marked "random" are randomly included or excluded. - Preset model + sync_presets() following existing category pattern - _resolve_preset_entity() / _resolve_preset_fields() helpers - Full route set: index, detail, generate, edit, upload, clone, save_json, create (LLM), rescan - 4 templates: index (gallery), detail (summary + generate), edit (3-way toggle UI), create (LLM form) - example_01.json reference preset + preset_system.txt LLM prompt - Presets nav link in layout.html Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
63
templates/presets/index.html
Normal file
63
templates/presets/index.html
Normal file
@@ -0,0 +1,63 @@
|
||||
{% 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 %}
|
||||
|
||||
<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 %}
|
||||
<span class="text-muted">No Image</span>
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user