multiple lora node
This commit is contained in:
+15
-4
@@ -396,16 +396,25 @@ class DrawThingsLoRA:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
# fmt: off
|
||||
return {
|
||||
first_model_widget = ("DT_MODEL", { "model_type": "loras", "tooltip": "The model used." })
|
||||
model_widget = ("DT_MODEL", { "model_type": "loras", "tooltip": "The model used.", "hidden": True, "default": None })
|
||||
first_weight_widget = ("FLOAT", { "default": 1.00, "min": -5.00, "max": 5.00, "step": 0.01, "tooltip": "How strongly to modify the diffusion model. This value can be negative."})
|
||||
weight_widget = ("FLOAT", { "default": 1.00, "min": -5.00, "max": 5.00, "step": 0.01, "tooltip": "How strongly to modify the diffusion model. This value can be negative.", "hidden": True})
|
||||
types = {
|
||||
"required": {
|
||||
"lora_name": ("DT_MODEL", { "model_type": "loras", "tooltip": "The model used." }),
|
||||
"lora_weight": ("FLOAT", { "default": 1.00, "min": -5.00, "max": 5.00, "step": 0.01, "tooltip": "How strongly to modify the diffusion model. This value can be negative."}),
|
||||
"buttons": ("DT_BUTTONS", { "default": None }),
|
||||
"model": first_model_widget,
|
||||
"weight": first_weight_widget,
|
||||
},
|
||||
"optional": {
|
||||
# "lora": ("DT_LORA",),
|
||||
},
|
||||
}
|
||||
for i in range(2, 9):
|
||||
types["required"]["lora_" + str(i)] = model_widget
|
||||
types["required"]["weight_" + str(i)] = weight_widget
|
||||
# fmt: on
|
||||
return types
|
||||
|
||||
RETURN_TYPES = ("DT_LORA",)
|
||||
RETURN_NAMES = ("LORA",)
|
||||
@@ -413,7 +422,9 @@ class DrawThingsLoRA:
|
||||
DESCRIPTION = "LoRAs are used to modify diffusion and CLIP models, altering the way in which latents are denoised such as applying styles. Multiple LoRA nodes can be linked together."
|
||||
FUNCTION = "add_to_pipeline"
|
||||
|
||||
def add_to_pipeline(self, lora_name, lora_weight, **kwargs) -> LoraStack:
|
||||
def add_to_pipeline(self, **kwargs) -> LoraStack:
|
||||
print(kwargs)
|
||||
return ([],)
|
||||
prev_lora: LoraStack = kwargs.get("lora", None)
|
||||
control_image = kwargs.get("control_image", None)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DtModelTypeHandler } from "./models.js"
|
||||
import { updateProto, setCallback } from "./util.js"
|
||||
import { findPropertyJson, findPropertyPython } from "./configProperties.js"
|
||||
import { DtButtonsTypeHandler } from './lora.js'
|
||||
|
||||
|
||||
export const nodePackVersion = "1.5.0"
|
||||
@@ -16,6 +17,7 @@ export default {
|
||||
getCustomWidgets(app) {
|
||||
return {
|
||||
DT_MODEL: DtModelTypeHandler,
|
||||
"DT_BUTTONS": DtButtonsTypeHandler
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -6,3 +6,19 @@
|
||||
-webkit-mask-image: url(/dt_grpc/logo.svg);
|
||||
mask-image: url(/dt_grpc/logo.svg);
|
||||
}
|
||||
|
||||
.dt-buttons-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: stretch;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.dt-button {
|
||||
/* font-weight: bold; */
|
||||
/* font-size: 0.5rem; */
|
||||
}
|
||||
|
||||
+80
-74
@@ -58,8 +58,8 @@ const dtModelNodeProto = {
|
||||
onConfigure(serialised) {
|
||||
this._lastSelectedModel = serialised._lastSelectedModel || {}
|
||||
},
|
||||
getModelWidget() {
|
||||
return this.widgets.find((w) => w.options?.modelType)
|
||||
getModelWidgets() {
|
||||
return this.widgets.filter((w) => w.options?.modelType)
|
||||
},
|
||||
onAdded() {
|
||||
modelService.updateNodes()
|
||||
@@ -96,29 +96,31 @@ const dtServerNodeProto = {
|
||||
* @param {string} version
|
||||
*/
|
||||
updateModels(models, version) {
|
||||
const widget = this.getModelWidget()
|
||||
if (!widget) return
|
||||
if (models === null) {
|
||||
widget.options.values = ["Not connected", "Click to retry"]
|
||||
widget.value = "Not connected"
|
||||
return
|
||||
}
|
||||
const widgets = this.getModelWidgets()
|
||||
for (const widget of widgets) {
|
||||
if (!widget) return
|
||||
if (models === null) {
|
||||
widget.options.values = ["Not connected", "Click to retry"]
|
||||
widget.value = "Not connected"
|
||||
return
|
||||
}
|
||||
|
||||
widget.options.values = ["(None selected)", ...models.models.map(m => getMenuItem(m)).sort((a, b) => a.content.localeCompare(b.content))]
|
||||
widget.options.values = ["(None selected)", ...models.models.map(m => getMenuItem(m)).sort((a, b) => a.content.localeCompare(b.content))]
|
||||
|
||||
if (widget.value === "Click to retry" || widget.value === "Not connected") {
|
||||
if (this._lastSelectedModel?.model) widget.value = this._lastSelectedModel.model
|
||||
else widget.value = "(None selected)"
|
||||
}
|
||||
if (widget.value === "Click to retry" || widget.value === "Not connected") {
|
||||
if (this._lastSelectedModel?.model) widget.value = this._lastSelectedModel.model
|
||||
else widget.value = "(None selected)"
|
||||
}
|
||||
|
||||
if (widget.value?.toString() === "[object Object]") {
|
||||
const value = {
|
||||
...widget.value,
|
||||
toString() {
|
||||
return this.value.name
|
||||
},
|
||||
if (widget.value?.toString() === "[object Object]") {
|
||||
const value = {
|
||||
...widget.value,
|
||||
toString() {
|
||||
return this.value.name
|
||||
},
|
||||
}
|
||||
widget.value = value
|
||||
}
|
||||
widget.value = value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,40 +138,42 @@ const dtModelStandardNodeProto = {
|
||||
*/
|
||||
updateModels(models, version) {
|
||||
/** @type {import('@comfyorg/litegraph').IWidget} */
|
||||
const widget = this.getModelWidget()
|
||||
if (!widget) return
|
||||
const widgets = this.getModelWidgets()
|
||||
for (const widget of widgets) {
|
||||
if (!widget) return
|
||||
|
||||
const type = widget?.options?.modelType
|
||||
const type = widget?.options?.modelType
|
||||
|
||||
if (models === null) {
|
||||
widget.options.values = ["Not connected", "Click to retry"]
|
||||
widget.value = "Not connected"
|
||||
return
|
||||
}
|
||||
if (models === null) {
|
||||
widget.options.values = ["Not connected", "Click to retry"]
|
||||
widget.value = "Not connected"
|
||||
return
|
||||
}
|
||||
|
||||
if (!(type in models)) return
|
||||
if (!(type in models)) return
|
||||
|
||||
widget.options.values = ["(None selected)", ...models[type]
|
||||
.map((m) => getMenuItem(m, version && m.version && m.version !== version))
|
||||
.sort((a, b) => {
|
||||
if (a.disabled && !b.disabled) return 1
|
||||
if (!a.disabled && b.disabled) return -1
|
||||
return a.content.toUpperCase().localeCompare(b.content.toUpperCase())
|
||||
})]
|
||||
widget.options.values = ["(None selected)", ...models[type]
|
||||
.map((m) => getMenuItem(m, version && m.version && m.version !== version))
|
||||
.sort((a, b) => {
|
||||
if (a.disabled && !b.disabled) return 1
|
||||
if (!a.disabled && b.disabled) return -1
|
||||
return a.content.toUpperCase().localeCompare(b.content.toUpperCase())
|
||||
})]
|
||||
|
||||
if (widget.value === "Click to retry" || widget.value === "Not connected") {
|
||||
if (this._lastSelectedModel?.[widget.name]) widget.value = this._lastSelectedModel[widget.name]
|
||||
else widget.value = "(None selected)"
|
||||
}
|
||||
if (widget.value === "Click to retry" || widget.value === "Not connected") {
|
||||
if (this._lastSelectedModel?.[widget.name]) widget.value = this._lastSelectedModel[widget.name]
|
||||
else widget.value = "(None selected)"
|
||||
}
|
||||
|
||||
if (widget.value?.toString() === "[object Object]") {
|
||||
const value = {
|
||||
...widget.value,
|
||||
toString() {
|
||||
return this.value.name
|
||||
},
|
||||
if (widget.value?.toString() === "[object Object]") {
|
||||
const value = {
|
||||
...widget.value,
|
||||
toString() {
|
||||
return this.value.name
|
||||
},
|
||||
}
|
||||
widget.value = value
|
||||
}
|
||||
widget.value = value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,35 +191,37 @@ const dtModelPromptNodeProto = {
|
||||
|
||||
updateOptions() {
|
||||
/** @type {import('@comfyorg/litegraph').IWidget} */
|
||||
const widget = this.getModelWidget()
|
||||
if (!widget) return
|
||||
const widgets = this.getModelWidgets()
|
||||
for (const widget of widgets) {
|
||||
if (!widget) return
|
||||
|
||||
if (this._models === null) {
|
||||
widget.options.values = ["Not connected", "Click to retry"]
|
||||
widget.value = "Not connected"
|
||||
return
|
||||
}
|
||||
if (this._models === null) {
|
||||
widget.options.values = ["Not connected", "Click to retry"]
|
||||
widget.value = "Not connected"
|
||||
return
|
||||
}
|
||||
|
||||
/** @type {string} */
|
||||
const promptText = this.widgets.find((w) => w.name === "prompt")?.value
|
||||
const matches = [...promptText.matchAll(/<(.*?)>/gm)]
|
||||
const tags = matches.map(m => m[1])
|
||||
widget.options.values = ["...", ...this._models
|
||||
.map((m) => getMenuItem(m, this._version && m.version && m.version !== this._version && !tags.includes(m.keyword)))
|
||||
.map(m => {
|
||||
Object.defineProperty(m, 'content', {
|
||||
get() {
|
||||
return `${tags.includes(m.value.keyword) ? "✓ " : ""}${m.value.name} (${m.value.version})`
|
||||
}
|
||||
/** @type {string} */
|
||||
const promptText = this.widgets.find((w) => w.name === "prompt")?.value
|
||||
const matches = [...promptText.matchAll(/<(.*?)>/gm)]
|
||||
const tags = matches.map(m => m[1])
|
||||
widget.options.values = ["...", ...this._models
|
||||
.map((m) => getMenuItem(m, this._version && m.version && m.version !== this._version && !tags.includes(m.keyword)))
|
||||
.map(m => {
|
||||
Object.defineProperty(m, 'content', {
|
||||
get() {
|
||||
return `${tags.includes(m.value.keyword) ? "✓ " : ""}${m.value.name} (${m.value.version})`
|
||||
}
|
||||
})
|
||||
return m
|
||||
})
|
||||
return m
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (a.disabled && !b.disabled) return 1
|
||||
if (!a.disabled && b.disabled) return -1
|
||||
return a.content.toUpperCase().localeCompare(b.content.toUpperCase())
|
||||
})]
|
||||
.sort((a, b) => {
|
||||
if (a.disabled && !b.disabled) return 1
|
||||
if (!a.disabled && b.disabled) return -1
|
||||
return a.content.toUpperCase().localeCompare(b.content.toUpperCase())
|
||||
})]
|
||||
|
||||
widget.value = "..."
|
||||
widget.value = "..."
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
+2
-1
@@ -3,13 +3,14 @@ import dtCore, { nodePackVersion } from "./ComfyUI-DrawThings-gRPC.js"
|
||||
import dtModelNodes from "./dtModelNodes.js"
|
||||
import dtDynamicInputs from "./dynamicInputs.js"
|
||||
import dtWidgets from "./widgets.js"
|
||||
import loraNode from "./lora.js"
|
||||
|
||||
import * as App from "../../scripts/app.js"
|
||||
|
||||
/** @type {import("@comfyorg/comfyui-frontend-types").ComfyApp} */
|
||||
const app = App.app
|
||||
|
||||
const modules = [dtCore, dtPrompt, dtModelNodes, dtDynamicInputs, dtWidgets]
|
||||
const modules = [dtCore, dtPrompt, dtModelNodes, /* dtDynamicInputs, */ dtWidgets, loraNode]
|
||||
|
||||
// different features of the nodepack extension are implemented in different modules
|
||||
// here we combine them and register a single extension
|
||||
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
import { DtModelTypeHandler } from './models.js'
|
||||
import { updateProto } from "./util.js"
|
||||
import { showWidget } from './widgets.js'
|
||||
|
||||
/**
|
||||
* so I don't really see an official way to remove widgets other than .splice
|
||||
* which seems weird, because then you have to call onRemove() yourself
|
||||
* So I'm going to go with "create 8 of them and hide the ones you don't need"
|
||||
* If someone needs more than 8 lora, well, they are crazy, and also they can
|
||||
* just chain another node
|
||||
*
|
||||
* we'll use model = null for loras above count
|
||||
* distinct from model = "(None selected)"
|
||||
*
|
||||
* .loraCount prop will 'add' and 'remove' (hide/show) widgets when changed
|
||||
* serialize and deserialize it with the node
|
||||
*
|
||||
* and we'll need a callback on each of the lora models so we can add control
|
||||
* image inputs if necessary
|
||||
*
|
||||
* the buttons widget is a custom widget type, so it's defined in the python
|
||||
* code, event listeners attached by the type handler
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param node {LGraphNode}
|
||||
* @param inputName {string}
|
||||
* @param inputData {["DT_MODEL", {model_type: string}]}
|
||||
*/
|
||||
export function DtButtonsTypeHandler(node, inputName, inputData, app) {
|
||||
const { container, buttons } = createButtons([{
|
||||
label: "Less",
|
||||
callback: () => {
|
||||
node.loraCount -= 1
|
||||
},
|
||||
}, {
|
||||
label: "More",
|
||||
callback: () => {
|
||||
node.loraCount += 1
|
||||
},
|
||||
}])
|
||||
const options = {
|
||||
hideOnZoom: false,
|
||||
getValue: () => undefined,
|
||||
setValue: (value) => { },
|
||||
getMinHeight: () => 36,
|
||||
getMaxHeight: () => 36,
|
||||
getHeight: () => 36,
|
||||
margin: 4
|
||||
}
|
||||
const widget = node.addDOMWidget("something", 'DT_BUTTONS', container, options)
|
||||
widget._buttonElements = buttons
|
||||
return { widget }
|
||||
}
|
||||
|
||||
/** @type {import("@comfyorg/litegraph").LGraphNode} */
|
||||
const loraProto = {
|
||||
onNodeCreated(graph) {
|
||||
this.loraCount = 1
|
||||
},
|
||||
|
||||
onConfigure(serialised) {
|
||||
if ("loraCount" in serialised)
|
||||
this.loraCount = serialised.loraCount
|
||||
console.log(serialised)
|
||||
},
|
||||
|
||||
onSerialize(serialised) {
|
||||
serialised.loraCount = this._loraCount
|
||||
},
|
||||
|
||||
loraCount: {
|
||||
get() {
|
||||
return this._loraCount
|
||||
},
|
||||
set(count) {
|
||||
if (this._loraCount === count) return
|
||||
this._loraCount = Math.max(0, Math.min(count, 8))
|
||||
this.updateWidgets()
|
||||
|
||||
const buttons = this.widgets[0]._buttonElements
|
||||
buttons[0].disabled = this._loraCount <= 1
|
||||
buttons[1].disabled = this._loraCount >= 8
|
||||
},
|
||||
enumerable: true
|
||||
},
|
||||
|
||||
updateWidgets() {
|
||||
console.log("updating lora widgets")
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const modelIndex = i * 2 + 1
|
||||
const weightIndex = i * 2 + 2
|
||||
if (i < this.loraCount) {
|
||||
console.log("show", this.widgets[modelIndex].name)
|
||||
showWidget(this, this.widgets[modelIndex].name, true)
|
||||
showWidget(this, this.widgets[weightIndex].name, true)
|
||||
if (!this.widgets[modelIndex].value) {
|
||||
this.widgets[modelIndex].value = "(None selected)"
|
||||
}
|
||||
} else {
|
||||
console.log("hide", this.widgets[modelIndex].name)
|
||||
showWidget(this, this.widgets[modelIndex].name, false)
|
||||
showWidget(this, this.widgets[weightIndex].name, false)
|
||||
|
||||
this.widgets[modelIndex].value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {import("@comfyorg/comfyui-frontend-types").ComfyExtension}*/
|
||||
export default {
|
||||
name: "loraNode",
|
||||
|
||||
beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass === "DrawThingsLoRA") {
|
||||
updateProto(nodeType, loraProto)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @typedef ButtonDef
|
||||
* @property {string?} label
|
||||
* @property {node => void} callback
|
||||
* @property {string?} style
|
||||
* @property {string[]?} classList
|
||||
*/
|
||||
|
||||
/** @param {ButtonDef[]} buttonsDefs */
|
||||
function createButtons(buttonsDefs) {
|
||||
const container = document.createElement("div")
|
||||
container.classList.add("dt-buttons-container")
|
||||
const buttons = []
|
||||
|
||||
for (const buttonDef of buttonsDefs) {
|
||||
const button = document.createElement("button")
|
||||
buttons.push(button)
|
||||
button.classList.add("dt-button")
|
||||
if (buttonDef.label)
|
||||
button.innerText = buttonDef.label
|
||||
button.addEventListener("click", () => {
|
||||
buttonDef.callback()
|
||||
})
|
||||
if (buttonDef.style) {
|
||||
button.style.cssText = buttonDef.style
|
||||
}
|
||||
if (buttonDef.classList) {
|
||||
button.classList.add(...buttonDef.classList)
|
||||
}
|
||||
container.appendChild(button)
|
||||
}
|
||||
|
||||
return { container, buttons }
|
||||
}
|
||||
@@ -76,6 +76,7 @@ export const modelService = new ModelService()
|
||||
* @param inputData {["DT_MODEL", {model_type: string}]}
|
||||
*/
|
||||
export function DtModelTypeHandler(node, inputName, inputData, app) {
|
||||
console.log("add dt model widget")
|
||||
const widget = node.addWidget(
|
||||
"combo",
|
||||
inputName,
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ function updateInput(input, node) {
|
||||
}
|
||||
|
||||
/** @param node {LGraphNode} */
|
||||
function showWidget(node, widgetName, show = false, suffix = "") {
|
||||
export function showWidget(node, widgetName, show = false, suffix = "") {
|
||||
const widget = findWidgetByName(node, widgetName);
|
||||
if (!widget) return;
|
||||
if (!origProps[widget.name]) {
|
||||
|
||||
Reference in New Issue
Block a user