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>
This commit is contained in:
@@ -164,6 +164,32 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tag Management -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-dark text-white">Tag Management</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>Migrate Tags</h6>
|
||||
<p class="text-muted small">Convert old list-format tags to new structured dict format across all resources.</p>
|
||||
<button class="btn btn-warning" id="migrate-tags-btn" onclick="migrateTags()">Migrate Tags to New Format</button>
|
||||
<span id="migrate-tags-status" class="ms-2"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Bulk Regenerate Tags</h6>
|
||||
<p class="text-muted small">Use LLM to regenerate structured tags for all resources. This will overwrite existing tags.</p>
|
||||
<button class="btn btn-danger" id="bulk-regen-btn" onclick="bulkRegenerateTags()">Regenerate All Tags (LLM)</button>
|
||||
<div id="bulk-regen-progress" class="mt-2" style="display: none;">
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
<small class="text-muted" id="bulk-regen-status"></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -339,5 +365,59 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function migrateTags() {
|
||||
const btn = document.getElementById('migrate-tags-btn');
|
||||
const status = document.getElementById('migrate-tags-status');
|
||||
if (!confirm('Convert all old list-format tags to new dict format?')) return;
|
||||
btn.disabled = true;
|
||||
status.textContent = 'Migrating...';
|
||||
try {
|
||||
const resp = await fetch('/admin/migrate_tags', { method: 'POST' });
|
||||
const data = await resp.json();
|
||||
status.textContent = data.success ? `Done! Migrated ${data.migrated} resources.` : `Error: ${data.error}`;
|
||||
} catch (err) {
|
||||
status.textContent = 'Failed: ' + err.message;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function bulkRegenerateTags() {
|
||||
if (!confirm('Regenerate tags for ALL resources using the LLM? This may take a while and will overwrite existing tags.')) return;
|
||||
const btn = document.getElementById('bulk-regen-btn');
|
||||
const progress = document.getElementById('bulk-regen-progress');
|
||||
const bar = progress.querySelector('.progress-bar');
|
||||
const status = document.getElementById('bulk-regen-status');
|
||||
btn.disabled = true;
|
||||
progress.style.display = 'block';
|
||||
|
||||
const categories = ['characters', 'outfits', 'actions', 'styles', 'scenes', 'detailers', 'looks'];
|
||||
// Fetch all slugs per category
|
||||
let allItems = [];
|
||||
for (const cat of categories) {
|
||||
try {
|
||||
const resp = await fetch(`/get_missing_${cat}`);
|
||||
// This endpoint returns items missing covers, but we need ALL items.
|
||||
// Instead, we'll use a simpler approach: fetch the index page data
|
||||
} catch (e) {}
|
||||
}
|
||||
// Use a simpler approach: call regenerate for each category via a bulk endpoint
|
||||
status.textContent = 'Queuing regeneration for all resources...';
|
||||
try {
|
||||
const resp = await fetch('/admin/bulk_regenerate_tags', { method: 'POST' });
|
||||
const data = await resp.json();
|
||||
if (data.success) {
|
||||
bar.style.width = '100%';
|
||||
status.textContent = `Queued ${data.total} resources for regeneration. Check console for progress.`;
|
||||
} else {
|
||||
status.textContent = `Error: ${data.error}`;
|
||||
}
|
||||
} catch (err) {
|
||||
status.textContent = 'Failed: ' + err.message;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user