Files
DemonGatanjieu-Anomalous_Mo…/web/modules/usage_view.js
T
DemonGatanjieuandClaude Opus 5.5 396b2a1489 Move the node packs not used lately to Settings → Node packs
Node packs are ComfyUI's extensions, not material, so looking for them
under the models page's Tidy tab felt odd. Tidy keeps the models and a
line leading to the new Settings tab; both pages share the check and
its head (usage_view.js), ui_pack_usage.js draws the packs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:25:04 +08:00

110 lines
4.8 KiB
JavaScript

/**
* What the two "not used lately" pages share (api/model_usage.py): the check, run on a press
* and kept in `owner.modelUsage` ({data, filter, picked, running, error}) for both — the models
* under the models page's Tidy tab (ui_model_usage.js) and the node packs in Settings → Node
* packs (ui_pack_usage.js) — and the head of each: title, how it judges, the 30 / 90 days /
* never groups and what the run record can vouch for.
*/
import { translate as t } from './locales.js';
import { checkUsage } from './model_placement.js';
import { noteOpenWorkflows } from './usage_notes.js';
const DAY = 24 * 3600;
const NEVER_DAYS = 30; // "never used" only for what has been here this long
const FILTERS = [['30', 'tidyUsage30'], ['90', 'tidyUsage90'], ['never', 'tidyUsageNever']];
export function el(tag, className, text) {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
}
export function button(className, label, onClick) {
const node = el('button', className, label);
node.type = 'button';
node.onclick = onClick;
return node;
}
export function usageState(owner) {
owner.modelUsage ||= { data: null, filter: '90', picked: new Set(), running: false, error: false };
return owner.modelUsage;
}
export const daysSince = (seconds, now) => Math.floor((now - seconds) / DAY);
const threshold = (filter) => (filter === 'never' ? NEVER_DAYS : Number(filter)) * DAY;
/** Whether `item` ({last_used, saved, added}) is listed under `filter` ('30' | '90' | 'never'), as
* of `now`: idle since its last use or its arrival, whichever is later; nothing new is judged. */
export function notUsedSince(item, filter, now) {
const since = Math.max(item.last_used || 0, item.added || 0);
if (now - since < threshold(filter)) return false;
return filter !== 'never' || (!item.last_used && !item.saved);
}
export function lastUsed(item, now) {
if (!item.last_used) return t('tidyUsageNoOutput');
const date = new Date(item.last_used * 1000).toLocaleDateString();
return t('tidyUsageLast', { date, days: daysSince(item.last_used, now) });
}
/** What the run record can vouch for under `filter`: a line, a warning while it is younger than that. */
function ledgerNote(since, filter, now) {
if (!since) return el('div', 'anomalous-usage-ledger is-short', t('tidyUsageLedgerNone'));
const values = { date: new Date(since * 1000).toLocaleDateString(), days: daysSince(since, now) };
const short = now - since < threshold(filter);
return el('div', `anomalous-usage-ledger${short ? ' is-short' : ''}`, t(short ? 'tidyUsageLedgerShort' : 'tidyUsageLedger', values));
}
async function run(owner, redraw) {
const view = usageState(owner);
view.running = true;
view.error = false;
redraw();
try {
await noteOpenWorkflows();
view.data = await checkUsage();
view.picked.clear();
} catch (error) {
console.warn('[AMB] Tidy: the usage check failed.', error);
view.error = true;
} finally {
view.running = false;
redraw();
}
}
/** A "not used lately" page: its head (`aside`, if any, under the lead), then `body(section, view)`
* once the check has an answer; `redraw()` draws the page again. */
export function usageSection(owner, { titleKey, leadKey, aside = null, redraw, body }) {
const view = usageState(owner);
const section = el('div', 'anomalous-usage');
const head = el('div', 'anomalous-tidy-step');
head.append(el('h3', '', t(titleKey)));
section.append(head, el('p', 'anomalous-tidy-why', t(leadKey)));
if (aside) section.append(aside);
if (view.running) {
section.append(el('div', 'anomalous-download-text', t('tidyUsageRunning')));
return section;
}
if (view.error) section.append(el('div', 'anomalous-download-text is-bad', t('tidyUsageFailed')));
if (!view.data) {
section.append(button('anomalous-scan-primary anomalous-scan-small-btn', t('tidyUsageRun'), () => run(owner, redraw)));
return section;
}
const { seen, now, ledger_since: since } = view.data;
const filters = el('div', 'anomalous-usage-filters');
for (const [value, key] of FILTERS) {
const chip = button('anomalous-model-base-chip', t(key), () => { view.filter = value; redraw(); });
chip.setAttribute('aria-pressed', String(view.filter === value));
filters.append(chip);
}
filters.append(button('anomalous-scan-row-btn', t('tidyUsageAgain'), () => run(owner, redraw)));
section.append(filters, ledgerNote(since, view.filter, now),
el('div', 'anomalous-tidy-file', t(seen.capped ? 'tidyUsageSeenCapped' : 'tidyUsageSeen', { outputs: seen.outputs, saved: seen.saved })));
body(section, view);
return section;
}