Signed-off-by: princepride <wangzhipeng628@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
156 lines
6.2 KiB
JavaScript
156 lines
6.2 KiB
JavaScript
/**
|
|
* @file Front-end extension for the vLLM-Omni nodes.
|
|
*
|
|
* Branding. Every vLLM-Omni node draws the vLLM-Omni "v" mark where LiteGraph
|
|
* would otherwise draw its default title dot, and is tinted by the kind of value
|
|
* it produces: blue nodes reach a server, amber and purple ones only assemble
|
|
* parameters, red ones pin what the server has loaded, teal ones carry media.
|
|
* A graph is then readable at a glance, and a node that turns out to be the
|
|
* wrong colour for where it sits is usually wired wrong.
|
|
*
|
|
* The tint is keyed off each node's declared output types rather than a list of
|
|
* node names, so a node added later is themed as soon as it returns one of the
|
|
* existing types.
|
|
*
|
|
* It also migrates Generate Video graphs saved before `duration` replaced the
|
|
* `num_frames` widget. See migrateNumFramesToDuration below.
|
|
*
|
|
* This file also used to host dynamic fields on vLLM-Omni nodes, driven by
|
|
* widget (in-node form fields) and input (connection link) values and changes.
|
|
* That is deliberately absent: it introduced too much complexity, and it even
|
|
* conflicted with the current backend (Python) validation for unknown reasons
|
|
* (pending ComfyUI upstream fixes).
|
|
*/
|
|
|
|
import { app } from "../../scripts/app.js";
|
|
|
|
const MARK_URL = new URL("./vllm-omni-mark.svg", import.meta.url).href;
|
|
const MARK_ASPECT = 157 / 165;
|
|
|
|
/**
|
|
* Title bar and body tints, one per node family.
|
|
*
|
|
* The hues are the logo's: the amber and blue of the "v" mark, and three stops
|
|
* of the gradient ring in the "O" (#9128A3, #CC5260, and the teal neighbouring
|
|
* its blue). All of them are dark because LiteGraph paints the title bar darker
|
|
* than the body and expects both to sit under light text; the ComfyUI light
|
|
* theme lightens custom node colours on its own.
|
|
*/
|
|
const FAMILIES = {
|
|
generate: { color: "#152A43", bgcolor: "#1F4269" },
|
|
sampling: { color: "#3A2C12", bgcolor: "#5C461C" },
|
|
model: { color: "#2E1233", bgcolor: "#4A1C52" },
|
|
deployment: { color: "#3A1A1F", bgcolor: "#5C2931" },
|
|
media: { color: "#0F2E2A", bgcolor: "#194A44" },
|
|
};
|
|
|
|
/** Output type -> family. Types absent here fall through to "generate". */
|
|
const FAMILY_BY_OUTPUT = {
|
|
SAMPLING_PARAMS: "sampling",
|
|
TTS_PARAMS: "model",
|
|
VIDEO_PARAMS: "model",
|
|
REMOTE_LORA: "deployment",
|
|
FASTH3_DEPLOYMENT: "deployment",
|
|
VIDEO_REFERENCES: "media",
|
|
};
|
|
|
|
function familyOf(nodeData) {
|
|
for (const type of nodeData.output ?? []) {
|
|
const family = FAMILY_BY_OUTPUT[type];
|
|
if (family) {
|
|
return family;
|
|
}
|
|
}
|
|
// What is left returns a generated artifact (IMAGE, VIDEO, AUDIO, STRING),
|
|
// which is only ever produced by a node that talks to a vLLM-Omni server.
|
|
return "generate";
|
|
}
|
|
|
|
/**
|
|
* Rewrite a pre-rename Generate Video graph in place.
|
|
*
|
|
* `duration` (seconds) took over the slot `num_frames` used to occupy, and
|
|
* ComfyUI restores widget values positionally, so a graph saved before the
|
|
* rename reads its frame count as a duration: the workflow this repository
|
|
* shipped stored 120 frames at 24 fps, which would come back as a 120-second
|
|
* request. Saved files still name the old widget under `inputs`, and that is
|
|
* what makes the rewrite both detectable and safe to apply exactly once -- once
|
|
* rewritten the node serializes `duration`, so a later load finds nothing to do.
|
|
*/
|
|
function migrateNumFramesToDuration(node, info) {
|
|
const savedBeforeRename = (info?.inputs ?? []).some((input) => input?.widget?.name === "num_frames");
|
|
if (!savedBeforeRename) {
|
|
return
|
|
}
|
|
const duration = node.widgets?.find((widget) => widget.name === "duration");
|
|
const fps = node.widgets?.find((widget) => widget.name === "fps");
|
|
if (!duration || !fps) {
|
|
return
|
|
}
|
|
// configure() has already placed the stale frame count in the duration widget.
|
|
const frames = Number(duration.value);
|
|
const rate = Number(fps.value);
|
|
if (!Number.isFinite(frames) || !Number.isFinite(rate) || rate <= 0) {
|
|
return
|
|
}
|
|
const seconds = Math.max(duration.options?.min ?? 0.1, Math.round((frames / rate) * 1000) / 1000);
|
|
console.info(
|
|
`vLLM-Omni: migrated Generate Video "${node.title}" from ${frames} frames to ${seconds}s at ${rate} fps.`,
|
|
);
|
|
duration.value = seconds;
|
|
}
|
|
|
|
const mark = new Image();
|
|
let markReady = false;
|
|
mark.addEventListener("load", () => {
|
|
markReady = true;
|
|
// The image resolves after the first frames are already on screen.
|
|
app.graph?.setDirtyCanvas?.(true, true);
|
|
});
|
|
mark.src = MARK_URL;
|
|
|
|
app.registerExtension({
|
|
name: "vllm.vllm_omni",
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
if (!nodeData.name.startsWith("VLLMOmni")) {
|
|
return
|
|
}
|
|
|
|
const { color, bgcolor } = FAMILIES[familyOf(nodeData)];
|
|
// On the prototype, not the instance: a colour the user picks by hand is
|
|
// written to the instance and shadows this, so manual overrides survive.
|
|
nodeType.prototype.color = color;
|
|
nodeType.prototype.bgcolor = bgcolor;
|
|
|
|
if (nodeData.name === "VLLMOmniGenerateVideo") {
|
|
const onConfigure = nodeType.prototype.onConfigure;
|
|
nodeType.prototype.onConfigure = function (info) {
|
|
onConfigure?.apply(this, arguments);
|
|
migrateNumFramesToDuration(this, info);
|
|
};
|
|
}
|
|
|
|
// Defining this replaces the default dot outright — LiteGraph draws its
|
|
// own square or circle only for nodes that leave onDrawTitleBox unset.
|
|
nodeType.prototype.onDrawTitleBox = function (ctx, titleHeight, _size, scale) {
|
|
// Zoomed out far enough that LiteGraph itself drops to flat shapes,
|
|
// the mark is a few muddy pixels. Leave the title bar clean instead.
|
|
if (!markReady || (scale !== undefined && scale < 0.5)) {
|
|
return
|
|
}
|
|
const height = titleHeight * 0.62;
|
|
const width = height * MARK_ASPECT;
|
|
ctx.drawImage(
|
|
mark,
|
|
titleHeight * 0.5 - width * 0.5,
|
|
titleHeight * -0.5 - height * 0.5,
|
|
width,
|
|
height,
|
|
);
|
|
};
|
|
},
|
|
async setup() {
|
|
console.info("vLLM-Omni Setup complete!")
|
|
},
|
|
})
|