- Badges are pure status indicators with tooltips; remediation moves to a
per-item action row (Download / Reconnect), matching the versions-tab
badge/button pattern
- Civitai link inlines with the model title; the action row renders only
when real actions exist, removing empty-row whitespace
- Single-LoRA download resolves identifiers from hash on demand (same
fallback as the bulk missing-download flow) and shows immediate
'Preparing download' feedback while resolving
- Successful downloads (LoRA and checkpoint) refresh the resources
section and the recipe card in place, mirroring the bulk flow
- Row navigation is limited to in-library items with keyboard support;
checkpoint type renders as muted text instead of a chip; badges use
tonal styling; the local-path hover tooltip is removed
- Add resourceItems frontend tests and translate the new keys for all
10 locales
- Recipe card: compact status pill with state icon + available/total
fraction (e.g. "2/3"), pinned to the footer bottom-right like model
card actions; status is encoded by icon + color, never color alone
- Recipe modal: "N missing" is now a real <button> with a persistent
border, leading download icon, focus-visible ring and aria-label;
clicking opens the download-missing flow
- Fix context menu missing-LoRA detection selector after badge refactor
- i18n: add recipes.status/loraStatus keys with translations for all
10 locales, and fill pending rate-limit translations
Bulk delete merged staged batches by physically moving each loser's
files into the winner's batch dir with os.rename. Cross-volume bulks
(winner and loser on different filesystems) always hit EXDEV, forcing a
rollback and degrading to the batch_ids array with per-batch undo.
Merge is now manifest-only: loser entries are appended to the winner's
manifest with their staged paths unchanged, so staged files keep living
in each model's own .lm-pending-delete/<batch_id> dir (no data IO, no
EXDEV). Loser dirs are recorded in the winner manifest's merged_sources
and each loser manifest is stamped merged_into so its own purge timer, a
post-restart sweep or a direct undo call no-op. A cross-volume bulk is
one undoable batch again, and undo/purge clean up the loser dirs once
the merged batch settles.
Stop resetting the whole listing after a successful download. The legacy
flow reloaded page 1, scrolled to the top and hijacked the sidebar's
active folder whenever a custom target folder was used, which made the
Updates view lose its place (and sometimes render as an empty page).
Downloads only flip the update flag for one model, so the listing is now
reconciled in place through the virtual scroller:
- Updates view: the model's cards are removed once its newest eligible
version is installed (the flag is model-level).
- Normal listing: the card stays; only update_available is cleared.
- Model not in the current view (different folder/filter/window):
no-op; the sidebar folder tree alone is refreshed.
- Falling back to the legacy reload only when no virtual scroller is
available (e.g. recipes page, duplicates mode, HF downloads).
- Extract a shared BaseModelPicker (search, keyboard navigation,
filename-based suggestions, dynamic API models such as MiniMax H3
under 'Other (API)') used by both the single-model metadata modal
and the bulk base model modal
- Rework the bulk base model modal into a dedicated inline-list
layout: fixed modal size, sticky-free footer with app-standard
modal-actions/primary-btn/cancel-btn buttons, and an inline option
list that scrolls itself instead of an overlay dropdown covering
the footer
- Selecting an option in change mode now filters the list to the
selection instead of resetting and scroll-jumping to it
- Restore opaque sticky section headers in the bulk modal so scrolled
items no longer bleed through
Phase 2 of docs/plans/issue-1085-rate-limit-design.md:
- Batch import: items that fail due to vendor rate limiting are now
SKIPPED with a "re-run the import later" hint instead of FAILED, so a
transient 429 no longer pollutes failure accounting; the progress
broadcast carries a rate_limited flag.
- Batch import UI: show a one-time "rate limited — slowing down" toast
and swap the running status text while rate_limited; i18n keys synced
to all locales.
- Downloader: download_file / download_to_memory / get_response_headers
register 429 cooldowns with the RateLimitCoordinator, so subsequent
API calls queue behind a download-triggered rate-limit window.
Implement Phase 1 of docs/plans/issue-1085-rate-limit-design.md:
- New RateLimitCoordinator: per-host shared Retry-After gate with
exponential backoff (30s base, 1800s cap), minimum inter-request pacing
(default 0.75s), herd-free waiter serialization via per-destination
locks, and a bounded wait (default 300s) that raises instead of parking.
- Downloader.make_request: connectivity-guard fail-fast first, then gate
pacing; on 429 register the cooldown and wait-and-resend (bounded);
errors that passed through the gate are marked gate_handled.
- FallbackMetadataProvider / MetadataSyncService: a network provider 429
no longer fails over to other network providers (stops the CivArchive
flood); sqlite stays as local last resort. Rate-limited lookups now
report "Rate limited" instead of "Model not found", so transient 429s
no longer mark models civitai_deleted.
- _RateLimitRetryHelper skips its own sleep for gate_handled errors,
removing the double wait.
- New settings: rate_limit_gate_enabled, rate_limit_max_wait_seconds,
rate_limit_min_interval_seconds.
Address the rate-limit flood and secondary errors seen during large
recipe ingestion (example-images directory import):
- batch import: share one adaptive-concurrency semaphore across the whole
batch (previously each item got a fresh semaphore, so the min/max
concurrency bounds never applied and every item ran concurrently);
synchronize the shared semaphore capacity after each completed item.
- comfy parser: guard ckpt_name against list/None values so re.search no
longer raises TypeError and fails the whole image import.
- civarchive client: normalize empty-string failure payloads to
"Request failed" and treat a missing payload as an error, fixing the
"'NoneType' object has no attribute 'get'" crash.
- civarchive client: log connectivity-guard offline-cooldown
short-circuits at DEBUG instead of one ERROR per request.
Add LORA_MANAGER_SETTINGS_DIR env var and standalone --settings-path to pin
the settings location (settings.json, cache/, wildcards/, backups/, logs/,
stats/) to an arbitrary directory. The override takes precedence over
portable mode and the platform user config dir, and skips legacy migration,
so sandboxed dev/E2E runs no longer need to write settings.json in the repo
root or collide with the real instance.
standalone.py pre-scans argv for --settings-path at import time because the
settings location is resolved before main() parses arguments. SettingsManager
portable-switch migration is a no-op while the directory is pinned.
Update the lora-manager-e2e skill (prefer --settings-path sandboxing;
start_server.py passes it through) and the lora-manager-runtime-context
skill (document precedence; inspect script honors the override).
The browser extension's apiFetch treats any 404 as a missing endpoint and
retries the legacy non-/api/lm URL, producing two spurious
'error_middleware - WARNING - API GET ... 404' log lines per occurrence.
- complete_download_in_queue / update_download_queue_status /
retry_download_from_history: 'not found' is a normal business outcome,
return 200 + success:false instead of 404 (extension behavior unchanged;
apiGet ignores the HTTP status)
- error_middleware: downgrade /api/lm/download-progress/ 404s to debug like
previews - the 404 status itself stays (extension uses it for failure
detection), only the log level is lowered
Old workflows (saved before the control_after_generate feature) carry a
shorter widgets_values array. The frontend's index-based widget restore
then shifts the old weight_dtype value into the hidden control widget
(leaving an invalid value like 'default') and silently resets
weight_dtype to its default. On graph load, hand the shifted value back
to weight_dtype when it still sits at its default, then reset the
control mode to 'fixed' so old workflows keep loading deterministically.
- Log expected "GID not found" tellStatus probes at DEBUG, and treat a
forgotten GID as permanent so the poll loop recovers immediately
instead of burning 4 retries x 3s of ERROR lines per cycle
- cancel_download tolerates a forgotten GID and always pops the
in-memory transfer so concurrent polls cannot re-register a
cancelled download
- Restore sweep deletes aria2 state records with no resolvable target
path instead of skipping them forever
- Clearing the download queue now also cancels in-memory tasks, removes
live aria2 transfers and drops persisted state for the cleared ids
(partial files on disk are preserved)
After a drag move empties the selected folder, refresh() resets the
stale activeFolder to root but the grid kept showing the old filtered
(empty) view until a manual reload. Trigger resetAndReload when the
fallback happens post-initialization; the initial page load is untouched
because it picks up the cleared filter on its own.
initialization.js falls back to polling /api/lm/init-status when the
/ws/init-progress WebSocket cannot be established, but no route ever
registered that path — each poll 404'd and the page never reloaded after
the scan completed. Report the aggregate status of all four scanners and
omit pageType so every initialization page accepts the update.
- Add Tag Autocomplete ON/OFF entry to the Prompt (LoraManager) node
right-click menu, cross-referencing the slash commands
- Show the current autocomplete state (/autocomplete or /noautocomplete
hint) below the slash command list
- Show a one-time dismissible tip in the suggestion dropdown on first use
- Clarify toggle command labels (Turn autocomplete ON/OFF) and cross-link
all three entry points in the settings tooltip
- Share the setting write path via setLoraManagerSettingValue()
With trust_env=True, aiohttp auto-loads credentials from ~/.netrc (e.g. a
'machine civitai.red' or 'default' entry) and refuses to combine them with
the explicit Authorization: Bearer header, aborting every authenticated
CivitAI request with 'Cannot combine AUTHORIZATION header with AUTH
argument or credentials encoded in URL'.
The version branch of check-model-exists now returns
downloadedFiles: [{fileId, fileName, filePath}] so clients (e.g. the
browser extension) can tell a partially downloaded version apart from a
fully downloaded one. Reuses ModelCivitaiHandler._match_downloaded_files
(D2 rule) against the local cache; unmatchable local files are reported
with fileId: None. No CivitAI API call added.
Distinct files of the same model version queued before a backend restart
were silently collapsed by deduplicate(), which grouped rows by
(model_id, model_version_id) only. Extract the file id from file_params
via json_extract and add it to the dedup key; rows without file identity
keep the old per-version behavior (NULL matches NULL).
Consolidate the duplicate name-matching logic into ModelScanner:
find_matching_models is now the single core, using each scanner's own
file_extensions for suffix stripping. get_model_info_by_name gains
require_unique/base_model kwargs while legacy route behavior is kept
byte-identical. reconnect_lora passes the recipe base model as a guard
and distinguishes ambiguous, base-model-mismatched, and missing LoRAs
in its error messages.
The Checkpoint/Unet Loader (LoraManager) nodes now support ComfyUI's
built-in control_after_generate mechanism on the ckpt_name/unet_name combos,
letting users pick a random model on every queue with the selected model
written back into the widget (visible, and lockable via the 'fixed' mode).
A base_model input narrows the random pool: a front-end extension fetches
the name/base_model mapping from the new /api/lm/checkpoints/loader-pool
endpoint and filters the combo options, wired through the node callback,
the refreshComboInNodes extension hook, and a graph.onConfigure hook
installed from onAdded (onNodeCreated fires before the node is attached to
a graph, so the graph reference is unavailable there).
Tag move-to-folder drags with a custom dataTransfer MIME type so card
preview-drop handlers skip them entirely (no highlight, no upload), and
mark the preview image non-draggable so the browser no longer synthesizes
a File payload when a drag starts on the image. Fixes card-on-card drops
and click-jitter self-drops replacing the preview with itself.
- Three-column layout (preview | generation parameters | resources) with
independent per-pane scrolling and a content-sized modal shell that
shrinks to fit short recipes and caps at viewport height for long ones
- Blurred, darker backdrop to focus attention on the modal
- Preview frame hugs the image instead of a fixed-size box
- Move recipe-level 'Send to ComfyUI' into the header actions row to match
the model detail modal convention; remove the modal 'Copy Recipe Syntax'
button (context menu action is unaffected)
- Add recipes.actions.sendRecipe i18n keys with translations
- Sync modal test fixtures to the new structure