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 += `
`;
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 };