const { app } = window.comfyAPI.app; const { api } = window.comfyAPI.api; function findWidget(node, name) { return (node.widgets || []).find((widget) => widget.name === name); } function findInput(node, name) { return (node.inputs || []).find((input) => input.name === name); } function getOriginNodeForInput(node, inputName) { const input = findInput(node, inputName); if (!input || input.link == null) return null; const link = app.graph.links[input.link]; if (!link) return null; return app.graph.getNodeById(link.origin_id); } function coercePreviewUrl(value) { if (!value) return ""; if (typeof value === "string") return value; if (typeof value === "object") { if (typeof value.src === "string") return value.src; if (typeof value.url === "string") return value.url; } return ""; } function resolvePreviewUrlFromOrigin(originNode) { if (!originNode) return ""; const preview = Array.isArray(originNode.imgs) ? coercePreviewUrl(originNode.imgs[0]) : ""; if (preview) return preview; const widgets = originNode.widgets || []; const fileWidget = widgets.find((widget) => ["image", "filename", "file"].includes(widget.name) ); const subfolderWidget = widgets.find((widget) => widget.name === "subfolder"); const filename = typeof fileWidget?.value === "string" ? fileWidget.value : ""; const subfolder = typeof subfolderWidget?.value === "string" ? subfolderWidget.value : ""; if (!filename) return ""; return api.apiURL( `/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=${encodeURIComponent(subfolder)}` ); } async function imageInputToDataUrl(node, inputName) { const originNode = getOriginNodeForInput(node, inputName); const previewUrl = resolvePreviewUrlFromOrigin(originNode); if (!previewUrl) return null; try { const response = await fetch(previewUrl); const blob = await response.blob(); return await new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }); } catch (error) { console.error("[MSRCharacter] Failed to load preview image", error); return null; } } function setWidgetValue(node, widget, value) { if (!widget) return; const previous = widget.value; widget.value = value; if (widget.element) { if (widget.element.type === "checkbox") { widget.element.checked = !!value; } else { widget.element.value = value; } } if (widget.callback) { try { widget.callback(value); } catch (error) {} } if (node.onWidgetChanged) { try { node.onWidgetChanged(widget.name, value, previous, widget); } catch (error) {} } if (node.properties) { node.properties[widget.name] = value; } } function refreshCharacterPreview(node) { if (!node._msrPreviewContainer) return; const container = node._msrPreviewContainer; container.innerHTML = ""; const inputNames = ["image_1", "image_2"]; const previewUrls = inputNames .map((inputName) => resolvePreviewUrlFromOrigin(getOriginNodeForInput(node, inputName))) .filter(Boolean); const description = findWidget(node, "description")?.value || ""; const alias = findWidget(node, "alias")?.value || ""; const topRow = document.createElement("div"); Object.assign(topRow.style, { display: "flex", gap: "6px", minHeight: "74px", }); if (previewUrls.length) { previewUrls.forEach((previewUrl) => { const image = document.createElement("img"); image.src = previewUrl; Object.assign(image.style, { flex: "1", minWidth: "0", height: "74px", objectFit: "cover", borderRadius: "6px", border: "1px solid #444", background: "#111", }); topRow.appendChild(image); }); } else { const placeholder = document.createElement("div"); placeholder.textContent = "Connect up to 2 image nodes"; Object.assign(placeholder.style, { width: "100%", height: "74px", display: "flex", alignItems: "center", justifyContent: "center", color: "#777", fontSize: "11px", borderRadius: "6px", border: "1px dashed #444", background: "#181818", }); topRow.appendChild(placeholder); } const meta = document.createElement("div"); Object.assign(meta.style, { marginTop: "8px", fontSize: "11px", color: "#aaa", lineHeight: "1.4", }); meta.innerHTML = [ alias ? `Alias: @${String(alias).replace(/^@/, "")}` : "Alias: none", description ? `