When importing a CivitAI image as a recipe, modelVersionIds[0] was blindly used as the checkpoint version ID. This array mixes checkpoints and LoRAs without ordering guarantees, causing LoRAs to be saved as the recipe checkpoint.
Fix by:
1. Removing the modelVersionIds[0] fallback in _download_remote_media
2. Parsing resources entries with type:"model" as the checkpoint
3. Adding model type validation in populate_checkpoint_from_civitai
Also add 2 tests for the new behavior and fix 3 tests whose mocks lacked the required model.type field.
- _resolve_commercial_bits() no longer has Sell-implies-Image
cascading; each CommercialUse value sets only its own bit,
matching CivitAI's modern array-format API.
- Keep filter tag label as 'Allow Selling' for brevity; add
title/tooltip 'Allow selling generated images' on hover.
- Same tooltip treatment for 'No Credit Required'.
- Add i18n keys for both tooltips across all 10 locales.
- Layer 2 fallback: user tags overlapping with auto-tag categories
(HIGH/LOW/I2V/T2V/TI2V/Lightning/Turbo) are merged into auto_tags,
providing manual override when filename-based detection fails.
Matching is case-insensitive so "high"/"High"/"HIGH" all work.
- Refresh on tag edit: save_metadata and add_tags handlers now return
recalculated auto_tags in the response; the frontend passes them to
VirtualScroller.updateSingleItem so badges update immediately without
requiring a page reload.
- 8 new test cases for Layer 2 fallback and case-insensitive matching.
Autocomplete, copy/send-to-workflow, and recipe syntax now emit
<lora:folder/name:strength> instead of <lora:name:strength>, using
relative paths to disambiguate identically-named loras in different
subfolders without requiring file renames.
Backend: 3-tier hybrid resolution (path → bare → basename fallback)
across get_lora_info, get_lora_info_absolute, get_model_preview_url,
get_model_civitai_url, get_model_info_by_name, get_lora_metadata_by_filename,
and get_hash_by_filename. Also fix get_random_loras and get_cycler_list
to return path-prefixed names for randomizer/cycler consistency.
Frontend: autocomplete, copyLoraSyntax, handleSendToWorkflow emit
folder-prefixed syntax. extract_lora_name preserves relative paths.
Saved image metadata (<lora:...> in EXIF) intentionally keeps basename-only
for compatibility with A1111/Forge ecosystem.
Duplicate filename detection is only relevant for LoRAs, which use
basename-only syntax (<lora:name:strength>). Checkpoints and diffusion
models reference files via relative paths with extensions, so filename
conflicts there are false positives — there is no resolution ambiguity.
Both _log_duplicate_filename_summary() and DoctorHandler's
_check_filename_conflicts() now skip scanners with model_type != 'lora'.
- Backend auto-tag extraction service: detect HIGH/LOW (Wan-only), I2V/T2V/TI2V,
Lightning/Turbo from filename, base_model, and CivitAI version name
- HIGH/LOW badge in card footer (inline before version name), color-coded:
blue for HIGH, teal for LOW; abbreviated to H/L in medium/compact density
- Auto-tag filter panel (I2V, T2V, TI2V, Lightning, Turbo) with tri-state
include/exclude filtering
- Full filter pipeline: FilterCriteria → ModelFilterSet → baseModelApi params
- AUTO_TAG_GROUPS exported for frontend use
- 19 unit tests for auto-tag extraction edge cases
CivitAI image API returns modelVersionIds at the root level of the
response (not inside meta), containing ALL model version IDs across
all resources (checkpoint + LoRAs). Two bugs prevented LoRAs from
being discovered:
1. _download_remote_media only extracted the first modelVersionId for
enrichment, dropping the rest.
2. CivitAI API meta parsing only ran as an EXIF fallback, but most
images have embedded EXIF metadata (prompt, steps, etc.), so the
fallback was never triggered.
3. When civitai_meta_raw itself has a nested 'meta' key, unwrapping
it stripped the injected modelVersionIds.
Also fixed gen_params merge: API gen_params now overlays EXIF at the
field level instead of full replacement, preserving EXIF-only fields
like detailed generation parameters.
The method mark_not_downloaded() was misleading — it doesn't negate
'downloaded' history (the model was indeed downloaded before), but
rather sets is_deleted_override = 1 to indicate the version was
downloaded and subsequently deleted. This flag allows re-download when
the 'skip previously downloaded' setting is enabled.
Rename to mark_as_deleted() to accurately reflect its semantics.
The main Refresh button and Quick Refresh dropdown item both called refreshModels(false). Split button dropdowns should only contain alternative actions (Hick's Law). Dropdown now has only Rebuild Cache (fullRebuild=true). Removed from 2 templates, 2 JS files, 1 test fixture, and 10 locale files.
Commit 454210a4 replaced renderFunction() with widget.value setter +
widget.callback() in endDrag, so the test assertion should verify
callback invocation instead of the removed renderSpy call.
Detects when multiple model files share the same basename (causing
ambiguity in LoRA resolution), logs warnings during scanning, and
provides a "Resolve Conflicts" button in the Doctor panel. Resolution
renames duplicates with hash-prefixed unique filenames, migrates all
sidecar and preview files, and updates the cache and frontend scroller
in-place so the model modal immediately reflects the new filename.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
_split _get_records_bulk into 500-id batches so the WHERE IN clause
never exceeds SQLite's 999-parameter ceiling.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>