ui_gallery_viewer.js (8c044d0) imported formatSize from
ui_model_download.js, which leads through the settings page and
browser.js back to ui_ai_workbench.js; with ui_ai_images.js ->
ui_gallery_detail.js -> ui_gallery_viewer.js at the start of that chain,
ui_ai_workbench.js called setMarker before ui_ai_images.js had run, and
the whole plugin failed to load ("Cannot access 'marker' before
initialization"). formatSize now lives in file_size.js, which imports
nothing, and every module takes it from there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
105 lines
4.8 KiB
JavaScript
105 lines
4.8 KiB
JavaScript
/**
|
|
* The Tidy tab's "Not used lately" part: the models by when they were last used (usage_view.js
|
|
* runs the check). Models picked here go to the Recycle Bin (model_placement.js). Node packs are
|
|
* in Settings → Node packs (ui_pack_usage.js); a line here leads there.
|
|
*/
|
|
|
|
import { translate as t } from './locales.js';
|
|
import { recycleModel } from './model_placement.js';
|
|
import { formatSize } from './file_size.js';
|
|
import { openSettingsPage } from './ui_settings_page.js';
|
|
import { button, el, lastUsed, notUsedSince, usageSection } from './usage_view.js';
|
|
|
|
const SHOWN = 100; // rows drawn at most; the rest is counted
|
|
|
|
const keyOf = (item) => `${item.type}|${item.root}|${item.rel}`;
|
|
|
|
function cover(item) {
|
|
const box = el('div', 'anomalous-tidy-cover is-small');
|
|
if (item.preview_url) {
|
|
const image = el('img');
|
|
image.loading = 'lazy';
|
|
image.alt = '';
|
|
image.src = `${item.preview_url}&variant=${/\.(mp4|webm|mov|avi)(&|$)/i.test(item.preview_url) ? 'poster' : 'card'}`;
|
|
box.append(image);
|
|
} else {
|
|
box.append(el('span', 'anomalous-tidy-cover-none', t('noPreview')));
|
|
}
|
|
return box;
|
|
}
|
|
|
|
function modelRow(view, item, now, redrawFoot) {
|
|
const row = el('label', 'anomalous-usage-row');
|
|
const pick = el('input');
|
|
pick.type = 'checkbox';
|
|
pick.checked = view.picked.has(keyOf(item));
|
|
pick.onchange = () => {
|
|
if (pick.checked) view.picked.add(keyOf(item));
|
|
else view.picked.delete(keyOf(item));
|
|
redrawFoot();
|
|
};
|
|
const body = el('div', 'anomalous-usage-body');
|
|
const name = el('div', 'anomalous-tidy-name');
|
|
name.append(el('strong', '', item.name));
|
|
if (item.version) name.append(el('span', 'anomalous-card-version anomalous-tidy-version', item.version));
|
|
body.append(name, el('div', 'anomalous-tidy-file', `${item.type} / ${item.rel} · ${formatSize(item.size)}`),
|
|
el('div', 'anomalous-usage-when', lastUsed(item, now)));
|
|
if (item.saved) body.append(el('div', 'anomalous-usage-saved', t('tidyUsageSaved', { count: item.saved })));
|
|
row.append(pick, cover(item), body);
|
|
return row;
|
|
}
|
|
|
|
function modelList(section, view, onRecycled) {
|
|
const { models, now } = view.data;
|
|
const listed = models.filter(item => notUsedSince(item, view.filter, now)).sort((a, b) => b.size - a.size);
|
|
const foot = el('div', 'anomalous-usage-foot');
|
|
const recycle = button('anomalous-scan-primary anomalous-scan-small-btn', t('tidyUsageRecycle'), async () => {
|
|
const picked = listed.filter(item => view.picked.has(keyOf(item)));
|
|
const size = picked.reduce((sum, item) => sum + item.size, 0);
|
|
if (!picked.length || !confirm(t('tidyUsageConfirm', { count: picked.length, size: formatSize(size) }))) return;
|
|
recycle.disabled = true;
|
|
const gone = [];
|
|
try {
|
|
for (const item of picked) {
|
|
await recycleModel(item);
|
|
gone.push(keyOf(item));
|
|
}
|
|
} catch (error) {
|
|
console.warn('[AMB] Tidy: could not recycle a model.', error);
|
|
alert(t('tidyUsageRecycleFailed'));
|
|
}
|
|
view.data.models = models.filter(item => !gone.includes(keyOf(item)));
|
|
view.picked.clear();
|
|
onRecycled(t('tidyUsageRecycled', { count: gone.length, size: formatSize(picked.filter(item => gone.includes(keyOf(item))).reduce((sum, item) => sum + item.size, 0)) }));
|
|
});
|
|
const summary = el('span', 'anomalous-download-note');
|
|
const drawFoot = () => {
|
|
const picked = listed.filter(item => view.picked.has(keyOf(item)));
|
|
summary.textContent = picked.length
|
|
? t('tidyUsagePicked', { count: picked.length, size: formatSize(picked.reduce((sum, item) => sum + item.size, 0)) })
|
|
: t('tidyUsagePickHint');
|
|
recycle.disabled = !picked.length;
|
|
};
|
|
drawFoot();
|
|
foot.append(summary, recycle);
|
|
|
|
if (!listed.length) section.append(el('div', 'anomalous-tidy-done', t('tidyUsageNone')));
|
|
else {
|
|
section.append(...listed.slice(0, SHOWN).map(item => modelRow(view, item, now, drawFoot)));
|
|
if (listed.length > SHOWN) {
|
|
const rest = listed.slice(SHOWN);
|
|
section.append(el('div', 'anomalous-tidy-file', t('tidyUsageMore', { count: rest.length, size: formatSize(rest.reduce((sum, item) => sum + item.size, 0)) })));
|
|
}
|
|
section.append(foot);
|
|
}
|
|
}
|
|
|
|
/** The part under the Tidy tab's checks; `onRecycled(note)` after models went to the Recycle Bin. */
|
|
export function renderUsageSection(owner, onRecycled) {
|
|
return usageSection(owner, {
|
|
titleKey: 'tidyUsageTitle', leadKey: 'tidyUsageLead', redraw: () => owner.loadModels(),
|
|
aside: button('anomalous-usage-elsewhere', t('tidyUsagePacksElsewhere'), () => openSettingsPage(owner, { tab: 'packs' })),
|
|
body: (section, view) => modelList(section, view, onRecycled),
|
|
});
|
|
}
|