When batch-downloading different versions of the same model, dedup by
modelId alone discards the second URL. Use modelId:modelVersionId as
the dedup key so users can download, e.g., latest + a specific version.
- refreshRecipes() now accepts fullRebuild param and passes it to scan endpoint
- Use consistent toast.api.refreshComplete / toast.api.refreshFailed keys
- Use loadingManager.show() with progress bar (matching models page style)
- Both Refresh and Rebuild Cache now hit the real /api/lm/recipes/scan endpoint
- Add sidebarManager.refresh() after recipe scan completes
- Backend scan_recipes handler reads full_rebuild query param
Bug: when scrolling down on recipes page, any operation with
preserveScroll: true would fetch only page 1 data then restore
scroll position to beyond the loaded items, leaving the grid empty.
Fix:
- Remove preserveScroll: true from all 7 must-refresh trigger
paths (filter, search, sort, import, settings reload, sync,
rebuild cache, sidebar folder nav)
- Replace full list refresh with updateSingleItem() for repair
and bulk missing-LoRA download operations
- Update tests to match new scroll-free behavior
Adds a new bulk operation in the recipes page that allows users to select
multiple recipes and repair their metadata in batch.
Backend:
- New POST /api/lm/recipes/repair-bulk endpoint accepting recipe_ids array
- repair_recipes_bulk handler iterates repair_recipe_by_id for each recipe
- Response includes per-recipe updated data for frontend card refresh
Frontend:
- Bulk context menu: new 'Repair Metadata for Selected' item in Metadata section
- BulkManager.repairSelectedRecipes() with loading/toast flow
- Uses VirtualScroller.updateSingleItem() per repaired recipe (no full reload)
- Visibility controlled via repairMetadata actionConfig flag
Locales:
- Added repairMetadata, repairBulkComplete, repairBulkSkipped, repairBulkFailed
- Translated across all 9 supported languages
The autocomplete module cached the lora_syntax_format value at module load
but never updated it when the setting changed, causing autocomplete to
always insert legacy A1111 format even when 'full path' was configured.
- Expose refreshLoraSyntaxFormat() to re-fetch the setting from the API
- Listen for cross-tab 'storage' events to react to settings saved in
the standalone web UI
- Listen for 'visibilitychange' to refresh when the user switches back
to the ComfyUI tab
- Wire SettingsManager.saveSetting() to set a localStorage key when
lora_syntax_format changes, triggering the storage event
was missing the line to set the
select element's value from ,
causing the dropdown to always show the first option ("Full Path")
when reopening the settings modal, regardless of the persisted value.
Runtime behavior was unaffected since reads from
the state directly.
- Remove [LoRAs] prefix noise from conflict detail display
- Limit inline conflict groups to 5, show remainder count
- Add 'Switch to Full Path Syntax' action in conflict card
- Add confirmation modal before resolving conflicts (shows rename strategy)
- Register resolveFilenameConflictsModal in ModalManager (fix no-op showModal)
- Switch to Interface section and add highlight animation on syntax-format nav
- Sync and translate conflictConfirm strings across all 10 locales
Adds lora_syntax_format setting (full/legacy) that controls whether <lora:...> syntax uses relative paths (full) or filename only (legacy). Default is legacy for backward compatibility with A1111 convention. The full path format (<lora:relative/path/filename:strength>) enables lossless model resolution across subfolders.
Ultraworked with Sisyphus (https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Set addTags: true in recipes bulk action config
- Add _saveRecipeTags() helper using recipe API endpoint
- Replace mode: saves tags array directly via PUT recipe/update
- Append mode: merges with existing tags from virtual scroller
- Shows bulk Add Tags modal & target menu item on recipes page
- ModelModal (ModelTags.js): auto-focus input on entering tag edit mode
- ModelModal (ModelTags.js): flush uncommitted input text as tag on Save
- Bulk Add Tags (BulkManager.js): same two fixes
- RecipeModal already handled both cases correctly
- 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.
Add multi-URL batch download support to the download modal.
Users can paste multiple CivitAI URLs (one per line) in a textarea,
preview all parsed models in a compact list, optionally change versions
per model, select a unified download path, and batch download sequentially.
Single URL behavior is preserved unchanged.
Changes:
- Replace single-line input with textarea for multi-URL input
- Add batch preview step with compact list (thumbnail, version, size)
- Per-item version editing via existing version selector
- Batch download with WebSocket progress tracking (reuses existing infra)
- URL deduplication by model ID, preserving paste order
- Invalid URLs shown inline with remove option
- Fix: prevent click listener accumulation in showVersionStep
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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
The bulk delete confirmation modal always displayed "models" in its
text (title, message, countMessage) regardless of the current page
type. On the recipes page this is misleading since users are managing
recipes, not models.
- Add bulkDeleteRecipes i18n keys to all 10 locale files
- Update showBulkDeleteModal() to detect currentPageType and use
recipes-specific wording when on the recipes page
Adds a compact inline toggle in the Generation Parameters section of the
Recipe Modal that, when enabled, strips <lora:name:weight> tags and
cleans up residual punctuation before copying to clipboard. The setting
persists across sessions via localStorage.
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.
Group 15 flat menu items into 5 logical sections (Workflow, Metadata,
Attributes, Organize, Download) with section headers to reduce cognitive
load. Nest the three workflow-related actions (Append, Replace, Copy
Syntax) into a single "Send to Workflow" hover-triggered submenu.
Add submenu infrastructure to BaseContextMenu with mouseover/mouseout
boundary detection, 250ms close delay, and viewport-aware positioning.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Allows downloading example images only for selected models instead of
the entire library. Reuses the existing /api/lm/force-download-example-images
endpoint which already accepts an array of model hashes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add MediaViewer overlay for full-size image/video display with prev/next
navigation, direction keys, counter, and adjacent preloading
- Recipe modal: click preview image/video opens full-size viewer
- Model showcase: click any example image/video opens viewer with full
gallery navigation; blurred NSFW content opens directly to clear view
- Use Map<Element, number> for DOM-index mapping instead of URL comparison
to avoid index mismatch from lazy-loaded vs data-attribute URLs
- Ernie & Anima: auto-fetched via CivitaiBaseModelService from Civitai API
- Ernie Turbo & Nucleus: pre-added as hardcoded constants (not yet in Civitai API)
- Added abbreviations (ERNI, ETRB, NUCL) and category entries across all layers
Replaces two separate menu items with a single smart item that dynamically
switches between 'Set as Favorite' and 'Remove from Favorites' based on
whether all selected items are already favorited. Shows a count badge
'(3/5)' when only some items are favorited in a mixed selection.
Supports all model types (LoRA, Checkpoint, Embedding) and recipes via
existing per-item save/update API — no backend changes needed.
The model-level API (GET /api/v1/models/{id}) does not include usageControl
on version entries, causing generation-only models to show as downloadable.
Backend changes:
- Add get_model_versions_by_hashes() to CivitaiClient (POST by-hash batch)
- Propagate through all provider classes including RateLimitRetryingProvider
- Add _enrich_version_entries() pipeline: extract SHA256 from files[].hashes,
batch-call by-hash endpoint, inject usageControl+earlyAccessEndsAt in-place
- Wire enrichment into both bulk (_fetch_model_versions_bulk) and individual
(_refresh_single_model) refresh paths
- Fix _build_record_from_remote dropping usage_control field
- Fix POST by-hash request format (plain JSON array, not {hashes:[...]} object)
Frontend changes:
- Fix disabled download button tooltip: wrap in <span> since HTML title
attribute does not fire on disabled elements
Five entry points that trigger recipe page reloads were not passing
preserveScroll: true, causing the page to snap back to top after
filtering, searching, or navigating folders — especially painful with
hundreds of recipes.
- RecipePageControls.resetAndReload() → refreshVirtualScroll() now
passes { preserveScroll: true } (sidebar folder clicks/drag moves)
- FilterManager applyFilters/clearAllFilters → loadRecipes(true)
changed to loadRecipes({ preserveScroll: true })
- SearchManager performSearch → loadRecipes(true) changed to
loadRecipes({ preserveScroll: true })
- SettingsManager reloadContent → loadRecipes() changed to
loadRecipes({ preserveScroll: true })
The normalizeLoadRecipesOptions boolean path always forces
preserveScroll: false — the object form is required to pass it.
- Add dynamic column calculation based on container width and min card width
- Prevent tiny cards on narrow windows by respecting density-based minimums:
- Default: 240px, Medium: 200px, Compact: 170px
- Fix edge-to-edge layout with proper CSS selector (.virtual-scroll-item.model-card)
- Add hamburger menu for mobile/small screens with proper translations
- Update all locale files with 'common.actions.menu' key
Fixes: Cards becoming too small/overlapping on narrow window widths (e.g., 1156px)
Changes: 15 files, +569/-114 lines
Handle models that are only available for on-site generation (usageControl:
"Generation" or "InternalGeneration") rather than downloadable.
Backend changes:
- Add usage_control field to ModelVersionRecord dataclass
- Extract usageControl from Civitai API responses
- Filter non-downloadable versions from update availability checks
- Add database schema migration for usage_control column
- Include usageControl in version response JSON
Frontend changes:
- Add isDownloadAllowed() helper function
- Show disabled download button for non-downloadable versions
- Add "On-Site Only" badge for restricted versions
- Update resolveUpdateAvailability() to filter non-downloadable versions
- Add CSS styling for disabled action button
Internationalization:
- Add translations for onSiteOnly badge and downloadNotAllowedTooltip
- Complete translations for all 10 supported languages
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>