diff --git a/web/modules/ui_scan_wizard.js b/web/modules/ui_scan_wizard.js
deleted file mode 100644
index c4e3de9..0000000
--- a/web/modules/ui_scan_wizard.js
+++ /dev/null
@@ -1,918 +0,0 @@
-/**
- * Scan wizard UI and scan launch/polling coordination.
- */
-
-import { app } from '../../../scripts/app.js';
-import { translate } from './locales.js';
-import { updateScanProgress, finishScanProgress, failScanProgress } from './scan_progress.js';
-import { showWorkbenchToast } from './ui_prompt_toast.js';
-
-const t = (key, params) => translate(key, params);
-
-const SCAN_RADAR_ICON_SVG = ``;
-
-export function setScanButtonState(btn, isScanning) {
- if (!btn) return;
- btn.innerHTML = SCAN_RADAR_ICON_SVG;
- btn.classList.toggle('anomalous-radar-spinning', Boolean(isScanning));
- btn.style.opacity = isScanning ? '0.85' : '1';
- btn.style.animation = '';
-}
-
-function setActiveScanButtonState(isScanning) {
- setScanButtonState(document.getElementById('anomalous-scan-btn'), isScanning);
-}
-
-export function openScanWizard({ isGlobal = false, targetFiles = null } = {}) {
- let wizard = document.getElementById('anomalous-wizard-modal');
- if (wizard) {
- if (typeof wizard.cleanupModal === 'function') wizard.cleanupModal();
- else if (wizard.parentNode) wizard.parentNode.removeChild(wizard);
- }
-
- wizard = document.createElement('div');
- wizard.id = 'anomalous-wizard-modal';
- wizard.style.position = 'fixed';
- wizard.style.top = '0';
- wizard.style.left = '0';
- wizard.style.width = '100vw';
- wizard.style.height = '100vh';
- wizard.style.backgroundColor = 'rgba(0,0,0,0.6)';
- wizard.style.zIndex = '999999';
- wizard.style.display = 'flex';
- wizard.style.justifyContent = 'center';
- wizard.style.alignItems = 'center';
- wizard.style.fontFamily = 'Roboto, "Segoe UI", sans-serif';
-
- const closeWizard = () => {
- document.removeEventListener('keydown', handleKeydown);
- if (wizard && wizard.parentNode) {
- wizard.parentNode.removeChild(wizard);
- }
- };
- wizard.cleanupModal = closeWizard;
-
- const handleKeydown = (e) => {
- if (e.key === 'Escape') closeWizard();
- };
- document.addEventListener('keydown', handleKeydown);
-
- wizard.onclick = (e) => {
- if (e.target === wizard) closeWizard();
- };
-
- const content = document.createElement('div');
- content.style.background = '#1E1E1E';
- content.style.borderRadius = '12px';
- content.style.padding = '28px 32px 20px 32px';
- content.style.width = '760px';
- content.style.maxWidth = '95%';
- content.style.maxHeight = '85vh';
- content.style.display = 'flex';
- content.style.flexDirection = 'column';
- content.style.boxShadow = '0 11px 15px -7px rgba(0,0,0,0.2), 0 24px 38px 3px rgba(0,0,0,0.14), 0 9px 46px 8px rgba(0,0,0,0.12)';
- content.style.color = '#fff';
- content.style.position = 'relative';
-
- // Top Header Area
- const headerArea = document.createElement('div');
- headerArea.style.display = 'flex';
- headerArea.style.justifyContent = 'space-between';
- headerArea.style.alignItems = 'center';
- headerArea.style.marginBottom = '24px';
-
- const title = document.createElement('h2');
- if (targetFiles) {
- title.textContent = t('sidebarSingleModelScan', { files: targetFiles });
- } else {
- title.textContent = t(isGlobal ? 'sidebarGlobalScanWizard' : 'sidebarScanWizardTitle');
- }
- title.style.margin = '0';
- title.style.fontSize = '1.6em';
- title.style.fontWeight = '500';
-
- // Toolbar right
- const topToolbar = document.createElement('div');
- topToolbar.style.display = 'flex';
- topToolbar.style.gap = '8px';
-
- const createGhostBtn = (icon, textKey, onClick) => {
- const btn = document.createElement('button');
- btn.textContent = `${icon} ${t(textKey)}`;
- btn.style.padding = '6px 12px';
- btn.style.background = 'transparent';
- btn.style.color = '#ccc';
- btn.style.border = '1px solid rgba(255,255,255,0.1)';
- btn.style.borderRadius = '6px';
- btn.style.cursor = 'pointer';
- btn.style.fontSize = '0.85em';
- btn.style.transition = 'all 0.2s';
- btn.onmouseover = () => { btn.style.background = 'rgba(255,255,255,0.08)'; btn.style.color = '#fff'; };
- btn.onmouseout = () => { btn.style.background = 'transparent'; btn.style.color = '#ccc'; };
- btn.onclick = onClick;
- return btn;
- };
-
- const apiKeyBtn = createGhostBtn('🔑', 'sidebarApiKey', () => {
- const newKey = prompt(t('sidebarApiKeyPrompt'), '');
- if (newKey !== null) {
- fetch('/anomalous/save_config', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ api_key: newKey.trim() })
- }).then(res => res.json()).then(data => {
- if (data.status !== 'ok') throw new Error(data.message || 'Save failed');
- alert(t('sidebarKeySaved'));
- }).catch(error => alert(t('sidebarSaveFailed') + error.message));
- }
- });
-
- topToolbar.appendChild(apiKeyBtn);
-
- headerArea.appendChild(title);
- headerArea.appendChild(topToolbar);
- content.appendChild(headerArea);
-
- let scanMode = 'civitai';
- let enableRename = true;
- let enableAutoCheck = true;
- let enableForceOverwrite = false;
- let updateSections = () => { };
-
- const formGroup = document.createElement('div');
- formGroup.style.display = 'flex';
- formGroup.style.flexDirection = 'column';
- formGroup.style.gap = '24px';
- formGroup.style.overflowY = 'auto';
- formGroup.style.flex = '1';
- formGroup.style.minHeight = '0';
- formGroup.style.paddingRight = '6px';
-
- const createChoiceCard = (id, icon, titleKey, descKey, isSelected) => {
- const card = document.createElement('div');
- card.style.flex = '1';
- card.style.background = isSelected ? 'rgba(255, 255, 255, 0.1)' : 'rgba(255, 255, 255, 0.04)';
- card.style.border = `2px solid ${isSelected ? '#e5e7eb' : 'transparent'}`;
- card.style.borderRadius = '8px';
- card.style.padding = '16px';
- card.style.cursor = 'pointer';
- card.style.transition = 'all 0.2s';
- card.style.display = 'flex';
- card.style.flexDirection = 'column';
- card.style.gap = '12px';
- card.style.alignItems = 'flex-start';
-
- card.onmouseover = () => {
- if (!isSelected) card.style.background = 'rgba(255, 255, 255, 0.08)';
- };
- card.onmouseout = () => {
- if (!isSelected) card.style.background = 'rgba(255, 255, 255, 0.04)';
- };
-
- const topRow = document.createElement('div');
- topRow.style.display = 'flex';
- topRow.style.alignItems = 'center';
- topRow.style.gap = '12px';
-
- const iconDiv = document.createElement('div');
- iconDiv.innerText = icon;
- iconDiv.style.fontSize = '1.6em';
-
- const tTitle = document.createElement('div');
- tTitle.textContent = t(titleKey);
- tTitle.style.fontWeight = '500';
- tTitle.style.fontSize = '1.05em';
- tTitle.style.color = isSelected ? '#ffffff' : '#e5e7eb';
-
- topRow.appendChild(iconDiv);
- topRow.appendChild(tTitle);
-
- const tDesc = document.createElement('div');
- tDesc.textContent = t(descKey);
- tDesc.style.fontSize = '0.85em';
- tDesc.style.color = '#9ca3af';
- tDesc.style.lineHeight = '1.5';
-
- card.appendChild(topRow);
- card.appendChild(tDesc);
- return card;
- };
-
- let targetMode = 'all';
- let selectedForScan = new Map();
-
- if (!targetFiles) {
- const section0 = document.createElement('div');
- const sec0Title = document.createElement('div');
- sec0Title.textContent = t('sidebarStep0');
- sec0Title.style.fontWeight = '500';
- sec0Title.style.color = '#e5e7eb';
- sec0Title.style.marginBottom = '12px';
- sec0Title.style.fontSize = '0.95em';
- section0.appendChild(sec0Title);
-
- const tContainer = document.createElement('div');
- tContainer.style.display = 'flex';
- tContainer.style.flexDirection = 'row';
- tContainer.style.gap = '16px';
- tContainer.style.marginBottom = '16px';
-
- const customActionDiv = document.createElement('div');
- customActionDiv.style.display = 'none';
- customActionDiv.style.marginTop = '16px';
-
- const openSelectorBtn = document.createElement('button');
- openSelectorBtn.textContent = t('sidebarOpenSelector');
- openSelectorBtn.style.cssText = 'width:100%;padding:12px;background:#e5e7eb;color:#111;border:none;border-radius:6px;cursor:pointer;font-weight:600;font-size:1.05em;box-shadow:0 2px 4px rgba(0,0,0,0.2);';
-
- const selectedCountSpan = document.createElement('div');
- selectedCountSpan.style.cssText = 'text-align:center;color:#e5e7eb;margin-top:8px;font-size:0.9em;';
- selectedCountSpan.textContent = t('sidebarSelectedZero');
-
- const updateSelectedCount = () => {
- let total = 0;
- for (const set of selectedForScan.values()) total += set.size;
- selectedCountSpan.textContent = t('sidebarSelectedCount', { count: total });
- };
-
- openSelectorBtn.onclick = () => {
- this._openAdvancedModelSelector(selectedForScan, (newSelection) => {
- selectedForScan = newSelection;
- updateSelectedCount();
- });
- };
-
- customActionDiv.appendChild(openSelectorBtn);
- customActionDiv.appendChild(selectedCountSpan);
-
- let tCard1, tCard2;
- const updateTargetCards = () => {
- if (tCard1 && tCard2) {
- tContainer.removeChild(tCard1);
- tContainer.removeChild(tCard2);
- }
- tCard1 = createChoiceCard('all', '📂', 'sidebarTargetGlobal', 'sidebarTargetGlobalDesc', targetMode === 'all');
- tCard2 = createChoiceCard('custom', '☑️', 'sidebarTargetCustom', 'sidebarTargetCustomDesc', targetMode === 'custom');
-
- tCard1.onclick = () => {
- targetMode = 'all';
- updateTargetCards();
- customActionDiv.style.display = 'none';
- };
- tCard2.onclick = () => {
- targetMode = 'custom';
- updateTargetCards();
- customActionDiv.style.display = 'block';
- };
-
- tContainer.appendChild(tCard1);
- tContainer.appendChild(tCard2);
- };
- updateTargetCards();
- section0.appendChild(tContainer);
- section0.appendChild(customActionDiv);
- formGroup.appendChild(section0);
- }
-
- // === Step 1: Fetch Data ===
- const section1 = document.createElement('div');
- const sec1Title = document.createElement('div');
- sec1Title.textContent = t('sidebarStep1');
- sec1Title.style.fontWeight = '500';
- sec1Title.style.color = '#e5e7eb';
- sec1Title.style.marginBottom = '12px';
- sec1Title.style.fontSize = '0.95em';
- section1.appendChild(sec1Title);
-
- const cardsContainer = document.createElement('div');
- cardsContainer.style.display = 'flex';
- cardsContainer.style.flexDirection = 'row';
- cardsContainer.style.gap = '16px';
-
-
-
- let card1, card2;
- const updateCards = () => {
- if (card1 && card2) {
- cardsContainer.removeChild(card1);
- cardsContainer.removeChild(card2);
- }
- card1 = createChoiceCard('civitai', '🌍', 'sidebarOnline', 'sidebarOnlineDesc', scanMode === 'civitai');
- card2 = createChoiceCard('offline', '🔌', 'sidebarOffline', 'sidebarOfflineDesc', scanMode === 'offline');
-
- card1.onclick = () => { scanMode = 'civitai'; updateCards(); updateSections(); };
- card2.onclick = () => { scanMode = 'offline'; updateCards(); updateSections(); };
-
- cardsContainer.appendChild(card1);
- cardsContainer.appendChild(card2);
- };
- updateCards();
- section1.appendChild(cardsContainer);
- formGroup.appendChild(section1);
-
- // Material Switch Builder
- const createMaterialSwitch = (initialState, onChange) => {
- const track = document.createElement('div');
- track.style.width = '36px';
- track.style.height = '14px';
- track.style.borderRadius = '7px';
- track.style.background = initialState ? 'rgba(255, 255, 255, 0.45)' : 'rgba(255,255,255,0.15)';
- track.style.position = 'relative';
- track.style.cursor = 'pointer';
- track.style.transition = 'background 0.3s';
- track.style.display = 'flex';
- track.style.alignItems = 'center';
-
- const thumb = document.createElement('div');
- thumb.style.width = '20px';
- thumb.style.height = '20px';
- thumb.style.borderRadius = '50%';
- thumb.style.background = initialState ? '#ffffff' : '#888888';
- thumb.style.position = 'absolute';
- thumb.style.left = initialState ? '16px' : '0px';
- thumb.style.transition = 'left 0.3s, background 0.3s';
- thumb.style.boxShadow = '0 1px 3px rgba(0,0,0,0.4)';
-
- let state = initialState;
- track.onclick = () => {
- state = !state;
- track.style.background = state ? 'rgba(255, 255, 255, 0.45)' : 'rgba(255,255,255,0.15)';
- thumb.style.background = state ? '#ffffff' : '#888888';
- thumb.style.left = state ? '16px' : '0px';
- onChange(state);
- };
- track.appendChild(thumb);
- return track;
- };
-
- const createListRow = (icon, titleKey, descKey, actionEl) => {
- const row = document.createElement('div');
- row.style.display = 'flex';
- row.style.alignItems = 'center';
- row.style.justifyContent = 'space-between';
- row.style.padding = '12px 0';
- row.style.borderBottom = '1px solid rgba(255,255,255,0.05)';
-
- const left = document.createElement('div');
- left.style.display = 'flex';
- left.style.alignItems = 'flex-start';
- left.style.gap = '16px';
-
- const iconEl = document.createElement('div');
- if (typeof icon === 'string' && icon.startsWith('<')) {
- iconEl.innerHTML = icon;
- } else {
- iconEl.innerText = icon;
- }
- iconEl.style.fontSize = '1.4em';
- iconEl.style.lineHeight = '1.2';
- iconEl.style.width = '24px';
- iconEl.style.textAlign = 'center';
-
- const textDiv = document.createElement('div');
- const titleEl = document.createElement('div');
- titleEl.textContent = t(titleKey);
- titleEl.style.fontWeight = '500';
- titleEl.style.fontSize = '1.0em';
- titleEl.style.color = '#fff';
-
- const d = document.createElement('div');
- d.innerHTML = t(descKey);
- d.style.fontSize = '0.85em';
- d.style.color = '#aaa';
- d.style.marginTop = '4px';
-
- textDiv.appendChild(titleEl);
- textDiv.appendChild(d);
- left.appendChild(iconEl);
- left.appendChild(textDiv);
- row.appendChild(left);
- if (actionEl) row.appendChild(actionEl);
-
- return row;
- };
-
- // === Step 2: Normalize Naming ===
- const section2 = document.createElement('div');
- const sec2Title = document.createElement('div');
- sec2Title.textContent = t('sidebarStep2');
- sec2Title.style.fontWeight = '500';
- sec2Title.style.color = '#e5e7eb';
- sec2Title.style.marginBottom = '8px';
- sec2Title.style.fontSize = '0.95em';
- section2.appendChild(sec2Title);
-
- const s2List = document.createElement('div');
- let enableVirtualRename = true;
- let enablePhysicalRename = false;
-
- const dualChannelRow = document.createElement('div');
- dualChannelRow.style.display = 'flex';
- dualChannelRow.style.flexDirection = 'column';
- dualChannelRow.style.gap = '14px';
- dualChannelRow.style.padding = '0 16px 12px 16px';
- dualChannelRow.style.marginLeft = '40px';
-
- const updateDualChannelUI = () => {
- dualChannelRow.style.opacity = enableRename ? '1' : '0.4';
- dualChannelRow.style.pointerEvents = enableRename ? 'auto' : 'none';
- };
-
- const virtualSwitch = createMaterialSwitch(enableVirtualRename, (s) => enableVirtualRename = s);
- let physicalProtectionNotice = null;
- const physicalSwitch = createMaterialSwitch(enablePhysicalRename, (s) => {
- enablePhysicalRename = s;
- if (physicalProtectionNotice) {
- physicalProtectionNotice.style.display = s ? 'block' : 'none';
- }
- });
-
- const vContainer = document.createElement('div');
- vContainer.style.display = 'flex';
- vContainer.style.flexDirection = 'column';
-
- const vRow = document.createElement('div');
- vRow.style.display = 'flex';
- vRow.style.alignItems = 'center';
- vRow.style.gap = '8px';
- vRow.innerHTML = `✨ ${t('sidebarVirtualRename')}`;
- vRow.appendChild(virtualSwitch);
-
- const vDesc = document.createElement('div');
- vDesc.style.fontSize = '0.8em';
- vDesc.style.color = '#888';
- vDesc.style.marginTop = '4px';
- vDesc.textContent = t('sidebarVirtualRenameDesc');
- vContainer.appendChild(vRow);
- vContainer.appendChild(vDesc);
-
- const pContainer = document.createElement('div');
- pContainer.style.display = 'flex';
- pContainer.style.flexDirection = 'column';
-
- const pRow = document.createElement('div');
- pRow.style.display = 'flex';
- pRow.style.alignItems = 'center';
- pRow.style.gap = '8px';
- pRow.innerHTML = `${t('sidebarPhysicalRename')}`;
- pRow.appendChild(physicalSwitch);
-
- const pDesc = document.createElement('div');
- pDesc.style.fontSize = '0.8em';
- pDesc.style.color = '#888';
- pDesc.style.marginTop = '4px';
- pDesc.innerHTML = t('sidebarPhysicalRenameDesc');
- pContainer.appendChild(pRow);
- pContainer.appendChild(pDesc);
-
- physicalProtectionNotice = document.createElement('div');
- physicalProtectionNotice.style.display = enablePhysicalRename ? 'block' : 'none';
- physicalProtectionNotice.style.marginTop = '8px';
- physicalProtectionNotice.style.padding = '9px 11px';
- physicalProtectionNotice.style.border = '1px solid rgba(251, 188, 4, 0.45)';
- physicalProtectionNotice.style.borderRadius = '6px';
- physicalProtectionNotice.style.background = 'rgba(251, 188, 4, 0.08)';
- physicalProtectionNotice.style.color = '#fdd663';
- physicalProtectionNotice.style.fontSize = '0.8em';
- physicalProtectionNotice.style.lineHeight = '1.45';
- physicalProtectionNotice.textContent = t('sidebarPhysicalRenameProtectedNotice');
- pContainer.appendChild(physicalProtectionNotice);
-
- dualChannelRow.appendChild(vContainer);
- dualChannelRow.appendChild(pContainer);
-
- const editSvg = '';
- s2List.appendChild(createListRow(editSvg, 'sidebarNormalize', 'sidebarNormalizeDesc', createMaterialSwitch(enableRename, (s) => { enableRename = s; updateDualChannelUI(); })));
- s2List.lastChild.style.borderBottom = 'none';
- s2List.appendChild(dualChannelRow);
- updateDualChannelUI();
- section2.appendChild(s2List);
- formGroup.appendChild(section2);
-
- // === Step 3: Workflow Protection & Fix ===
- const section3 = document.createElement('div');
- const sec3Title = document.createElement('div');
- sec3Title.textContent = t('sidebarStep3');
- sec3Title.style.fontWeight = '500';
- sec3Title.style.color = '#e5e7eb';
- sec3Title.style.marginBottom = '8px';
- sec3Title.style.fontSize = '0.95em';
- section3.appendChild(sec3Title);
-
- const s3List = document.createElement('div');
- const isInject = localStorage.getItem('anomalous_inject_hash') !== 'false';
- const cubeSvg = '';
- const warnSvg = '';
- const wandSvg = '';
- s3List.appendChild(createListRow(cubeSvg, 'sidebarProvenance', 'sidebarProvenanceDesc', createMaterialSwitch(isInject, (s) => localStorage.setItem('anomalous_inject_hash', s ? 'true' : 'false'))));
- s3List.appendChild(createListRow(warnSvg, 'sidebarOverwrite', 'sidebarOverwriteDesc', createMaterialSwitch(enableForceOverwrite, (s) => enableForceOverwrite = s)));
- s3List.appendChild(createListRow(wandSvg, 'sidebarSmartFix', 'sidebarSmartFixDesc', createMaterialSwitch(enableAutoCheck, (s) => enableAutoCheck = s)));
- s3List.lastChild.style.borderBottom = 'none';
-
- section3.appendChild(s3List);
- formGroup.appendChild(section3);
- content.appendChild(formGroup);
-
- updateSections = () => {
- if (scanMode === 'offline') {
- section2.style.opacity = '0.3';
- section2.style.pointerEvents = 'none';
- section3.style.opacity = '0.3';
- section3.style.pointerEvents = 'none';
- } else {
- section2.style.opacity = '1';
- section2.style.pointerEvents = 'auto';
- section3.style.opacity = '1';
- section3.style.pointerEvents = 'auto';
- }
- };
- updateSections();
-
- // Execute Scan Logic
- const doScan = async () => {
- try {
- // Offline greys out renaming and overwriting, so none of them may be sent.
- const offline = scanMode === 'offline';
- const reqBody = {
- offline_only: offline,
- skip_rename: offline || !enableRename,
- virtual_rename: !offline && enableRename && enableVirtualRename,
- physical_rename: !offline && enableRename && enablePhysicalRename,
- force_overwrite: !offline && enableForceOverwrite
- };
-
- if (targetMode === 'custom') {
- let hasAny = false;
- for (const set of selectedForScan.values()) {
- if (set.size > 0) hasAny = true;
- }
- if (!hasAny) {
- alert(t('sidebarNoModelsSelected'));
- return;
- }
-
- closeWizard();
- const activeScanBtn = document.getElementById('anomalous-scan-btn');
- if (activeScanBtn) {
- setScanButtonState(activeScanBtn, true);
- }
-
- const customFolders = Array.from(selectedForScan.entries()).filter(([, files]) => files.size > 0);
- let customFolderCurrent = 0;
- updateScanProgress({
- scanning: true,
- phase: 'preparing',
- folder_total: customFolders.length,
- folder_current: 0,
- });
-
- // Sequential scan for multiple folders
- for (const [folderKey, fileSet] of customFolders) {
- customFolderCurrent += 1;
- const parts = folderKey.split('|');
- if (parts.length < 3) continue;
- const type = parts[0], path_idx = parts[1], subfolder = parts.slice(2).join('|');
-
- const params = new URLSearchParams({ type, path_idx, subfolder });
- const targetUrl = '/anomalous/scan?' + params.toString();
-
- const currentReqBody = { ...reqBody, target_files: Array.from(fileSet) };
-
- try {
- const res = await fetch(targetUrl, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(currentReqBody)
- });
- const data = await res.json();
- if (data.status === 'ok') {
- // wait for this folder to finish scanning before starting next
- await new Promise(resolve => {
- const poll = setInterval(async () => {
- try {
- const statusUrl = '/anomalous/scan_status?' + params.toString();
- const statusRes = await fetch(statusUrl);
- const statusData = await statusRes.json();
- updateScanProgress({
- ...statusData,
- folder_total: customFolders.length,
- folder_current: customFolderCurrent,
- folder: subfolder,
- });
- if (!statusData.scanning) {
- clearInterval(poll);
- resolve();
- }
- } catch (err) {
- clearInterval(poll);
- resolve();
- }
- }, 2000);
- });
- } else {
- console.error("Scan failed for folder: " + folderKey, data.message);
- }
- } catch(e) {
- console.error("Request failed for folder: " + folderKey, e);
- }
- }
-
- setActiveScanButtonState(false);
- finishScanProgress();
- try {
- if (app?.refreshComboInNodes) await app.refreshComboInNodes();
- if (window.anomalous_reload_hashes) await window.anomalous_reload_hashes();
- } catch (e) {
- console.warn('[AMB] Error reloading hashes or combo nodes:', e);
- }
- if (enableAutoCheck && window.anomalous_resolve_all_missing_nodes) {
- window.anomalous_resolve_all_missing_nodes(true);
- }
- this.loadModels();
-
- return;
- }
-
- // Existing logic for 'all' mode or single file
- let finalTargetFiles = targetFiles;
- let currentReqBody = { ...reqBody };
-
- let targetUrl = '';
- if (isGlobal && targetMode !== 'custom') {
- targetUrl = '/anomalous/scan_all';
- } else {
- const params = new URLSearchParams({ type: this.currentType, path_idx: this.currentPathIdx, subfolder: this.currentSubfolder });
- if (finalTargetFiles) {
- currentReqBody.target_files = finalTargetFiles.split(',');
- }
- targetUrl = '/anomalous/scan?' + params.toString();
- }
-
- const res = await fetch(targetUrl, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(currentReqBody)
- });
-
- const data = await res.json();
- if (data.status === 'ok') {
- updateScanProgress({ scanning: true, phase: 'preparing', recovered: data.recovered });
- setActiveScanButtonState(true);
-
- // Start polling
- const poll = setInterval(async () => {
- try {
- let statusUrl = '/anomalous/global_scan_status';
- if (!isGlobal) {
- const params = new URLSearchParams({ type: this.currentType, path_idx: this.currentPathIdx, subfolder: this.currentSubfolder });
- statusUrl = '/anomalous/scan_status?' + params.toString();
- }
- const statusRes = await fetch(statusUrl);
- const statusData = await statusRes.json();
- updateScanProgress(statusData);
- if (!statusData.scanning) {
- clearInterval(poll);
- if (statusData.interrupted) failScanProgress(t('scanProgressInterrupted'));
- else finishScanProgress();
- setActiveScanButtonState(false);
-
- try {
- if (app?.refreshComboInNodes) await app.refreshComboInNodes();
- if (window.anomalous_reload_hashes) await window.anomalous_reload_hashes();
- } catch (e) {
- console.warn('[AMB] Error reloading hashes or combo nodes:', e);
- }
- if (enableAutoCheck && window.anomalous_resolve_all_missing_nodes) {
- window.anomalous_resolve_all_missing_nodes(true);
- }
- this.loadModels();
- }
- } catch (err) {
- clearInterval(poll);
- setActiveScanButtonState(false);
- }
- }, 2000);
- closeWizard();
- return; // return early so we don't remove wizard again below
- } else {
- failScanProgress(t('sidebarScanFailed') + data.message);
- alert(t('sidebarScanFailed') + data.message);
- }
- } catch (e) {
- failScanProgress(String(e));
- alert("Error: " + e);
- }
-
- closeWizard();
- };
-
- const footer = document.createElement('div');
- footer.style.marginTop = '16px';
- footer.style.paddingTop = '16px';
- footer.style.borderTop = '1px solid rgba(255,255,255,0.08)';
- footer.style.display = 'flex';
- footer.style.justifyContent = 'flex-end';
- footer.style.gap = '8px';
- footer.style.flexShrink = '0';
-
- const closeBtn = document.createElement('button');
-closeBtn.textContent = t('sidebarCancel');
- closeBtn.style.padding = '8px 16px';
- closeBtn.style.background = 'transparent';
- closeBtn.style.color = '#9ca3af';
- closeBtn.style.border = 'none';
- closeBtn.style.borderRadius = '4px';
- closeBtn.style.cursor = 'pointer';
- closeBtn.style.fontSize = '0.95em';
- closeBtn.style.fontWeight = '500';
- closeBtn.style.textTransform = 'uppercase';
- closeBtn.style.transition = 'background 0.2s';
- closeBtn.onmouseover = () => closeBtn.style.background = 'rgba(255, 255, 255, 0.08)';
- closeBtn.onmouseout = () => closeBtn.style.background = 'transparent';
- closeBtn.onclick = closeWizard;
-
- const startBtn = document.createElement('button');
-startBtn.textContent = t('sidebarExecute');
- startBtn.style.padding = '8px 24px';
- startBtn.style.background = '#e5e7eb';
- startBtn.style.color = '#111827';
- startBtn.style.border = 'none';
- startBtn.style.borderRadius = '4px';
- startBtn.style.cursor = 'pointer';
- startBtn.style.fontSize = '0.95em';
- startBtn.style.fontWeight = '600';
- startBtn.style.textTransform = 'uppercase';
- startBtn.style.transition = 'background 0.2s, box-shadow 0.2s';
- startBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.3)';
- startBtn.onmouseover = () => {
- startBtn.style.background = '#ffffff';
- startBtn.style.boxShadow = '0 3px 8px rgba(0,0,0,0.4)';
- };
- startBtn.onmouseout = () => {
- startBtn.style.background = '#e5e7eb';
- startBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.3)';
- };
- startBtn.onclick = doScan;
-
- footer.appendChild(closeBtn);
- footer.appendChild(startBtn);
- content.appendChild(footer);
- wizard.appendChild(content);
- document.body.appendChild(wizard);
-}
-
-export async function formatScanCompletionToast(model) {
- const isZh = window.anomalous_browser_lang === 'zh';
- const fallbackName = model.name || model.filename;
- try {
- const findRes = await fetch('/anomalous/find_model?search=' + encodeURIComponent(model.filename));
- if (findRes.ok) {
- const updated = await findRes.json();
- const target = updated?.model || updated || model;
- if (target && target.metadata) {
- const meta = target.metadata;
- const isCivitai = Boolean(
- (meta.id && meta.id !== -1) ||
- (meta.modelId && meta.modelId !== -1) ||
- (meta.model_id && meta.model_id !== -1) ||
- (meta.version_id && meta.version_id !== -1) ||
- meta.civitai_url
- );
- const hasPreview = Boolean(target.preview_url);
- const baseModel = meta.baseModel;
-
- if (isCivitai && hasPreview) {
- return isZh ? `✓ 已从 Civitai 获取封面与模型信息!` : `✓ Civitai cover & metadata fetched!`;
- }
- if (isCivitai && !hasPreview) {
- return isZh ? `✓ 已匹配到 Civitai 信息(线上未提供封面)` : `✓ Civitai metadata matched (no cover online)`;
- }
- if (!isCivitai) {
- if (baseModel) {
- return isZh
- ? `ℹ️ 非 Civitai 模型:已识别底模为 [${baseModel}]`
- : `ℹ️ Non-Civitai model: inferred base model [${baseModel}]`;
- }
- return isZh
- ? `ℹ️ 未在 Civitai 匹配到此模型`
- : `ℹ️ No Civitai match found for this model`;
- }
- }
- }
- } catch {
- // fallback to standard text
- }
- return isZh ? `✓ 模型 [${fallbackName}] 扫描完成!` : `✓ Model [${fallbackName}] scanned!`;
-}
-
-function pollDirectScanStatus(params, titleText, model, onComplete) {
- const statusUrl = '/anomalous/scan_status?' + params.toString();
- const poll = setInterval(async () => {
- try {
- const statusRes = await fetch(statusUrl);
- const statusData = await statusRes.json();
- updateScanProgress(statusData, titleText);
-
- if (!statusData.scanning) {
- clearInterval(poll);
- if (statusData.interrupted) {
- failScanProgress(t('scanProgressInterrupted'));
- showWorkbenchToast(window.anomalous_browser_lang === 'zh' ? '扫描被中断' : 'Scan interrupted');
- } else {
- const toastMsg = await formatScanCompletionToast(model);
- finishScanProgress(toastMsg);
- showWorkbenchToast(toastMsg);
- }
- onComplete(true);
- }
- } catch (err) {
- clearInterval(poll);
- failScanProgress(String(err));
- onComplete(false);
- }
- }, 1200);
- return poll;
-}
-
-export async function triggerDirectModelScan(model, triggerBtn = null, browserInstance = null) {
- const browser = browserInstance || this || {};
- if (!model || !model.filename) return;
-
- const modelLabel = model.name || model.filename;
- const isZh = window.anomalous_browser_lang === 'zh';
- const titleText = isZh ? `精准扫描: ${modelLabel}` : `Scanning: ${modelLabel}`;
-
- if (triggerBtn) {
- triggerBtn.classList.add('anomalous-radar-spinning');
- if (triggerBtn.firstElementChild) {
- triggerBtn.firstElementChild.style.animation = 'anomalous-radar-spin 1.2s linear infinite';
- triggerBtn.firstElementChild.style.stroke = '#10b981';
- }
- triggerBtn.style.pointerEvents = 'none';
- triggerBtn.style.opacity = '0.7';
- }
- setActiveScanButtonState(true);
- updateScanProgress({ scanning: true, phase: 'preparing', total: 1, current: 0, filename: model.filename }, titleText);
-
- const resetBtn = () => {
- setActiveScanButtonState(false);
- if (triggerBtn) {
- triggerBtn.classList.remove('anomalous-radar-spinning');
- if (triggerBtn.firstElementChild) {
- triggerBtn.firstElementChild.style.animation = '';
- triggerBtn.firstElementChild.style.stroke = '';
- }
- triggerBtn.style.pointerEvents = '';
- triggerBtn.style.opacity = '';
- }
- };
-
- try {
- const params = new URLSearchParams({
- type: browser.currentType || 'checkpoints',
- path_idx: browser.currentPathIdx || 0,
- subfolder: browser.currentSubfolder || '/',
- });
- const reqBody = {
- target_files: [model.filename],
- offline_only: false,
- skip_rename: true,
- virtual_rename: false,
- physical_rename: false,
- force_overwrite: false
- };
-
- const res = await fetch('/anomalous/scan?' + params.toString(), {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(reqBody)
- });
-
- const data = await res.json();
- if (data.status === 'ok') {
- showWorkbenchToast(isZh ? `开始精准扫描: ${modelLabel}` : `Scanning model: ${modelLabel}`);
- pollDirectScanStatus(params, titleText, model, async () => {
- resetBtn();
- if (typeof browser.loadModels === 'function') {
- browser.loadModels();
- }
- try {
- if (app?.refreshComboInNodes) await app.refreshComboInNodes();
- if (window.anomalous_reload_hashes) await window.anomalous_reload_hashes();
- } catch (e) {
- console.warn('[AMB] Error reloading hashes or combo nodes:', e);
- }
- });
- } else {
- resetBtn();
- const errMsg = data.message || (isZh ? '扫描启动失败' : 'Failed to start scan');
- failScanProgress(errMsg);
- showWorkbenchToast(errMsg);
- }
- } catch (e) {
- resetBtn();
- failScanProgress(String(e));
- showWorkbenchToast(String(e));
- }
-}
-