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:
@@ -11,13 +11,14 @@ from services.sync import _resolve_preset_entity, _resolve_preset_fields
|
||||
logger = logging.getLogger('gaze')
|
||||
|
||||
|
||||
def generate_from_preset(preset, overrides=None):
|
||||
def generate_from_preset(preset, overrides=None, save_category='presets'):
|
||||
"""Execute preset-based generation.
|
||||
|
||||
Args:
|
||||
preset: Preset ORM object
|
||||
overrides: optional dict with keys:
|
||||
checkpoint, extra_positive, extra_negative, seed, width, height, action
|
||||
save_category: upload sub-directory ('presets' or 'generator')
|
||||
|
||||
Returns:
|
||||
job dict from _enqueue_job()
|
||||
@@ -52,6 +53,19 @@ def generate_from_preset(preset, overrides=None):
|
||||
detailer_obj = _resolve_preset_entity('detailer', detailer_cfg.get('detailer_id'))
|
||||
look_obj = _resolve_preset_entity('look', look_cfg.get('look_id'))
|
||||
|
||||
# Build sidecar metadata with resolved entity slugs
|
||||
resolved_meta = {
|
||||
'preset_slug': preset.slug,
|
||||
'preset_name': preset.name,
|
||||
'character_slug': character.slug if character else None,
|
||||
'outfit_slug': outfit.slug if outfit else None,
|
||||
'action_slug': action_obj.slug if action_obj else None,
|
||||
'style_slug': style_obj.slug if style_obj else None,
|
||||
'scene_slug': scene_obj.slug if scene_obj else None,
|
||||
'detailer_slug': detailer_obj.slug if detailer_obj else None,
|
||||
'look_slug': look_obj.slug if look_obj else None,
|
||||
}
|
||||
|
||||
# Checkpoint: override > preset config > default
|
||||
checkpoint_override = overrides.get('checkpoint', '').strip() if overrides.get('checkpoint') else ''
|
||||
if checkpoint_override:
|
||||
@@ -71,14 +85,31 @@ def generate_from_preset(preset, overrides=None):
|
||||
else:
|
||||
ckpt_path, ckpt_data = _get_default_checkpoint()
|
||||
|
||||
resolved_meta['checkpoint_path'] = ckpt_path
|
||||
|
||||
# Resolve selected fields from preset toggles
|
||||
selected_fields = _resolve_preset_fields(data)
|
||||
|
||||
# Check suppress_wardrobe: preset override > action default
|
||||
suppress_wardrobe = False
|
||||
preset_suppress = action_cfg.get('suppress_wardrobe')
|
||||
if preset_suppress == 'random':
|
||||
suppress_wardrobe = random.choice([True, False])
|
||||
elif preset_suppress is not None:
|
||||
suppress_wardrobe = bool(preset_suppress)
|
||||
elif action_obj:
|
||||
suppress_wardrobe = action_obj.data.get('suppress_wardrobe', False)
|
||||
|
||||
if suppress_wardrobe:
|
||||
selected_fields = [f for f in selected_fields if not f.startswith('wardrobe::')]
|
||||
|
||||
# Build combined data for prompt building
|
||||
active_wardrobe = char_cfg.get('fields', {}).get('wardrobe', {}).get('outfit', 'default')
|
||||
wardrobe_source = outfit.data.get('wardrobe', {}) if outfit else None
|
||||
if wardrobe_source is None:
|
||||
wardrobe_source = character.get_active_wardrobe() if character else {}
|
||||
if suppress_wardrobe:
|
||||
wardrobe_source = {}
|
||||
|
||||
combined_data = {
|
||||
'character_id': character.character_id if character else 'unknown',
|
||||
@@ -88,7 +119,6 @@ def generate_from_preset(preset, overrides=None):
|
||||
'styles': character.data.get('styles', {}) if character else {},
|
||||
'lora': (look_obj.data.get('lora', {}) if look_obj
|
||||
else (character.data.get('lora', {}) if character else {})),
|
||||
'tags': (character.data.get('tags', []) if character else []) + data.get('tags', []),
|
||||
}
|
||||
|
||||
# Build extras prompt from secondary resources
|
||||
@@ -108,7 +138,6 @@ def generate_from_preset(preset, overrides=None):
|
||||
trg = action_obj.data.get('lora', {}).get('lora_triggers', '')
|
||||
if trg:
|
||||
extras_parts.append(trg)
|
||||
extras_parts.extend(action_obj.data.get('tags', []))
|
||||
if style_obj:
|
||||
s = style_obj.data.get('style', {})
|
||||
if s.get('artist_name'):
|
||||
@@ -133,7 +162,6 @@ def generate_from_preset(preset, overrides=None):
|
||||
trg = scene_obj.data.get('lora', {}).get('lora_triggers', '')
|
||||
if trg:
|
||||
extras_parts.append(trg)
|
||||
extras_parts.extend(scene_obj.data.get('tags', []))
|
||||
if detailer_obj:
|
||||
prompt_val = detailer_obj.data.get('prompt', '')
|
||||
if isinstance(prompt_val, list):
|
||||
@@ -195,6 +223,7 @@ def generate_from_preset(preset, overrides=None):
|
||||
)
|
||||
|
||||
label = f"Preset: {preset.name} – {action}"
|
||||
job = _enqueue_job(label, workflow, _make_finalize('presets', preset.slug, Preset, action))
|
||||
db_model = Preset if save_category == 'presets' else None
|
||||
job = _enqueue_job(label, workflow, _make_finalize(save_category, preset.slug, db_model, action, metadata=resolved_meta))
|
||||
|
||||
return job
|
||||
|
||||
Reference in New Issue
Block a user