async function checkAndShowMissingPackagesDialog(missingCustomPackages, missingNodes, flowConfig) { const combinedMissingCustomPackages = enrichMissingCustomPackages(missingCustomPackages, flowConfig); const filteredMissingCustomPackages = combinedMissingCustomPackages; if (filteredMissingCustomPackages.length === 0 && missingNodes.length === 0) { return; } showMissingPackagesDialog(filteredMissingCustomPackages, missingNodes); } function enrichMissingCustomPackages(missingCustomPackages, flowConfig) { if (!flowConfig.configCustomNodes) { return missingCustomPackages; } const customNodesByTitle = {}; for (const nodeConfig of flowConfig.configCustomNodes) { customNodesByTitle[nodeConfig.title] = nodeConfig; } const enrichedPackages = missingCustomPackages.map(pkg => { let title; if (typeof pkg === 'string') { title = pkg; } else { title = pkg.title; } const nodeConfig = customNodesByTitle[title]; if (nodeConfig) { return { title: nodeConfig.title, packageUrl: nodeConfig.installUrl, downloadModal: nodeConfig.downloadModal || {}, }; } else { return pkg; } }); for (const nodeConfig of flowConfig.configCustomNodes) { if (!enrichedPackages.some(pkg => pkg.title === nodeConfig.title)) { enrichedPackages.push({ title: nodeConfig.title, packageUrl: nodeConfig.installUrl, downloadModal: nodeConfig.downloadModal || {}, }); } } const uniquePackages = []; const seenTitles = new Set(); for (const pkg of enrichedPackages) { if (!seenTitles.has(pkg.title)) { seenTitles.add(pkg.title); uniquePackages.push(pkg); } } return uniquePackages; } async function fetchInstalledCustomNodes() { try { const response = await fetch('/flow/api/installed-custom-nodes'); if (response.ok) { const data = await response.json(); return data.installedNodes || []; } else { console.error('Failed to fetch installed custom nodes'); return []; } } catch (error) { console.error('Error fetching installed custom nodes:', error); return []; } } function showMissingPackagesDialog(missingCustomPackages, missingNodes) { const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 999; `; const dialog = document.createElement('div'); dialog.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1e1e1e; color: #ffffff; padding: 24px; border: 2px dashed #ff4444; box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3); border-radius: 5px; z-index: 1000; min-width: 700px; width: 45%; max-height: 95vh; overflow-y: auto; font-family: Arial, sans-serif; `; const scrollbarStyles = ` `; let content = ` ${scrollbarStyles}

Nodes Unavailable

Some custom nodes require manual installation. Follow the instructions on the custom node page.

`; if (missingCustomPackages.length > 0) { content += `

${missingCustomPackages.length} Missing Custom Nodes:

`; } if (missingNodes.length > 0) { content += `

${missingNodes.length} Missing Nodes: (Click to toggle)

`; } content += `
Return to Menu
`; dialog.innerHTML = content; dialog.classList.add('custom-scrollbar'); document.body.appendChild(overlay); document.body.appendChild(dialog); const allActionButtons = document.querySelectorAll('.install-btn, .update-btn, .uninstall-btn'); allActionButtons.forEach(button => { button.addEventListener('mouseover', function() { if (!this.disabled) { const currentBg = getComputedStyle(this).backgroundColor; const currentTextColor = getComputedStyle(this).color; this.style.backgroundColor = currentTextColor; this.style.color = currentBg; } }); button.addEventListener('mouseout', function() { if (!this.disabled) { let originalColor; if (this.classList.contains('install-btn')) { originalColor = '#28a745'; // Green } else if (this.classList.contains('update-btn')) { originalColor = '#ffc107'; // Yellow } else if (this.classList.contains('uninstall-btn')) { originalColor = '#dc3545'; // Red } this.style.backgroundColor = '#121212'; this.style.color = originalColor; } }); }); document.querySelectorAll('.install-btn').forEach(button => { button.addEventListener('click', function() { handleButtonClick(this, sendInstallRequest); }); }); document.querySelectorAll('.update-btn').forEach(button => { button.addEventListener('click', function() { handleButtonClick(this, sendUpdateRequest); }); }); document.querySelectorAll('.uninstall-btn').forEach(button => { button.addEventListener('click', function() { handleButtonClick(this, sendUninstallRequest); }); }); const toggleMissingNodes = document.getElementById('toggle-missing-nodes'); const missingNodesList = document.getElementById('missing-nodes-list'); if (toggleMissingNodes && missingNodesList) { toggleMissingNodes.addEventListener('click', function() { missingNodesList.style.display = missingNodesList.style.display === 'none' ? 'block' : 'none'; }); } } function handleButtonClick(button, requestFunction) { if (button.disabled) return; const packageUrl = button.getAttribute('data-url'); const downloadModalData = button.getAttribute('data-download-modal'); const downloadModal = downloadModalData ? JSON.parse(downloadModalData) : {}; const statusMessage = button.closest('li').querySelector('.status-message'); const allButtons = document.querySelectorAll('.install-btn, .update-btn, .uninstall-btn'); allButtons.forEach(btn => { btn.disabled = true; btn.classList.add('disabled-button'); }); requestFunction(packageUrl, downloadModal, statusMessage) .finally(() => { allButtons.forEach(btn => { btn.disabled = false; btn.classList.remove('disabled-button'); }); }); } function sendInstallRequest(packageUrl, downloadModal, statusMessageElement) { statusMessageElement.textContent = 'Installing...'; return fetch('/flow/api/install-package', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ packageUrl, downloadModal }) }) .then(response => response.json()) .then(data => { if (data.status === 'success') { statusMessageElement.textContent = `${data.message} Reset ComfyUI to apply changes.`; } else if (data.status === 'already_installed') { statusMessageElement.textContent = `${data.message} Reset ComfyUI to apply changes.`; } else { statusMessageElement.textContent = data.message || 'Installation failed.'; } }) .catch(error => { console.error('Installation error:', error); statusMessageElement.textContent = 'Installation failed.'; }); } function sendUpdateRequest(packageUrl, downloadModal, statusMessageElement) { statusMessageElement.textContent = 'Updating...'; return fetch('/flow/api/update-package', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ packageUrl }) }) .then(response => response.json()) .then(data => { if (data.status === 'success') { statusMessageElement.textContent = `${data.message} \n Reset ComfyUI to apply changes.`; } else if (data.status === 'not_installed') { statusMessageElement.textContent = data.message; } else { statusMessageElement.textContent = data.message || 'Update failed.'; } }) .catch(error => { console.error('Update error:', error); statusMessageElement.textContent = 'Update failed.'; }); } function sendUninstallRequest(packageUrl, downloadModal, statusMessageElement) { statusMessageElement.textContent = 'Uninstalling...'; return fetch('/flow/api/uninstall-package', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ packageUrl }) }) .then(response => response.json()) .then(data => { if (data.status === 'success') { statusMessageElement.textContent = `${data.message} Reset ComfyUI to apply changes.`; } else if (data.status === 'not_installed') { statusMessageElement.textContent = data.message; } else { statusMessageElement.textContent = data.message || 'Uninstallation failed.'; } }) .catch(error => { console.error('Uninstallation error:', error); statusMessageElement.textContent = 'Uninstallation failed.'; }); } export { checkAndShowMissingPackagesDialog };