Downloads failed with "No suitable file found in metadata" for models whose
only file uses newer CivitAI file types (e.g. 'Enhancement LoRA' for
Anima/AIR image-editing LoRAs) because the primary-file allowlist only
covered legacy types.
- unify the weights-type allowlist as MODEL_WEIGHT_FILE_TYPES
(py/utils/constants.py) and apply it across download, recipe and
metadata-refresh lookups
- mirror CivitAI's getPrimaryFile() semantics: prefer weights-type primary,
fall back to weights files, then trust CivitAI's primary flag (excluding
non-downloadable artifacts like Config/Archive/Workflow)
- mirror the allowlist in the frontend via shared isModelWeightFile() helper
- add regression tests for the Enhancement LoRA primary-file download,
primary-flag fallback and weights-over-non-weights-primary preference
Version-tab updates reused the current version's folder, so updating a LoRA
to a version with a different base model (e.g. Illustrious -> Anima) ignored
the download path template and landed in the old version's directory.
When the target version's base model differs from the current local version
and a path template is configured, re-resolve the template under the same
model root. The backend keeps an explicitly provided root when
use_save_dir_as_root is set, so regular downloads still use the default root.
- Remove delete_undo_enabled setting (backend default, frontend state,
settings modal UI, 10 locales); staged deletes with 30s undo are now
the only delete path and stale settings keys are silently ignored
- Remove the 1500ms delete-button arm delay (armDeleteButton) from all
delete modals; misclicks are recoverable via the undo toast
- Delete modal always shows the recoverable warning
- Log the first staged file path in staging log lines for easier support
Promote aria2 stderr lines that indicate disk write failures (e.g. the
'cause: No space left on device' line following 'Write disk cache flush
failure') from DEBUG to INFO so the root cause is visible in default logs,
including Windows-specific phrases (file locked by another process, sharing
violation). The same line is rate-limited to one INFO report per 60s window
and the report map is pruned on insert so repeated failures cannot spam the
log or grow memory. All other stderr output stays at DEBUG.
Fix ~790 basedpyright errors across the test suite:
- Type stub subclasses of real production classes with super().__init__()
- Add missing generic type arguments and Dict[str, Any] annotations
- Add None guards before subscript/member access
- Adapt tests to production API changes (removed dead handlers,
PersistentModelCache.get_default, _i18n_filter_added location)
- Read AutoV3 directly from the downloaded file's own file_info hashes
(no SHA256 cross-matching against version_info.files, so the value is
captured even when the API omits SHA256)
- Extract normalize_autov3() validation helper shared with the
sha256-matching autov3_from_civitai_files path
- Fall back to the embedded safetensors header hash at download
completion; mark '' (checked-unavailable) so the startup backfill
query (autov3 IS NULL) never revisits the row
- Clear archive-level AutoV3 for zip-extracted models so per-file
header resolution applies to every extracted model
- Three-state autov3 field (not-checked / checked-unavailable / 12-hex value)
in .metadata.json sidecars, in-memory ModelHashIndex, and SQLite
(models.autov3 column + autov3_index table) with column-presence migration
- Background self-terminating backfill for legacy rows: per-model-type
concurrency guard, executor-offloaded I/O, Civitai-first resolution
(SHA256-matched version file) falling back to the embedded safetensors
header hash
- Civitai-first propagation on metadata refresh, scan, and download paths;
reject the empty-string SHA256 placeholder and strip OneTrainer 0x prefix
- List API hash filters and hash index lookups accept 12-char AutoV3
- Cap safetensors header reads at 64 MiB to prevent crafted-file allocation
- Prevent stale AutoV3 mappings on file replacement while preserving them on
same-file re-registration (lazy-hash completion)
Add /af and /noaf toggle commands (plus /activefilters aliases) to the
loras autocomplete widget. When enabled (default off), suggestions are
matched within the active filters (folder, base model, tags, auto-tags,
license, tag logic) persisted by the LoRA Manager page in localStorage,
keeping the match pool consistent with the list endpoint, including the
global show_only_sfw setting.
Backend: /lm/{prefix}/relative-paths accepts the filter query params and
pre-filters the scanner cache with ModelFilterSet. The presence of the
recursive param signals the filter pipeline to run even without concrete
filters so global settings stay in parity with the list endpoint.
os.path.commonpath raises ValueError for paths on different Windows
drives. Treat that as no common root so cross-drive recipes migrations
succeed instead of failing with 'Invalid recipes path change'.
Split the single-model and bulk context menu actions into 'Download
Missing Example Images' (regular endpoint, skips already-processed
models) and 'Re-process Example Images' (force endpoint, retries
failed models).
- start_download accepts model_hashes so a selected subset can be
processed with the progress-aware skip logic; explicitly targeted
models bypass the failed/processed model-level guards so per-image
gaps are filled
- pre-download existence check in the processor skips network requests
for image files already on disk across all download paths
- force download retries previously failed models and clears their
failed status on success
- add i18n keys for the new menu items across all locales
Drop the SequenceMatcher-based fuzzy_match fallback that froze the server
when FTS returned empty results. FTS now returns empty set for zero results
(no fallback), and when the index is not yet ready, search returns empty
rather than scanning all items in Python.
LM Studio and some other OpenAI-compatible servers reject
response_format=json_object but accept json_schema. Switch to the
equivalent json_schema format and add a fallback that retries
without response_format when the provider rejects the format type.
- Add enable_civarchive_api toggle (default on) to allow disabling
CivArchive to avoid its rate-limit windows entirely
- Add metadata_provider_order dropdown with two presets:
CivitAI → CivArchive → Archive DB (default) and
CivitAI → Archive DB → CivArchive
- Wire both settings through backend (metadata_service, settings_manager,
misc_handlers) and frontend (SettingsManager, state, settings modal)
- Reorder Metadata section in settings modal: toggles → status/management
→ fallback order, for natural top-down workflow
- Make update_metadata_providers() log the effective provider chain
using actually-registered providers rather than settings assumptions
- Add 5 test cases covering all provider-combination paths
- Complete i18n translations for 6 new keys across all 9 non-English locales
- Add PersistentModelCache.update_single_model() for lightweight targeted
SQL update (single row + incremental tag/hash deltas, no full table scan)
- Add ModelScanner.sync_cache_from_metadata() with compare-first logic:
skips entirely when cache is already in sync; when stale, updates the
entry in-place (O(1) instead of O(n) remove+append), incrementally
adjusts tag counts/hash index/version index, and resorts only when
sort-relevant fields changed
- Wire sync_cache_from_metadata() into BaseModelService.get_model_metadata()
via fire-and-forget asyncio.create_task — disk I/O is already paid for
- Include identity re-validation guard against concurrent cache replacement
- Add 16 tests covering _cache_entries_differ, sync_cache_from_metadata
(no-change, in-place, fallback, conditional resort), and
update_single_model (insert, tag delta, hash delta)
When Civitai returns 429 (Too Many Requests) during example image
downloads, the previous behavior treated all failures identically and
permanently removed the corresponding images from model metadata —
making them impossible to retry.
This commit adds:
- 429 detection + Retry-After header parsing in download_to_memory
- Exponential backoff retry (up to 3 attempts) in
download_model_images_with_tracking
- Separate tracking of rate-limited vs permanently failed URLs
- rate_limited_models progress tracking persisted to disk
- Rate-limited models are NOT added to failed_models/processed_models
so they are automatically retried on subsequent download runs
- Force mode clears failed_models when rate-limited images exist
- Parse limit.output from model catalog alongside model IDs
for per-model max output token limits
- Use catalog lookup in chat_completion_json() to set max_tokens;
fall back to 4096 for unknown models (e.g. local Ollama)
- Remove the JSON retry (response_format → plain text fallback);
keep _try_salvage_json as last-resort for truncated responses
- Reduce Ollama num_ctx from 32768 to 8192 (sufficient for
metadata enrichment, saves VRAM)
- Fix stale test comment referencing removed retry
- Rename py/agent_cli/ -> py/metadata_ops/ (module was never agent-related)
- Rename tests/agent_cli/ -> tests/metadata_ops/
- Remove 9 low-value/debug INFO log points across agent_handlers.py,
agent_service.py, llm_service.py, and metadata_ops/__init__.py
- Keep LLM raw response at DEBUG level for diagnostics
- Consolidate per-model progress + LLM result into single concise
log line with basename instead of full path
- Update package/class/method docstrings to clarify this is a
pipeline infrastructure, not a true agent loop
- Add extract_gallery_images() to parse YAML widget entries from README
frontmatter, convert relative image URLs to absolute HF URLs, and
build civitai.images-compatible entries with prompt metadata
- LLM now extracts recommended_width/recommended_height from README
(e.g. "Best Dimensions"), used as gallery image dimensions
- extract_gallery_images() accepts default_width/height parameters,
falling back to 512x512 when LLM provides no recommendation
- Frontend ShowcaseView.js: defensive NaN guard for 0 width/height
- post_processor: consistently merge civitai updates across triggers,
description, and gallery blocks with distinct variable names
- SKILL.md: add recommended_width/recommended_height to output schema
- 62 tests pass, including gallery extraction and dimension tests
- Add identify_model_type() helper to determine lora/checkpoint/embedding
- Pass priority_tags from user settings to LLM prompt for tag relevance
- SKILL.md: instruct LLM to exclude technical/generic HF tags, cross-reference
against priority_tags; forbid ['None'] placeholder for trigger words
- post_processor: fix preview_url not updated after download (now writes local
.webp path to metadata); write trigger words to civitai.trainedWords instead
of top-level; sanitize ['None']/'null'/'n/a' placeholder values to []
- download_preview() now returns str | None (local path) instead of bool
- Update tests for new return type and nested civitai.trainedWords structure
Merge skill.yaml (metadata) and prompt.md (prompt template) into a
single SKILL.md file with YAML frontmatter, matching the agent-skill
convention used by opencode and Claude Code.
- Add frontmatter parser (_parse_skill_file) to SkillRegistry
- Remove skill.yaml, prompt.md, empty skills/__init__.py
- Remove obsolete load_handler method
- Update tests for new format and cleaned-up fields
Introduce an agent skill framework for LLM-driven metadata enrichment:
- AgentCLI (py/agent_cli/): in-process wrappers around internal services
using standard relative imports, eliminating the need for sys.path hacks
- LLMService: centralized BYOK (bring-your-own-key) LLM client supporting
OpenAI, Ollama, and custom OpenAI-compatible endpoints
- PostProcessor: deterministic engine that applies LLM output via AgentCLI
(replaces old handler.py + _BASE_MODEL_ALIASES approach)
- SkillRegistry: filesystem-based skill discovery (skill.yaml + prompt.md)
- AgentService: orchestrates skill execution with WebSocket progress
- Frontend AgentManager: WebSocket listeners, skill execution, config UI
- Context menu entries (single + bulk) for "Enrich Metadata (Agent)"
- Settings UI for AI Provider configuration (BYOK)
- Full i18n support across 9 locales
Bug fixes found during review:
- aiohttp.web.json_response: status_code= -> status=
- settings_modal cancelEditApiKey: wrong argument position
- AgentManager.isLlmConfigured: allow Ollama without API key
- PostProcessor._merge_tags: lowercase all tags to match TagUpdateService