Files
DemonGatanjieuandClaude Opus 5.5 c30180cb8d Fix Tidy misjudging Anima LLLite and IP-Adapter files
An Anima LLLite in controlnet is not misplaced while the ComfyUI-Anima-LLLite pack
(which reads it from there) is loaded, and identical copies kept one per loader folder
(controlnet and model_patches) are listed as shared, both in use, not as duplicates to
recycle. An IP-Adapter carrying LoRA layers (the Anima IP-Adapter) is no longer read as a
LoRA. The header cache keeps the new "form" field (older entries are read again).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:27:49 +08:00

92 lines
3.9 KiB
JavaScript

/**
* The models page's Tidy check (api/model_placement.py): models in the wrong folder and
* identical copies; what one model's header says it is; moving a model to where it belongs (or
* to another type, ui_model_retype.js), a copy to the Recycle Bin, and the words for a move
* that failed. No DOM. The type bar's count is asked once and kept until something changes
* the folders.
*/
import { translate as t } from './locales.js';
let counted = null;
async function json(url, options) {
const response = await fetch(url, options);
const data = await response.json().catch(() => ({}));
if (!response.ok) {
const error = new Error(data.error || `HTTP ${response.status}`);
error.code = data.error || 'http';
throw error;
}
return data;
}
const post = (url, body) => json(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
/** { misplaced, duplicates, shared, unchecked }; `deep` also reads same-size files never scanned. */
export const checkPlacement = (deep = false, signal) => json(`/anomalous/placement/check${deep ? '?deep=1' : ''}`, { signal });
/** { models, packs, seen, now }: what was not used lately (api/model_usage.py); reads every output. */
export const checkUsage = (signal) => json('/anomalous/usage/check', { signal });
/** Marks a node pack as still used (`kept`), or not: { kept: [names] }. */
export const keepPack = (pack, kept) => post('/anomalous/usage/keep', { pack, kept });
/** The misplaced models the Tidy tab lists: not those that load where they are, nor an extra
* copy of a file that is in the right folder (the identical files step has it). */
export function tidyMisplaced(data) {
const key = (item) => `${item.type}|${item.root}|${item.rel}`;
const extra = new Set(data.duplicates.filter(group => group.files.some(file => file.fits))
.flatMap(group => group.files.filter(file => !file.fits).map(key)));
return data.misplaced.filter(item => !item.works && !extra.has(key(item)));
}
/** How many things the Tidy tab has to show: listed misplaced models and groups of identical files. */
export function tidyCount() {
counted ||= checkPlacement()
.then(data => tidyMisplaced(data).length + data.duplicates.length)
.catch(() => 0);
return counted;
}
/** The folders changed (an import, a move, a deletion): count again next time. */
export function forgetTidyCount() {
counted = null;
}
/** What the header of `item` ({type, root, rel}) says it is: {kind ('' when it cannot tell), sure, base}. */
export const lookModel = item => json(`/anomalous/placement/look?${new URLSearchParams({ type: item.type, root: String(item.root), rel: item.rel })}`);
/** Why a move or a removal failed, in words. */
export function moveErrorText(error) {
for (const key of [`importError_${error.code}`, `downloadError_${error.code}`]) {
const text = t(key);
if (text !== key) return text;
}
return error.message && error.code === 'unknown' ? error.message : t('importError_unknown');
}
/** Moves `item` ({type, root, rel}) to `to` ({type, root, rel}); resolves to where it went. */
export async function moveModel(item, to) {
const moved = await post('/anomalous/placement/move', {
type: item.type, root: item.root, rel: item.rel, to_type: to.type, to_root: to.root, to_rel: to.rel,
});
forgetTidyCount();
return moved;
}
/** Sends one copy ({type, root, rel}) with its covers and info to the Recycle Bin. */
export async function recycleModel(item) {
const cut = item.rel.lastIndexOf('/');
const data = await post('/anomalous/delete_model', {
type: item.type, path_idx: item.root, subfolder: cut < 0 ? '/' : `/${item.rel.slice(0, cut)}`, filename: item.rel.slice(cut + 1),
});
if (data.status !== 'success') {
const error = new Error(data.message || 'failed');
error.code = 'unknown';
throw error;
}
forgetTidyCount();
return data;
}