Files

482 lines
19 KiB
JavaScript

import { app } from "../../scripts/app.js";
const getValFromDialog = async (title, message, defaultValue) => {
return await app.extensionManager.dialog.prompt({
title,
message,
defaultValue,
});
}
const MAX_CHAR_VARNAME = 50;
function makeValidVariable(
varName,
textContent,
regex = /^[a-z_][a-z0-9_]*$/i
) {
if (
!varName ||
varName.trim() === "" ||
varName.length > MAX_CHAR_VARNAME ||
!regex.test(varName)
) {
alert(`${textContent}校验未通过!`);
return false;
}
return true;
}
app.registerExtension({
name: "Comfy.DynamicParams",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "DynamicParams") {
// 初始化默认属性
nodeType.prototype.properties = {
dynamic_params: []
};
// 保存原始方法
const origConfigure = nodeType.prototype.configure;
const onNodeCreated = nodeType.prototype.onNodeCreated;
// 添加节点创建方法
nodeType.prototype.onNodeCreated = function () {
const ret = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
// 初始化基本属性
if (!this.initialized) {
this.initialized = true;
this.properties = this.properties || {};
this.properties.dynamic_params = this.properties.dynamic_params || [];
this.dynamic_params = this.properties.dynamic_params;
// 清除现有widgets和连接点
this.widgets = this.widgets || [];
this.inputs = this.inputs || [];
this.outputs = this.outputs || [];
// 添加"添加参数"按钮
this.addWidget("button", "Add Parameter", "add_param", () => {
this.showAddParamDialog();
});
// 设置节点大小
this.setSize(this.computeSize());
}
return ret;
};
// 修改配置加载方法
nodeType.prototype.configure = function (info) {
// 避免重复调用configure
if (this._configuring) return;
this._configuring = true;
try {
// 确保properties和dynamic_params存在
this.properties = this.properties || {};
this.properties.dynamic_params = this.properties.dynamic_params || [];
this.dynamic_params = this.properties.dynamic_params;
// 调用原始configure
if (origConfigure) {
origConfigure.call(this, info);
}
// 合并新的属性
if (info.properties) {
this.properties = {
...this.properties,
...info.properties,
dynamic_params: info.properties.dynamic_params || this.properties.dynamic_params
};
this.dynamic_params = this.properties.dynamic_params;
}
// 清除现有widgets和连接点
this.widgets = [];
this.inputs = [];
this.outputs = [this.outputs[0]]; // python中RETURN_TYPES有一个默认placeholder占位, 再次加载workflow时直接使用
// 添加"Add Parameter"按钮
this.addWidget("button", "Add Parameter", "add_param", () => {
this.showAddParamDialog();
});
// 恢复参数
if (Array.isArray(this.dynamic_params) && this.dynamic_params.length > 0) {
this.dynamic_params.forEach(param => {
if (param && param.name && param.type) {
this.addDynamicParam(param.name, param.type, param.value, param.description, false);
}
});
}
// 恢复连接
if (Array.isArray(info.inputs)) {
info.inputs.forEach((input, index) => {
if (this.inputs[index]) {
this.inputs[index].link = input.link;
}
});
}
if (Array.isArray(info.outputs)) {
info.outputs.forEach((output, index) => {
if (this.outputs[index]) {
this.outputs[index].links = output.links || [];
}
});
}
// 更新widget状态
if (Array.isArray(this.inputs)) {
this.inputs.forEach(input => {
if (input && input.link != null && Array.isArray(this.widgets)) {
const widget = this.widgets.find(w => w && w.options && w.options.nameLinkable === input.name);
if (widget) {
widget.disabled = true;
}
}
});
}
this.setSize(this.computeSize());
} finally {
this._configuring = false;
}
};
// 添加参数对话框
nodeType.prototype.showAddParamDialog = async function () {
const outputLen = this?.outputs?.length;
const defaultVarValue = outputLen ? `var${outputLen + 1}` : "var1";
const name = await getValFromDialog("Variable", "Enter output variable name:", defaultVarValue);
if (!makeValidVariable(name, "名称")) {
return;
}
// 检查参数名是否已存在
if (Array.isArray(this.dynamic_params) &&
this.dynamic_params.some(p => p && p.name === name)) {
alert("变量名称已经存在");
return;
}
const type = await getValFromDialog("Type", "Enter output variable type:", "STRING");
if (!makeValidVariable(type, "类型", /^[*a-z_][a-z0-9_]*$/i)) {
return;
}
const defaultValue = await getValFromDialog("Default Value", "Enter output variable Default Value:", "");
if (defaultValue === null) return;
const description = await getValFromDialog("description", "Enter parameter description (optional):", "");
if (description === null) return;
this.addDynamicParam(name, type.toUpperCase(), defaultValue, description, true);
};
// 添加参数方法
nodeType.prototype.addDynamicParam = function (name, type, defaultValue, description, updateStorage = true) {
const validTypes = ["STRING", "INT", "FLOAT"];
if (!validTypes.includes(type)) {
if (updateStorage) {
console.warn("Invalid parameter type. Must be STRING, INT, or FLOAT");
}
return;
}
// 确保dynamic_params是数组
if (!Array.isArray(this.dynamic_params)) {
this.dynamic_params = [];
}
// 创建参数配置
const param = {
name,
type,
value: defaultValue,
description: description || ""
};
if (updateStorage) {
this.dynamic_params.push(param);
this.properties.dynamic_params = this.dynamic_params;
}
// 创建widget
let widget;
const widgetConfig = {
tooltip: description,
linkable: true,
nameLinkable: name
};
switch (type) {
case "INT":
widget = this.addWidget("number", name, parseInt(defaultValue) || 0,
(v) => this.updateParamValue(name, v),
{ ...widgetConfig, precision: 0 });
break;
case "FLOAT":
widget = this.addWidget("number", name, parseFloat(defaultValue) || 0.0,
(v) => this.updateParamValue(name, v),
{ ...widgetConfig, precision: 3 });
break;
default: // STRING
widget = this.addWidget("string", name, defaultValue || "",
(v) => this.updateParamValue(name, v),
widgetConfig);
}
// 添加input和output
this.addInput(name, type, { widget: widget });
this.addOutput(name, type);
};
// 更新参数值
nodeType.prototype.updateParamValue = function (name, value) {
if (!Array.isArray(this.dynamic_params)) return;
const param = this.dynamic_params.find(p => p && p.name === name);
if (param) {
param.value = value;
this.properties.dynamic_params = this.dynamic_params;
this.setDirtyCanvas(true, false);
}
};
// 删除参数方法
nodeType.prototype.removeParam = function (name) {
// 找到参数索引
const paramIndex = this.dynamic_params.findIndex(p => p.name === name);
if (paramIndex === -1) return;
// 删除参数
this.dynamic_params.splice(paramIndex, 1);
this.properties.dynamic_params = this.dynamic_params;
// 找到并删除相关的widget
const widgetIndex = this.widgets.findIndex(w => w.name === name);
if (widgetIndex !== -1) {
this.widgets.splice(widgetIndex, 1);
}
// 找到并删除相关的input和其连接
const inputIndex = this.inputs.findIndex(input => input.name === name);
if (inputIndex !== -1) {
// 如果input有连接,先删除连接
const input = this.inputs[inputIndex];
if (input.link !== null) {
const graph = app.graph;
if (graph) {
graph.removeLink(input.link);
}
}
// 删除input
this.inputs.splice(inputIndex, 1);
}
// 找到并删除相关的output和其连接
const outputIndex = this.outputs.findIndex(output => output.name === name);
if (outputIndex !== -1) {
// 如果output有连接,先删除所有连接
const output = this.outputs[outputIndex];
if (output.links && output.links.length > 0) {
const graph = app.graph;
if (graph) {
[...output.links].forEach(linkId => {
graph.removeLink(linkId);
});
}
}
// 删除output
this.outputs.splice(outputIndex, 1);
}
// 更新工作流配置
if (this.properties.connections) {
// 删除input连接配置
if (this.properties.connections.inputs) {
this.properties.connections.inputs = this.properties.connections.inputs.filter(
input => input.name !== name
);
}
// 删除output连接配置
if (this.properties.connections.outputs) {
this.properties.connections.outputs = this.properties.connections.outputs.filter(
output => output.name !== name
);
}
}
// 删除widget值
if (this.widgets_values) {
const valueIndex = this.widgets.findIndex(w => w.name === name);
if (valueIndex !== -1) {
this.widgets_values.splice(valueIndex, 1);
}
}
// 更新序列化数据
this.onSerialize({
properties: this.properties
});
// 重新配置节点
this.setDirtyCanvas(true, true);
this.setSize(this.computeSize());
// 通知图表已修改
if (app.graph) {
app.graph.setDirtyCanvas(true, true);
app.graph.change();
}
};
// 序列化方法
nodeType.prototype.onSerialize = function (o) {
if (!o.properties) {
o.properties = {};
}
// 更新dynamic_params
o.properties.dynamic_params = this.dynamic_params;
// 更新连接信息
o.properties.connections = {
inputs: this.inputs.map(input => ({
name: input.name,
link: input.link,
type: input.type
})),
outputs: this.outputs.map(output => ({
name: output.name,
links: output.links,
type: output.type
}))
};
};
// 反序列化方法
nodeType.prototype.onConfigure = function (o) {
if (o.properties) {
this.properties = o.properties;
if (Array.isArray(o.properties.dynamic_params)) {
this.dynamic_params = o.properties.dynamic_params;
}
}
};
// 保存原始的getExtraMenuOptions方法
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
// 重写getExtraMenuOptions方法
nodeType.prototype.getExtraMenuOptions = function (_, options) {
// 调用原始方法
if (origGetExtraMenuOptions) {
origGetExtraMenuOptions.apply(this, arguments);
}
// 如果没有动态参数,直接返回
if (!this.dynamic_params || !this.dynamic_params.length) {
return options;
}
// 添加分隔符
options.push(null);
// 为每个动态参数添加删除选项
this.dynamic_params.forEach(param => {
if (param && param.name) {
options.push({
content: `Delete Parameter "${param.name}"`,
callback: () => {
if (confirm(`Are you sure you want to delete parameter "${param.name}"?`)) {
this.removeParam(param.name);
}
}
});
}
});
// return options;
};
// 添加处理 widget 输入值的方法
nodeType.prototype.onWidgetChanged = function (name, value, old_value, widget) {
if (widget && widget.type === "*") {
// 如果是从 Primitive Node 传入的值,优先使用该值
return value;
}
return old_value;
};
// 处理连接状态变化
nodeType.prototype.onConnectionsChange = function (slotType, slot, isConnected, link_info, ioSlot) {
if (!ioSlot || slotType !== LiteGraph.INPUT || !Array.isArray(this.widgets)) return;
const widget = this.widgets.find(w => w && w.options && w.options.nameLinkable === ioSlot.name);
if (widget) {
widget.disabled = isConnected;
}
};
// 处理输入值更新
nodeType.prototype.onExecute = function () {
for (const widget of this.widgets) {
if (!widget.options?.nameLinkable) continue;
const input = this.inputs.find(input => input.name === widget.options.nameLinkable);
if (input?.link !== null) {
const value = this.getInputData(input.slot);
if (value !== undefined) {
widget.value = value;
this.updateParamValue(widget.options.nameLinkable, value);
}
}
}
};
// 绘制连接点
nodeType.prototype.onDrawForeground = function (ctx) {
if (this.flags.collapsed) return;
// 不再绘制外部连接点
// 如果需要其他UI元素的绘制,可以在这里添加
};
// 由于移除了外部连接点,相应的鼠标交互逻辑也需要移除
nodeType.prototype.onMouseDown = function (e, localPos) {
// 不再处理外部连接点的点击
return false;
};
// 处理输入值变化
nodeType.prototype.onInputChanged = function (slot, value) {
const input = this.inputs[slot];
if (!input) return;
const widget = this.widgets.find(w => w.options.nameLinkable === input.name);
if (widget) {
widget.value = value;
this.updateParamValue(input.name, value);
}
};
// 切换参数模式
nodeType.prototype.toggleParamMode = function (name) {
const param = this.dynamic_params.find(p => p.name === name);
if (!param) return;
param.widget_mode = !param.widget_mode;
// 重新配置节点以更新UI
this.onConfigure({ properties: this.properties });
};
}
}
});