feat(builder-ui): render install card when Distributed companion missing

Distribution panel sections now async-check for the companion via
/uscg-distribution/health. 200 → renders normal panel; 404 → renders
install card (Comfy Manager + GitHub link + Re-check button).

DOM methods only - no innerHTML. Saved Distribution settings preserved
in node.state when companion isn't installed - no data loss.
This commit is contained in:
Jason Hoku
2026-05-19 15:34:01 -07:00
parent 8f3250dfca
commit 5f6c2b5a95
+149 -3
View File
@@ -6,12 +6,142 @@
import { createInputGroup, createSlider, createSectionHeader } from './conf-builder-ui-components.js';
// =============================================================================
// DISTRIBUTED COMPANION DETECTION
//
// Distribution feature lives in ComfyUI-USCG-Distributed companion plugin.
// 200 from /uscg-distribution/health -> installed. 404 -> missing.
//
// Cached per Builder load. Cleared by the "Re-check after install" button.
// =============================================================================
let _distributedStatusPromise = null;
function isDistributedAvailable() {
if (_distributedStatusPromise === null) {
_distributedStatusPromise = fetch('/uscg-distribution/health')
.then(r => r.ok)
.catch(() => false);
}
return _distributedStatusPromise;
}
function _resetDistributedCache() {
_distributedStatusPromise = null;
}
function _renderDistributedInstallCard(node) {
const card = document.createElement('div');
card.className = 'cb-distributed-install-card';
card.style.cssText = [
"border: 1px solid #b07530",
"background: rgba(176, 117, 48, 0.08)",
"border-radius: 4px",
"padding: 8px 10px",
"margin: 4px 0",
"font-size: 11px",
"color: #ddd",
"line-height: 1.4",
].join(';');
const header = document.createElement('div');
header.style.fontWeight = "600";
header.style.marginBottom = "6px";
header.style.color = "#f0a050";
header.textContent = "⚠ Distributed multi-machine generation requires the companion plugin";
card.appendChild(header);
const intro = document.createElement('div');
intro.textContent = "Install via:";
card.appendChild(intro);
const list = document.createElement('ul');
list.style.margin = "4px 0 4px 16px";
list.style.padding = "0";
const liManager = document.createElement('li');
liManager.appendChild(document.createTextNode("Comfy Manager — search "));
const bold = document.createElement('b');
bold.textContent = "USCG Distributed";
liManager.appendChild(bold);
list.appendChild(liManager);
const liManual = document.createElement('li');
liManual.appendChild(document.createTextNode("Manual: "));
const codeClone = document.createElement('code');
codeClone.style.fontSize = "10px";
codeClone.textContent = "git clone";
liManual.appendChild(codeClone);
liManual.appendChild(document.createTextNode(" the repo into "));
const codeDir = document.createElement('code');
codeDir.style.fontSize = "10px";
codeDir.textContent = "custom_nodes/";
liManual.appendChild(codeDir);
list.appendChild(liManual);
card.appendChild(list);
const restart = document.createElement('div');
restart.style.marginTop = "6px";
restart.textContent = "Then restart ComfyUI.";
card.appendChild(restart);
const buttonRow = document.createElement('div');
buttonRow.style.marginTop = "8px";
buttonRow.style.display = "flex";
buttonRow.style.gap = "8px";
const ghLink = document.createElement('a');
ghLink.href = "https://github.com/JasonHoku/ComfyUI-USCG-Distributed";
ghLink.target = "_blank";
ghLink.rel = "noopener";
ghLink.style.cssText = "background:#444;color:#ddd;padding:4px 8px;border-radius:3px;text-decoration:none;font-size:11px;";
ghLink.textContent = "Open on GitHub ↗";
buttonRow.appendChild(ghLink);
const recheckBtn = document.createElement('button');
recheckBtn.type = "button";
recheckBtn.className = "cb-button";
recheckBtn.style.fontSize = "11px";
recheckBtn.style.padding = "4px 8px";
recheckBtn.textContent = "Re-check after install";
recheckBtn.addEventListener('click', () => {
_resetDistributedCache();
if (typeof debouncedRenderUI === 'function') {
debouncedRenderUI(node);
}
});
buttonRow.appendChild(recheckBtn);
card.appendChild(buttonRow);
return card;
}
// --- DISTRIBUTION SECTION RENDERER ---
export function renderDistributionSection(node, container) {
const placeholder = document.createElement('div');
placeholder.style.cssText = "font-size: 9px; color: #666; padding: 2px 4px;";
placeholder.textContent = "Checking for Distributed companion plugin...";
container.appendChild(placeholder);
isDistributedAvailable().then(available => {
if (!available) {
placeholder.replaceWith(_renderDistributedInstallCard(node));
} else {
placeholder.remove();
_renderDistributionSectionReal(node, container);
}
});
}
function _renderDistributionSectionReal(node, container) {
const section = document.createElement("div");
section.className = "cb-section";
section.innerHTML = '<div class="cb-section-title">🌐 Distribution</div>';
const sectionTitle = document.createElement("div");
sectionTitle.className = "cb-section-title";
sectionTitle.textContent = "🌐 Distribution";
section.appendChild(sectionTitle);
const content = document.createElement("div");
// Ensure distribution state defaults exist
@@ -57,7 +187,7 @@ export function renderDistributionSection(node, container) {
workerList.style.cssText = "display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px;";
const renderWorkerEntries = () => {
workerList.innerHTML = "";
while (workerList.firstChild) workerList.removeChild(workerList.firstChild);
node.state.worker_urls.forEach((url, idx) => {
const row = document.createElement("div");
@@ -229,6 +359,22 @@ export function renderDistributionSection(node, container) {
// Used by the new top-bar layout (distribution toggle is in Settings dropdown)
export function renderDistributionSettingsSection(node, container) {
const placeholder = document.createElement('div');
placeholder.style.cssText = "font-size: 9px; color: #666; padding: 2px 4px;";
placeholder.textContent = "Checking for Distributed companion plugin...";
container.appendChild(placeholder);
isDistributedAvailable().then(available => {
if (!available) {
placeholder.replaceWith(_renderDistributedInstallCard(node));
} else {
placeholder.remove();
_renderDistributionSettingsSectionReal(node, container);
}
});
}
function _renderDistributionSettingsSectionReal(node, container) {
// Ensure distribution state defaults exist
if (!node.state.worker_urls) node.state.worker_urls = [];
if (node.state.claim_timeout === undefined) node.state.claim_timeout = 600;
@@ -282,7 +428,7 @@ export function renderDistributionSettingsSection(node, container) {
workerList.style.cssText = "display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px;";
const renderWorkerEntries = () => {
workerList.innerHTML = "";
while (workerList.firstChild) workerList.removeChild(workerList.firstChild);
node.state.worker_urls.forEach((url, idx) => {
const row = document.createElement("div");