Files
DemonGatanjieu-Anomalous_Mo…/web/modules/model_placement.js
T
DemonGatanjieuandClaude Opus 5.5 98148481bc Models page tabs, base model filter, Tidy tab redone with covers
- The models page has tabs: All models, Tidy (with its count) and Sources;
  the type bar keeps the type chips and Import.
- Base model chips under the type chips (All, Illustrious, SDXL, Unknown...),
  one per family the listed models have (model_bases.js; SDXL 1.0 = SDXL);
  models not scanned are listed by their header's guess (metadata base_guess).
- Tidy is cards with the model's cover and name in two steps: models in a
  folder whose loader cannot read them (from -> to, one plain reason, Move
  there, Change place), and identical files side by side with the copy to
  keep marked (the one in the right folder; click another to change) and one
  button for the extra copies. Models that load anyway and extra copies of a
  well-placed file are not in step 1. Same-size files up to 256 MB are
  compared at once; larger ones on Compare.

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

70 lines
2.8 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 });
/** 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;
}