Files
DemonGatanjieu-Anomalous_Mo…/web/modules/model_placement.js
T
DemonGatanjieuandClaude Opus 5.5 f70d890e61 Tidy: models and node packs not used lately
A Bilibili user asked for a way to clear out models and node packs they
tried once and no longer use, without breaking other workflows.

api/model_usage.py (GET /anomalous/usage/check, this computer only, run
on a press): when each model and node pack was last used, from the PNG
outputs' prompts (image_search's cached records now keep node types) and
the saved ComfyUI workflows, recipes, combos and parameter sets; models
match by path, file name or scanned SHA-256; reading is capped and
nothing is deleted there.

ui_model_usage.js under Tidy's two steps: not used for 30 / 90 days or
never, biggest first, the last output's date, a warning when a saved
workflow still names a model; picked models go to the Recycle Bin. Node
packs are only listed, with a button that opens the installed ComfyUI
Manager's node list (Manager 4 or 3, probed by its own routes; without
one it says so).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:28:44 +08:00

73 lines
3.0 KiB
JavaScript

/**
* The models page's Tidy check (api/model_placement.py): models in the wrong folder and
* identical copies; moving a model to where it belongs, a copy to the Recycle Bin. No DOM.
* The type bar's count is asked once and kept until something changes the folders.
*/
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, 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 });
/** 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;
}
/** 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;
}