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);
};
}