722 lines
31 KiB
JavaScript
722 lines
31 KiB
JavaScript
/**
|
|
* Scan wizard UI and scan launch/polling coordination.
|
|
*/
|
|
|
|
import { translate } from './locales.js';
|
|
import { updateScanProgress, finishScanProgress, failScanProgress } from './scan_progress.js';
|
|
import { configureSidebarAction } from './sidebar_actions.js';
|
|
|
|
const t = (key, params) => translate(key, params);
|
|
|
|
const SCAN_RADAR_ICON_SVG = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><circle cx="12" cy="12" r="9"/><line x1="12" y1="3" x2="12" y2="21" stroke-opacity="0.35"/><line x1="3" y1="12" x2="21" y2="12" stroke-opacity="0.35"/><line x1="12" y1="12" x2="18.5" y2="5.5" stroke-width="2"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/></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 = '';
|
|
configureSidebarAction(btn);
|
|
}
|
|
|
|
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) document.body.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 content = document.createElement('div');
|
|
content.style.background = '#1E1E1E';
|
|
content.style.borderRadius = '12px';
|
|
content.style.padding = '32px';
|
|
content.style.width = '760px';
|
|
content.style.maxWidth = '95%';
|
|
content.style.maxHeight = '90vh';
|
|
content.style.overflowY = 'auto';
|
|
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';
|
|
|
|
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 = `<span style="font-size:0.9em; color:#ddd;">✨ ${t('sidebarVirtualRename')}</span>`;
|
|
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 = `<span style="font-size:0.9em; color:#ddd; display:inline-flex; align-items:center; gap:6px;"><svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>${t('sidebarPhysicalRename')}</span>`;
|
|
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 = '<svg style="width:20px;height:20px;vertical-align:middle;stroke:#dc143c;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>';
|
|
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 = '<svg style="width:20px;height:20px;vertical-align:middle;stroke:#dc143c;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>';
|
|
const warnSvg = '<svg style="width:20px;height:20px;vertical-align:middle;stroke:#f59e0b;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>';
|
|
const wandSvg = '<svg style="width:20px;height:20px;vertical-align:middle;stroke:#8b5cf6;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>';
|
|
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 {
|
|
const reqBody = {
|
|
offline_only: scanMode === 'offline',
|
|
skip_rename: !enableRename,
|
|
virtual_rename: enableRename ? enableVirtualRename : false,
|
|
physical_rename: enableRename ? enablePhysicalRename : false,
|
|
force_overwrite: 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;
|
|
}
|
|
|
|
document.body.removeChild(wizard);
|
|
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();
|
|
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);
|
|
|
|
if (enableAutoCheck && window.anomalous_resolve_all_missing_nodes) {
|
|
window.anomalous_resolve_all_missing_nodes(true);
|
|
}
|
|
this.loadModels();
|
|
}
|
|
} catch (err) {
|
|
clearInterval(poll);
|
|
setActiveScanButtonState(false);
|
|
}
|
|
}, 2000);
|
|
document.body.removeChild(wizard);
|
|
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);
|
|
}
|
|
|
|
document.body.removeChild(wizard);
|
|
};
|
|
|
|
const footer = document.createElement('div');
|
|
footer.style.marginTop = '32px';
|
|
footer.style.display = 'flex';
|
|
footer.style.justifyContent = 'flex-end';
|
|
footer.style.gap = '8px';
|
|
|
|
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 = () => document.body.removeChild(wizard);
|
|
|
|
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);
|
|
}
|
|
|