Fast_deploy / ComfyUI-MiniMaxH3-Context-Loop /web /h3_run_assets_core.mjs
dzmdd's picture
Upload folder using huggingface_hub (part 3)
8fb6433 verified
Raw History Blame Contribute Delete
8.92 kB
export const MAX_ASSET_BINDINGS = 12;
export const ASSET_ROLES = Object.freeze([
["picture", "Picture"],
["video", "Video"],
["audio_reference", "Audio reference"],
["source_track", "Source track"],
]);
export const SEMANTIC_ANCHOR_BUNDLE_NODE = "MiniMaxH3SemanticAnchorBundle";
export const SEMANTIC_PICTURE_ANCHOR_NODE = "MiniMaxH3SemanticPictureAnchor";
const MEDIA_WIDGET_NAMES = ["image", "audio", "file", "video", "path", "filename"];
const MEDIA_EXTENSION = /\.(?:png|jpe?g|webp|gif|bmp|tiff?|wav|mp3|flac|ogg|m4a|aac|mp4|mov|mkv|webm|avi)(?:\s+\[(?:input|output|temp)\])?$/i;
export function assetInputNumber(input) {
const match = /^asset_(\d+)$/.exec(String(input?.name ?? ""));
return match ? Number(match[1]) : null;
}
export function nodeType(node) {
return node?.comfyClass ?? node?.type ?? "";
}
export function mediaWidget(node, preferred = "") {
const widgets = node?.widgets ?? [];
if (preferred) {
const exact = widgets.find((widget) => widget.name === preferred);
if (exact) return exact;
}
for (const name of MEDIA_WIDGET_NAMES) {
const widget = widgets.find((item) => item.name === name
&& typeof item.value === "string");
if (widget) return widget;
}
return widgets.find((widget) => typeof widget.value === "string"
&& MEDIA_EXTENSION.test(widget.value.trim())) ?? null;
}
export function inferAssetRole(outputType, sourceType = "") {
const type = String(outputType ?? "").toUpperCase();
if (type === "VIDEO" || /video/i.test(sourceType)) return "video";
if (type === "AUDIO" || /audio/i.test(sourceType)) return "audio_reference";
return "picture";
}
function randomBindingId() {
if (typeof globalThis.crypto?.randomUUID === "function") {
return globalThis.crypto.randomUUID();
}
const random = Math.random().toString(16).slice(2);
return `asset-${Date.now().toString(16)}-${random}`;
}
function sourceForInput(manager, input) {
const link = input?.link == null ? null : manager?.graph?.links?.[input.link];
const source = link ? manager.graph?.getNodeById?.(link.origin_id) : null;
return source ? {source, link} : null;
}
function inputByName(node, name) {
return node?.inputs?.find((input) => input.name === name) ?? null;
}
function linkedNode(node, inputName) {
const input = inputByName(node, inputName);
const link = input?.link == null ? null : node?.graph?.links?.[input.link];
const source = link ? node.graph?.getNodeById?.(link.origin_id) : null;
return source ? {source, link, input} : null;
}
function sourceBinding(manager, source, link, createId, {
input = null, roleOverride = "", labelPrefix = "",
} = {}) {
source.properties ??= {};
const outputSlot = Number(link.origin_slot) || 0;
source.properties.h3_asset_binding_ids ??= {};
source.properties.h3_asset_binding_ids[outputSlot] ||= (
(outputSlot === 0 && source.properties.h3_asset_binding_id)
|| createId()
);
const bindingId = source.properties.h3_asset_binding_ids[outputSlot];
const output = source.outputs?.[outputSlot] ?? {};
const widget = mediaWidget(source);
const inferred = roleOverride || inferAssetRole(output.type, nodeType(source));
const role = manager.properties.h3_asset_roles[bindingId] ?? inferred;
manager.properties.h3_asset_roles[bindingId] = role;
const title = String(source.title || nodeType(source) || `Node ${source.id}`);
if (input) input.label = `${assetInputNumber(input)}: ${title}`;
return {
binding_id: bindingId,
label: labelPrefix ? `${labelPrefix} · ${title}` : title,
role,
node_id: String(source.id ?? ""),
node_type: nodeType(source),
node_title: String(source.title ?? ""),
output_slot: outputSlot,
output_type: String(output.type ?? ""),
widget_name: String(widget?.name ?? ""),
original_value: typeof widget?.value === "string" ? widget.value : "",
};
}
export function collectSemanticAnchorBindings(manager, createId = randomBindingId) {
manager.properties ??= {};
manager.properties.h3_asset_roles ??= {};
const bundleLink = linkedNode(manager, "tagged_references");
if (!bundleLink || nodeType(bundleLink.source) !== SEMANTIC_ANCHOR_BUNDLE_NODE) {
return [];
}
const bindings = [];
const seenNodes = new Set();
let linked = linkedNode(bundleLink.source, "anchors");
while (linked && nodeType(linked.source) === SEMANTIC_PICTURE_ANCHOR_NODE
&& !seenNodes.has(linked.source)) {
const anchor = linked.source;
seenNodes.add(anchor);
const tag = String(anchor.widgets?.find((item) => item.name === "tag")?.value
?? "semantic").trim();
const image = linkedNode(anchor, "image");
if (image) {
bindings.push(sourceBinding(manager, image.source, image.link, createId, {
roleOverride: "picture",
labelPrefix: `#${tag || "semantic"}`,
}));
}
linked = linkedNode(anchor, "previous");
}
return bindings.reverse();
}
export function collectAssetBindings(manager, createId = randomBindingId) {
manager.properties ??= {};
manager.properties.h3_asset_roles ??= {};
const bindings = [];
for (const input of manager.inputs ?? []) {
const number = assetInputNumber(input);
if (number == null || input.link == null) continue;
const linked = sourceForInput(manager, input);
if (!linked) continue;
const {source, link} = linked;
bindings.push(sourceBinding(
manager, source, link, createId, {input}));
}
const seen = new Set(bindings.map((binding) => binding.binding_id));
for (const binding of collectSemanticAnchorBindings(manager, createId)) {
if (seen.has(binding.binding_id)) continue;
seen.add(binding.binding_id);
bindings.push(binding);
}
return bindings;
}
function graphNodes(graph) {
return graph?._nodes ?? graph?.nodes ?? [];
}
function compatibleLoader(node, binding) {
const widget = mediaWidget(node, binding.widget_name);
if (!widget) return false;
const output = node.outputs?.[Number(binding.output_slot)]
?? node.outputs?.find((item) => item.type === binding.output_type);
return !binding.output_type || output?.type === binding.output_type;
}
export function findAssetTarget(graph, binding) {
const nodes = graphNodes(graph);
const persistent = nodes.filter((node) =>
(node?.properties?.h3_asset_binding_ids?.[Number(binding.output_slot)]
=== binding.binding_id
|| node?.properties?.h3_asset_binding_id === binding.binding_id)
&& compatibleLoader(node, binding));
if (persistent.length === 1) return {node: persistent[0], match: "binding"};
const byId = graph?.getNodeById?.(binding.node_id)
?? graph?.getNodeById?.(Number(binding.node_id));
if (byId && nodeType(byId) === binding.node_type
&& compatibleLoader(byId, binding)) {
return {node: byId, match: "node_id"};
}
const sameType = nodes.filter((node) => nodeType(node) === binding.node_type
&& compatibleLoader(node, binding));
if (sameType.length === 1) return {node: sameType[0], match: "compatible_type"};
const compatible = nodes.filter((node) => compatibleLoader(node, binding));
if (compatible.length === 1) return {node: compatible[0], match: "compatible_loader"};
return {
node: null,
match: compatible.length || sameType.length || persistent.length
? "ambiguous" : "missing",
};
}
export function applyAssetBinding(graph, binding) {
if (!binding?.restore_value) {
return {applied: false, reason: "media_missing", binding};
}
const target = findAssetTarget(graph, binding);
if (!target.node) return {applied: false, reason: target.match, binding};
const widget = mediaWidget(target.node, binding.widget_name);
if (!widget) return {applied: false, reason: "widget_missing", binding};
const previous = widget.value;
const options = widget.options?.values;
if (Array.isArray(options) && !options.includes(binding.restore_value)) {
options.push(binding.restore_value);
options.sort((left, right) => String(left).localeCompare(String(right)));
}
widget.value = binding.restore_value;
widget.callback?.(binding.restore_value);
target.node.onWidgetChanged?.(
widget.name, binding.restore_value, previous, widget);
target.node.properties ??= {};
target.node.properties.h3_asset_binding_ids ??= {};
target.node.properties.h3_asset_binding_ids[Number(binding.output_slot) || 0]
= binding.binding_id;
return {applied: true, match: target.match, node: target.node, binding};
}