Files
character-browser/templates/presets/create.html
Aodhan Collins 32a73b02f5 Add semantic tagging, search, favourite/NSFW filtering, and LLM job queue
Replaces old list-format tags (which duplicated prompt content) with structured
dict tags per category (origin_series, outfit_type, participants, style_type,
scene_type, etc.). Tags are now purely organizational metadata — removed from
the prompt pipeline entirely.

Adds is_favourite and is_nsfw columns to all 8 resource models. Favourite is
DB-only (user preference); NSFW is mirrored in JSON tags for rescan persistence.
All library pages get filter controls and favourites-first sorting.

Introduces a parallel LLM job queue (_enqueue_task + _llm_queue_worker) for
background tag regeneration, with the same status polling UI as ComfyUI jobs.
Fixes call_llm() to use has_request_context() fallback for background threads.

Adds global search (/search) across resources and gallery images, with navbar
search bar. Adds gallery image sidecar JSON for per-image favourite/NSFW metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 03:22:09 +00:00

81 lines
4.2 KiB
HTML

{% extends "layout.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Create Preset</h1>
<a href="{{ url_for('presets_index') }}" class="btn btn-outline-secondary">Cancel</a>
</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 justify-content-center">
<div class="col-md-7">
<div class="card">
<div class="card-header bg-dark text-white">New Preset</div>
<div class="card-body">
<form action="{{ url_for('create_preset') }}" method="post">
<div class="mb-3">
<label for="name" class="form-label">Preset Name <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="name" name="name" required placeholder="e.g. Beach Day Casual" value="{{ form_data.get('name', '') }}">
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="use_llm" name="use_llm" {{ 'checked' if form_data.get('use_llm', True) }}>
<label class="form-check-label" for="use_llm">Use AI to build preset from description</label>
</div>
</div>
<div class="mb-3" id="description-section">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description" rows="5"
placeholder="Describe the kind of generations you want this preset to produce. Include the type of character, setting, mood, outfit style, etc.
Example: A random character in a cheerful beach scene, wearing a swimsuit, with a random action pose. Always include identity and wardrobe fields. Randomise headwear and accessories. No style LoRA needed.">{{ form_data.get('description', '') }}</textarea>
<div class="form-text">The AI will set entity IDs (specific, random, or none) and field toggles (on/off/random) based on your description.</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-success" id="create-btn">Create Preset</button>
</div>
</form>
</div>
</div>
<div class="card mt-3">
<div class="card-header">About Presets</div>
<div class="card-body text-muted small">
<p>A <strong>preset</strong> is a saved generation recipe. It stores:</p>
<ul>
<li><strong>Entity selections</strong> — which character, action, scene, etc. to use. Set to a specific ID, <span class="badge bg-warning text-dark">Random</span> (pick at generation time), or None.</li>
<li><strong>Field toggles</strong> — which prompt fields to include. Each can be <span class="badge bg-success">ON</span>, <span class="badge bg-secondary">OFF</span>, or <span class="badge bg-warning text-dark">RNG</span> (randomly decide each generation).</li>
</ul>
<p class="mb-0">After creation you'll be taken to the edit page to review and adjust the AI's choices before generating.</p>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
document.getElementById('use_llm').addEventListener('change', function() {
document.getElementById('description-section').style.display = this.checked ? '' : 'none';
});
document.querySelector('form').addEventListener('submit', function() {
const btn = document.getElementById('create-btn');
if (document.getElementById('use_llm').checked) {
btn.disabled = true;
btn.textContent = 'Generating with AI...';
}
});
</script>
{% endblock %}