A user found model folders cluttered after scans: four files per model.
model_sidecars.py now says where the plugin's own side files are (.info,
.anomalous.json, .civitai_bak.*): beside the model, or in the folder's
hidden .amb (Settings -> Models -> Files that come with models). Every
reader and writer goes through it and reads both places; covers stay
beside the model for ComfyUI and other plugins.
Scans no longer keep a copy of Civitai's picture identical to the cover:
the cover is noted as Civitai's (SHA-256 in the .info) and a copy is kept
aside only while the user's own cover shows. api/sidecar_layout.py moves
existing files and drops the duplicates to the Recycle Bin. The folder
tree leaves .amb out; the duplicated tree builder and cover owner check
are shared now (model_catalog.py, backup.py back under 600 lines).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The scan summary marks new models whose safetensors header reads but names no drawing model
kind (a voice model such as F5-TTS) as not_drawing; the scan page asks: don't scan them,
scan them, or see each. The choice lives in <model>.anomalous.json ("scan": "skip" / "scan",
model_identity.scan_choice), saved by POST /anomalous/scan_choice (api/scan_choice.py, this
computer only, at most 2000 models, only readable .safetensors layers).
- "skip": a scan of a whole folder leaves the model out (picked by hand it is still scanned),
the summary counts it apart (excluded) with its own list to change it back, the card and the
details say "不扫描"; the details have the same button.
- The unmatched count warns only for damaged files or lookups still to do; not-found rows say
the fingerprint is kept for repairing workflows.
- model_kind.inspect_file now gives format None when the header cannot be read, as its text said.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On this line the header reading moved to model_kind.py, which took model_identity's struct import with it; the damaged-header check from feature/next needed both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A model Civitai has no record of now has its safetensors header checked (model_identity.
safetensors_damage: too short, header longer than the file, not JSON, or a tensor ending past
the file's end; only the header is read). Such files get the unmatched reason "damaged": a red
badge on the card, a line in the details, a warning on the scan page (the summary counts them)
and first place in the unmatched list, with advice to download the model again.
Tests: header cases, a scan telling a damaged file from a whole one; the scan report fixtures
are whole safetensors files now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Models page: "+ Import" chip and model files dropped on the grid open an import
window. model_kind.py tells a file's type (LoRA, checkpoint, VAE, text encoder,
diffusion model, ControlNet, embedding, CLIP Vision, upscaler) and base model from
its safetensors / GGUF header only; .ckpt/.pt are never opened. api/model_import.py
finds the dropped file in Downloads/Desktop (name, size, time) and moves it, or
takes an upload; checks it against Civitai and the scanned models; never replaces
a file (" (2)"). New files are scanned after.
- Model cards show the Civitai version name (metadata version_name).
- Settings page in tabs (Look, Models, Workflows, Prompts, Backup, Help); the
download folder is a choice of ready-made folders or typed by hand; default is now
by base model ({base}).
- The scanner's offline base model uses model_kind (LoRAs, Qwen, Wan, Chroma...).
- AD-019: no Civitai browser inside the plugin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
model_resolution imported calculate_sha256 and the header inference from
scraper.py, which only works when the plugin folder is on sys.path (the
tests); inside ComfyUI the import failed, so a renamed model the scan had
not hashed was reported missing and the route answered 500. Both
functions now live in model_identity.py, which the API and the scanner
already share; scraper.py keeps its printing wrapper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scan page now says which models are unmatched or not scanned yet (type, folder,
why Civitai had nothing) and what each scan did, model by model; rows open the model,
scan it, look it up again or search Civitai. The progress box shows the counts and,
when floating, a way to the result. The activity log records a scan when it ends with
its result, and model edits (from -> to) and deletions (files moved) in detail.
- scraper.py reports one JSON line per changed model (--report-file); a failing model
no longer ends the scan. Unmatched info keeps why (not_found / network / offline);
online scans look network/offline ones up again. After two models in a row without
an answer the rest of the scan infers from the files (--civitai-down for the next
folders) instead of waiting out every timeout.
- civitai_client.py: the network side moved out of scraper.py; tells a 404 from
Civitai being unreachable (CivitaiUnreachable).
- api/scan_report.py: ScanJob turns a scan's reports into one result, kept as the last
scan (/anomalous/last_scan) and logged; /anomalous/scan_model opens a listed model.
- /anomalous/scan_all takes picked or listed models as `targets`, so one scan has one
result; scan_summary lists unmatched and new models with their reason.
- Activity middleware takes details a handler attaches (request[ACTIVITY_DETAIL]).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A model's information now has three layers, the user's first:
<model>.anomalous.json (written only by the model editor), the scan result
(<model>.info, or another tool's .civitai.info with Civitai ids; id -1 =
inferred from the file) and the file itself. get_metadata reads the user
layer last, so it wins (an emptied field stays empty), and reports
info_source (civitai / local / "") and user_fields. Edits saved by earlier
versions in .civitai.info keep working.
- The editor writes .anomalous.json (atomically) instead of mixing edits
into .civitai.info, which other tools rewrite.
- scan_info_path: a .civitai.info holding only old edits is not a scan
result. Editing a never-scanned model used to make scans skip it for good.
- Covers: a cover is Civitai's only while byte-identical to
<model>.civitai_bak.*; any other cover is the user's and is never
replaced. "Fetch again" updates only Civitai's own cover, keeps names,
notes and covers, and asks before starting.
- A scan no longer downloads covers and model pages again for models whose
information is already on disk (it did for every model, every scan).
- The earlier "keep the editor's name" change in the scraper is reverted:
editor names never lived in .info; the CHANGELOG entry is corrected.
- .anomalous.json moves and goes to the Recycle Bin with its model.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5af9b6d was committed with only the removal of ui_scan_wizard.js staged;
this adds everything its message describes, so the branch works again:
- ui_scan_page.js / scan_runner.js / 18-scan-page.css: the scan page with
matched / unmatched / not-scanned counts, one scan button, "look up the
unmatched models again", progress in the page, Advanced options.
- api/scan_summary.py: GET /anomalous/scan_summary.
- scraper.py: --retry-unmatched (saved hash when the file is unchanged,
covers kept); display names set in the editor survive scans.
- api/scanner.py: one request-to-switch mapping (_scraper_flags) and one
cache-clearing helper for both scan routes.
- model_identity.py: sidecar_info / is_unmatched shared by scraper and API.
- Shell wiring (rail, Home, panel lists, close), the provenance switch in
Settings -> Model settings, locale keys, docs and CHANGELOG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>