feat: live sync linked director controls

This commit is contained in:
OpenClaw Agent
2026-07-16 16:14:10 +00:00
parent 4dfcb1e597
commit 4c0358c11b

View File

@@ -11443,6 +11443,66 @@ app.registerExtension({
}
const self = this;
this._resolveLinkedInputValue = function (inputName, options = {}) {
const graph = app.graph;
const visited = new Set();
const readNodeOutputValue = (node, slotIndex) => {
if (!node) return null;
if (Array.isArray(node.widgets)) {
for (const widget of node.widgets) {
const value = widget?.value;
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
}
}
if (Array.isArray(node.outputs) && slotIndex != null && slotIndex >= 0) {
const outputName = node.outputs[slotIndex]?.name;
if (outputName && Array.isArray(node.widgets)) {
const namedWidget = node.widgets.find(w => w?.name === outputName);
const value = namedWidget?.value;
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
}
}
return null;
};
const walkLink = (linkId) => {
if (!graph || linkId == null) return null;
if (visited.has(linkId)) return null;
visited.add(linkId);
const link = graph.links?.[linkId];
if (!link) return null;
const originNode = graph.getNodeById?.(link.origin_id);
if (!originNode) return null;
const comfyClass = originNode.comfyClass || originNode.type || "";
if (/reroute/i.test(comfyClass)) {
const upstreamLink = originNode.inputs?.[0]?.link;
if (upstreamLink != null) {
return walkLink(upstreamLink);
}
}
return readNodeOutputValue(originNode, link.origin_slot);
};
const input = self.inputs?.find(i => i.name === inputName);
const value = input?.link != null ? walkLink(input.link) : null;
if (value == null || !Number.isFinite(value)) return null;
if (options.integer) {
return Math.round(value);
}
const decimals = Number.isInteger(options.decimals) ? options.decimals : 3;
return parseFloat(value.toFixed(decimals));
};
this._syncGlobalPromptFromLink = function () {
const globalInput = self.inputs?.find(i => i.name === "global_prompt");
if (globalInput && globalInput.link !== null && globalInput.link !== undefined) {
@@ -11497,6 +11557,51 @@ app.registerExtension({
}
}
};
this._syncTimingFromLinks = function () {
const linkedStart = self._resolveLinkedInputValue("start", { decimals: 3 });
const linkedEnd = self._resolveLinkedInputValue("end", { decimals: 3 });
const linkedDuration = self._resolveLinkedInputValue("duration", { decimals: 3 });
const snapshot = `${linkedStart ?? ""}|${linkedEnd ?? ""}|${linkedDuration ?? ""}`;
if (snapshot === self._lastLinkedTimingSnapshot) {
return;
}
self._lastLinkedTimingSnapshot = snapshot;
let changed = false;
const applyWidget = (name, value) => {
if (value == null) return;
const widget = self.widgets?.find(w => w.name === name);
if (!widget) return;
if (widget.value === value) return;
widget.value = value;
if (widget.callback) {
try {
widget.callback(value);
} catch (e) {}
}
changed = true;
};
applyWidget("start_second", linkedStart);
if (linkedDuration != null) {
applyWidget("duration_seconds", Math.max(0.1, linkedDuration));
} else {
applyWidget("end_second", linkedEnd);
}
if (changed) {
if (self._ltxSettingsRefresh) {
try { self._ltxSettingsRefresh(); } catch (e) {}
}
if (self._timelineEditor) {
self._timelineEditor.render();
}
if (self.setDirtyCanvas) {
self.setDirtyCanvas(true, true);
}
}
};
const origOnConnectionsChange = this.onConnectionsChange;
this.onConnectionsChange = function (type, index, connected, link_info) {
@@ -11504,6 +11609,8 @@ app.registerExtension({
origOnConnectionsChange.apply(this, arguments);
}
self._syncGlobalPromptFromLink();
self._syncTimingFromLinks();
self._syncResolutionFromLinks?.();
};
const origOnDrawForeground = this.onDrawForeground;
@@ -11512,6 +11619,8 @@ app.registerExtension({
origOnDrawForeground.apply(this, arguments);
}
self._syncGlobalPromptFromLink();
self._syncTimingFromLinks();
self._syncResolutionFromLinks?.();
};
// --- LTX Director settings panel (Stage 1: Resolution | Timing/Reference) ---
@@ -11648,6 +11757,65 @@ app.registerExtension({
syncPreset(); syncFps();
const refreshResolutionInputLocks = () => {
const widthLinked = !!node.inputs?.find(i => i.name === "custom_width")?.link;
const heightLinked = !!node.inputs?.find(i => i.name === "custom_height")?.link;
const fpsLinked = !!node.inputs?.find(i => i.name === "frame_rate")?.link;
const presetLinked = widthLinked || heightLinked;
widthIn.disabled = widthLinked;
heightIn.disabled = heightLinked;
presetSel.disabled = presetLinked;
fpsIn.disabled = fpsLinked;
fpsSel.disabled = fpsLinked;
};
node._syncResolutionFromLinks = () => {
const linkedWidth = node._resolveLinkedInputValue?.("custom_width", { integer: true });
const linkedHeight = node._resolveLinkedInputValue?.("custom_height", { integer: true });
const linkedFps = node._resolveLinkedInputValue?.("frame_rate", { integer: true });
const snapshot = `${linkedWidth ?? ""}|${linkedHeight ?? ""}|${linkedFps ?? ""}`;
refreshResolutionInputLocks();
if (snapshot === node._lastLinkedResolutionSnapshot) {
return;
}
node._lastLinkedResolutionSnapshot = snapshot;
let changed = false;
const widthWidget = getW("custom_width");
if (linkedWidth != null && widthWidget && widthWidget.value !== linkedWidth) {
widthWidget.value = linkedWidth;
if (widthWidget.callback) {
try { widthWidget.callback(linkedWidth); } catch (e) {}
}
changed = true;
}
const heightWidget = getW("custom_height");
if (linkedHeight != null && heightWidget && heightWidget.value !== linkedHeight) {
heightWidget.value = linkedHeight;
if (heightWidget.callback) {
try { heightWidget.callback(linkedHeight); } catch (e) {}
}
changed = true;
}
if (linkedFps != null) {
const currentFps = parseInt(getW("frame_rate")?.value) || 24;
if (currentFps !== linkedFps) {
applyFrameRate(Math.max(1, linkedFps));
changed = true;
}
}
if (changed && node._ltxSettingsRefresh) {
try { node._ltxSettingsRefresh(); } catch (e) {}
}
};
// ---- shared seconds<->frames timing infrastructure ----
const TIME_NAMES = ["start_second", "end_second", "duration_seconds", "start_frame", "end_frame", "duration_frames"];
const timeMode = () => { const dm = getW("display_mode"); return (dm && dm.value === "frames") ? "frames" : "seconds"; };
@@ -11747,6 +11915,7 @@ app.registerExtension({
if (rm && rm.value != null) rmSel.value = rm.value;
if (rs) refIn.value = rs.value;
syncPreset(); syncFps();
refreshResolutionInputLocks();
if (typeof unitSel !== "undefined" && unitSel) unitSel.value = timeMode();
timeRefreshers.forEach(fn => fn());
ensureTimingHidden();