Stabilize MSR character form syncing

This commit is contained in:
OpenClaw Agent
2026-07-10 10:10:56 +00:00
parent 9b62c51a55
commit c2f39a2f0e

View File

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