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)); - } -} -