diff --git a/js/msr_character.js b/js/msr_character.js index 5272000..931ca61 100644 --- a/js/msr_character.js +++ b/js/msr_character.js @@ -34,9 +34,7 @@ function resolvePreviewUrlFromOrigin(originNode) { if (preview) return preview; const widgets = originNode.widgets || []; - const fileWidget = widgets.find((widget) => - ["image", "filename", "file"].includes(widget.name) - ); + 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 : ""; @@ -91,27 +89,38 @@ function setWidgetValue(node, widget, value) { } } +function syncFormFromWidgets(node) { + const description = findWidget(node, "description")?.value || ""; + const alias = findWidget(node, "alias")?.value || ""; + + if (node._msrAliasInput && node._msrAliasInput.value !== alias) { + node._msrAliasInput.value = alias; + } + if (node._msrDescriptionInput && node._msrDescriptionInput.value !== description) { + node._msrDescriptionInput.value = description; + } + if (node._msrMeta) { + node._msrMeta.innerHTML = [ + alias + ? `Alias: @${String(alias).replace(/^@/, "")}` + : "Alias: none", + description + ? `
${description}
` + : `
Description will be used for prompt replacement outside MSR prefix mode.
`, + ].join(""); + } +} + function refreshCharacterPreview(node) { - if (!node._msrPreviewContainer) return; - const container = node._msrPreviewContainer; - container.innerHTML = ""; + if (!node._msrPreviewRow) return; 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 aliasWidget = findWidget(node, "alias"); - const descriptionWidget = findWidget(node, "description"); - - const topRow = document.createElement("div"); - Object.assign(topRow.style, { - display: "flex", - gap: "6px", - minHeight: "74px", - }); + const previewRow = node._msrPreviewRow; + previewRow.innerHTML = ""; if (previewUrls.length) { previewUrls.forEach((previewUrl) => { @@ -126,7 +135,7 @@ function refreshCharacterPreview(node) { border: "1px solid #444", background: "#111", }); - topRow.appendChild(image); + previewRow.appendChild(image); }); } else { const placeholder = document.createElement("div"); @@ -143,9 +152,27 @@ function refreshCharacterPreview(node) { border: "1px dashed #444", background: "#181818", }); - topRow.appendChild(placeholder); + previewRow.appendChild(placeholder); } + syncFormFromWidgets(node); +} + +function buildCharacterUi(node) { + const container = document.createElement("div"); + Object.assign(container.style, { + boxSizing: "border-box", + width: "100%", + padding: "6px 2px 2px 2px", + }); + + const previewRow = document.createElement("div"); + Object.assign(previewRow.style, { + display: "flex", + gap: "6px", + minHeight: "74px", + }); + const meta = document.createElement("div"); Object.assign(meta.style, { marginTop: "8px", @@ -153,15 +180,6 @@ function refreshCharacterPreview(node) { color: "#aaa", lineHeight: "1.4", }); - meta.innerHTML = [ - alias ? `Alias: @${String(alias).replace(/^@/, "")}` : "Alias: none", - description - ? `
${description}
` - : `
Description will be used for prompt replacement outside MSR prefix mode.
`, - ].join(""); - - container.appendChild(topRow); - container.appendChild(meta); const form = document.createElement("div"); Object.assign(form.style, { @@ -180,7 +198,6 @@ function refreshCharacterPreview(node) { const aliasInput = document.createElement("input"); aliasInput.type = "text"; - aliasInput.value = alias || ""; aliasInput.placeholder = "optional alias, e.g. bob"; Object.assign(aliasInput.style, { width: "100%", @@ -193,7 +210,8 @@ function refreshCharacterPreview(node) { fontSize: "12px", }); aliasInput.addEventListener("input", () => { - setWidgetValue(node, aliasWidget, aliasInput.value); + setWidgetValue(node, findWidget(node, "alias"), aliasInput.value); + syncFormFromWidgets(node); }); const descLabel = document.createElement("label"); @@ -204,7 +222,6 @@ function refreshCharacterPreview(node) { }); const descInput = document.createElement("textarea"); - descInput.value = description || ""; descInput.placeholder = "Character description..."; Object.assign(descInput.style, { width: "100%", @@ -220,14 +237,25 @@ function refreshCharacterPreview(node) { lineHeight: "1.35", }); descInput.addEventListener("input", () => { - setWidgetValue(node, descriptionWidget, descInput.value); + setWidgetValue(node, findWidget(node, "description"), descInput.value); + syncFormFromWidgets(node); }); form.appendChild(aliasLabel); form.appendChild(aliasInput); form.appendChild(descLabel); form.appendChild(descInput); + + container.appendChild(previewRow); + container.appendChild(meta); container.appendChild(form); + + node._msrPreviewContainer = container; + node._msrPreviewRow = previewRow; + node._msrMeta = meta; + node._msrAliasInput = aliasInput; + node._msrDescriptionInput = descInput; + return container; } async function analyzeCharacterNode(node, buttonWidget) { @@ -261,7 +289,12 @@ async function analyzeCharacterNode(node, buttonWidget) { throw new Error(result.message || "Unknown analysis error"); } - setWidgetValue(node, descriptionWidget, result.description || ""); + const description = result.description || ""; + setWidgetValue(node, descriptionWidget, description); + if (node._msrDescriptionInput) { + node._msrDescriptionInput.value = description; + } + syncFormFromWidgets(node); refreshCharacterPreview(node); } catch (error) { console.error("[MSRCharacter] analyze failed", error); @@ -272,6 +305,16 @@ async function analyzeCharacterNode(node, buttonWidget) { } } +function hideNodeWidget(widget) { + if (!widget) return; + widget.hidden = true; + if (!widget.options) widget.options = {}; + widget.options.hidden = true; + if (widget.element) { + widget.element.style.display = "none"; + } +} + app.registerExtension({ name: "Comfy.MSRCharacterCS", async beforeRegisterNodeDef(nodeType, nodeData) { @@ -290,13 +333,7 @@ app.registerExtension({ } if (!this._msrPreviewWidget) { - const previewContainer = document.createElement("div"); - Object.assign(previewContainer.style, { - boxSizing: "border-box", - width: "100%", - padding: "6px 2px 2px 2px", - }); - this._msrPreviewContainer = previewContainer; + const previewContainer = buildCharacterUi(this); this._msrPreviewWidget = this.addDOMWidget("msr_character_ui", "msr_character_ui", previewContainer, { getValue: () => "", setValue: () => {}, @@ -314,19 +351,19 @@ app.registerExtension({ const originalConfigure = this.onConfigure; this.onConfigure = function () { const result = originalConfigure?.apply(this, arguments); - setTimeout(() => refreshCharacterPreview(this), 0); + setTimeout(() => { + syncFormFromWidgets(this); + refreshCharacterPreview(this); + }, 0); return result; }; ["alias", "description"].forEach((widgetName) => { - const widget = findWidget(this, widgetName); - if (!widget) return; - widget.hidden = true; - if (!widget.options) widget.options = {}; - widget.options.hidden = true; + hideNodeWidget(findWidget(this, widgetName)); }); this.size[0] = Math.max(this.size[0] || 0, 330); + syncFormFromWidgets(this); refreshCharacterPreview(this); }; }