Files
gsusgg-ComfyUI_CozyGen/js/web/cozygen_dynamic_input.js
gsusgg 7da9b874be Dev2 (#6)
* Fix page not loading if no choice_type

* Chain lora ipmrovements, workflows work but need to test further.

* More improvement of lora bypass function.Might be stable

* 1 feat: Add video output and enhance responsive UI
    2                                                                                      3 This commit introduces two major sets of changes: a new video                          output node and significant improvements to the UI layout and                          responsiveness.
    4                                                                                      5 Video Output:
    6 - Adds a new  node to save image batches as
      video files (webm, mp4, or gif).
    7 - Implements a  WebSocket message to notify
      the frontend upon completion.
    8 - Adds  and  as dependencies for video
      encoding.
    9                                                                                     10 UI Enhancements:                                                                    11 - Reduces padding and vertical spacing across all main UI
      components for a more compact and unified layout.
   12 - Adds a new  screen breakpoint () to the Tailwind CSS
      configuration.
   13 - Updates the controls form to use the new  breakpoint,
      improving the two-column layout on smaller screens.

* moved pagination controls in gallery to top,added options for per page load amount.

* added video/audio/gif support initial. most load and play, investigating outliers

* feat: Add video output, gallery sorting, and UI layout improvements

* Removed webp support for now, verified i2v and t2v work with basic nodes (should work anywhere, but ya know), fixed video playback not loading in preview (still doesnt show if page is unloaded during generation, but will save to gallery)

* ui update for static generate button, broke out input nodes to reduce complexity(dynamicinputnodes still function), improved model population of dropdowns with new choice input node

* fixed bugs, completed example workflows

* fixed max int stuck at 2048

* fixed >1 batch size image preview

* added qwen image edit example,bypass option now disables dropdown

* Update2 commit

* readme update

* cleanup
2025-09-24 12:16:32 -05:00

291 lines
14 KiB
JavaScript

import { app } from "/scripts/app.js";
(function () {
// Mapping of ComfyUI node types and their input names to CozyGen choice_type strings
const nodeCategoryMap = {
"CheckpointLoaderSimple": { "ckpt_name": "checkpoints" },
"VAELoader": { "vae_name": "vae" },
"LoraLoader": { "lora_name": "loras" },
"LoraSelect": { // Assuming LoraSelect node has inputs like lora_01, lora_02 etc.
"lora_01": "loras",
"lora_02": "loras",
"lora_03": "loras",
"lora_04": "loras",
"lora_05": "loras",
"lora_06": "loras",
"lora_07": "loras",
"lora_08": "loras",
"lora_09": "loras",
"lora_10": "loras",
"lora_11": "loras",
"lora_12": "loras",
"lora_13": "loras",
"lora_14": "loras",
"lora_15": "loras",
"lora_16": "loras",
"lora_17": "loras",
"lora_18": "loras",
"lora_19": "loras",
"lora_20": "loras",
},
"ClipLoader": { "clip_name": "clip" },
"UNETLoader": { "unet_name": "unet" },
"SamplerCustom": { "sampler_name": "sampler" },
"KSampler": { "sampler_name": "samplers_list", "scheduler": "schedulers_list" },
"Scheduler": { "scheduler_name": "scheduler" },
// Add more mappings as needed for other common loaders
};
// Helper function to determine if a value matches a given type
function valueMatchesType(value, type, options) {
if (type === "number" || type === "number (integer)") {
return typeof value === "number";
} else if (type === "combo") {
return options?.values?.includes(value);
} else if (type === "toggle") {
return typeof value === "boolean";
}
return typeof value === "string";
}
// Function to change the widgets on the CozyGenDynamicInput node
function changeCozyGenWidgets(node, connectedInputType, connectedInputProps, connectedWidget, targetNodeType, targetInputName) {
let paramType = "STRING";
let defaultValue = "";
let min = 0.0;
let max = 1.0;
let increment = null;
let multiline = false;
let choices = [];
let detectedWidgetType = connectedWidget?.type; // Use optional chaining
// Determine the type and properties based on the connected input
if (connectedInputType === "COMBO") { // Explicitly check for "COMBO" string type
paramType = "DROPDOWN";
// For COMBO types, choices are often in inputWidget.options.values
// or sometimes directly in input.options.values (which is connectedInputProps)
if (connectedWidget?.options?.values) {
choices = connectedWidget.options.values;
} else if (connectedInputProps.values) { // Fallback to connectedInputProps.values
choices = connectedInputProps.values;
} else if (Array.isArray(connectedWidget?.options?.default_value)) { // Sometimes default_value is the list
choices = connectedWidget.options.default_value;
} else if (Array.isArray(connectedWidget?.options?.input_type)) { // Another possible location for choices
choices = connectedWidget.options.input_type;
}
defaultValue = connectedInputProps.default;
if (!choices.includes(defaultValue) && choices.length > 0) {
defaultValue = choices[0];
} else if (choices.length === 0) {
defaultValue = "";
}
} else if (connectedInputType === "INT" || (detectedWidgetType === "number" && connectedInputProps.precision === 0)) {
paramType = "INT";
defaultValue = connectedInputProps.default || 0;
min = connectedInputProps.min || 0;
max = connectedInputProps.max || 0;
increment = connectedInputProps.step || 1;
} else if (connectedInputType === "FLOAT" || detectedWidgetType === "number") {
paramType = "FLOAT";
defaultValue = connectedInputProps.default || 0.0;
min = connectedInputProps.min || 0.0;
max = connectedInputProps.max || 0.0;
increment = connectedInputProps.step || 0.01;
} else if (connectedInputType === "STRING" || detectedWidgetType === "text") {
paramType = "STRING";
defaultValue = connectedInputProps.default || "";
multiline = connectedInputProps.multiline || false;
} else if (connectedInputType === "BOOLEAN" || detectedWidgetType === "toggle") {
paramType = "BOOLEAN";
defaultValue = connectedInputProps.default || false;
}
// Update the selected value of the param_type dropdown (widget at index 2)
node.widgets[2].value = paramType;
// Remove all widgets after param_type (index 2)
while (node.widgets.length > 3) { // Keep param_name, priority, param_type
node.widgets.pop();
}
// Add the default_value widget with the correct type
let defaultWidget;
if (paramType === "DROPDOWN") {
defaultWidget = node.addWidget("combo", "default_value", defaultValue, function(v) { node.properties["default_value"] = v; }, { values: choices });
let inferredChoiceType = "";
if (targetNodeType && targetInputName && nodeCategoryMap[targetNodeType] && nodeCategoryMap[targetNodeType][targetInputName]) {
inferredChoiceType = nodeCategoryMap[targetNodeType][targetInputName];
}
let choiceTypeWidget = node.addWidget("text", "choice_type", node.properties["choice_type"] || inferredChoiceType, function(v) { node.properties["choice_type"] = v; });
// If an inferred type is found and the property is currently empty, set it
if (inferredChoiceType && !node.properties["choice_type"]) {
node.properties["choice_type"] = inferredChoiceType;
choiceTypeWidget.value = inferredChoiceType; // Update widget value directly
}
// Add the display_bypass toggle for dropdowns
let displayBypassToggle = node.addWidget("toggle", "display_bypass", node.properties["display_bypass"], function(v) { node.properties["display_bypass"] = v; });
node.properties["display_bypass"] = displayBypassToggle.value;
} else if (paramType === "INT") {
defaultWidget = node.addWidget("number", "default_value", defaultValue, function(v) { node.properties["default_value"] = v; }, { precision: 0, min: min, max: max, step: increment });
} else if (paramType === "FLOAT") {
defaultWidget = node.addWidget("number", "default_value", defaultValue, function(v) { node.properties["default_value"] = v; }, { min: min, max: max, step: increment });
} else if (paramType === "BOOLEAN") {
defaultWidget = node.addWidget("toggle", "default_value", defaultValue, function(v) { node.properties["default_value"] = v; });
} else { // STRING
// For STRING, create the text widget first.
defaultWidget = node.addWidget("text", "default_value", defaultValue, function(v) { node.properties["default_value"] = v; }, { multiline: multiline });
// Then add the multiline toggle that controls it.
let multilineToggle = node.addWidget("toggle", "Multiline", multiline, function(v) {
// Update the multiline property of the default_value widget
defaultWidget.options.multiline = v;
node.properties["multiline"] = v; // Also update the property
// We need to tell the node to redraw to see the change from single to multi-line
node.setDirtyCanvas(true, true);
});
node.properties["multiline"] = multilineToggle.value; // Link to node.properties
}
// Link the widget value to node.properties
node.properties["default_value"] = defaultWidget.value;
// Add min/max/step for number types if applicable
if (paramType === "INT" || paramType === "FLOAT") {
let minWidget = node.addWidget("number", "min_value", min, function(v) { node.properties["min_value"] = v; }, { precision: (paramType === "INT" ? 0 : undefined) });
let maxWidget = node.addWidget("number", "max_value", max, function(v) { node.properties["max_value"] = v; }, { precision: (paramType === "INT" ? 0 : undefined) });
let incrementWidget = node.addWidget("number", "increment", increment, function(v) { node.properties["increment"] = v; }, { precision: (paramType === "INT" ? 0 : undefined) });
node.properties["min_value"] = minWidget.value;
node.properties["max_value"] = maxWidget.value;
node.properties["increment"] = incrementWidget.value;
// Add the randomize toggle
let randomizeToggle = node.addWidget("toggle", "add_randomize_toggle", node.properties["add_randomize_toggle"], function(v) { node.properties["add_randomize_toggle"] = v; });
} else {
// Ensure these properties are reset or not present if not a number type
node.properties["min_value"] = 0.0;
node.properties["max_value"] = 1.0;
node.properties["increment"] = 0.0;
}
// Ensure multiline and choices properties are correctly set/reset
node.properties["multiline"] = multiline;
const choicesInput = node.inputs.find(input => input.name === 'choices');
if (choicesInput) {
choicesInput.value = choices.join(",");
} else {
console.warn("CozyGen: 'choices' input object not found. This is unexpected.");
node.properties["choices"] = JSON.stringify(choices); // Fallback
}
// Update node size to fit new widgets
node.setDirtyCanvas(true);
}
// Function to adapt widgets based on connection
function adaptCozyGenWidgetsToConnection(node) {
if (!node.outputs || node.outputs.length === 0) {
return;
}
const links = node.outputs[0].links; // Assuming the first output is the dynamic one
if (links && links.length === 1) {
const link = node.graph.links[links[0]];
if (!link) return;
const theirNode = node.graph.getNodeById(link.target_id);
if (!theirNode || !theirNode.inputs) return;
const input = theirNode.inputs[link.target_slot];
if (!input) return;
// Get the actual widget object from the connected node's widgets array
const connectedWidget = theirNode.widgets.find(w => w.name === input.name);
if (!connectedWidget) return; // Should not happen for standard inputs
// Get the actual type and properties of the connected input
const connectedInputType = input.type; // This is still correct
const connectedInputProps = connectedWidget.options || {}; // Use the full widget's options
// Update param_name if it's still default
if (node.widgets[0].value === "Dynamic Parameter") {
node.widgets[0].value = input.name || "Dynamic Parameter";
}
changeCozyGenWidgets(node, connectedInputType, connectedInputProps, connectedWidget, theirNode.type, input.name);
} else {
// No connection or multiple connections, revert to default state
// Reset param_name to default
node.widgets[0].value = "Dynamic Parameter";
// Update param_type to STRING
node.widgets[2].value = "STRING";
// Remove all widgets after param_type (index 2)
while (node.widgets.length > 3) { // Keep param_name, priority, param_type
node.widgets.pop();
}
// Add default_value as STRING
let defaultWidget = node.addWidget("text", "default_value", "", function(v) { node.properties["default_value"] = v; }, { multiline: false });
node.properties["default_value"] = defaultWidget.value;
// Reset other properties
node.properties["min_value"] = 0.0;
node.properties["max_value"] = 1.0;
node.properties["increment"] = 0.0;
node.properties["multiline"] = false;
node.properties["choices"] = "";
node.properties["choice_type"] = "";
node.setDirtyCanvas(true);
}
}
// Setup function for CozyGenDynamicInput node
function setupCozyGenDynamicInputNode(nodeType) {
const onAdded = nodeType.prototype.onAdded;
nodeType.prototype.onAdded = function () {
onAdded?.apply(this, arguments);
// Initial adaptation when node is added
adaptCozyGenWidgetsToConnection(this);
};
const onAfterGraphConfigured = nodeType.prototype.onAfterGraphConfigured;
nodeType.prototype.onAfterGraphConfigured = function () {
onAfterGraphConfigured?.apply(this, arguments);
// Adaptation after graph is loaded/configured
adaptCozyGenWidgetsToConnection(this);
};
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function (_, index, connected) {
// Only adapt if the change is on the output and not during graph configuration
if (index === 0 && !app.configuringGraph) { // Assuming output is at index 0
adaptCozyGenWidgetsToConnection(this);
}
onConnectionsChange?.apply(this, arguments);
};
}
// Register the extension
app.registerExtension({
name: "cozygen.dynamic_input",
beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "CozyGenDynamicInput") {
setupCozyGenDynamicInputNode(nodeType);
}
},
});
})();