Updated a1111 pnginfo importer, supports:
- model name, other basic settings - clip skip - hr upscale - loras - embeddings
This commit is contained in:
parent
6d6758e9e4
commit
85989c74e5
|
@ -73,6 +73,14 @@ class PromptServer():
|
||||||
async def get_root(request):
|
async def get_root(request):
|
||||||
return web.FileResponse(os.path.join(self.web_root, "index.html"))
|
return web.FileResponse(os.path.join(self.web_root, "index.html"))
|
||||||
|
|
||||||
|
@routes.get("/embeddings")
|
||||||
|
def get_embeddings(self):
|
||||||
|
models_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "models")
|
||||||
|
embed_dir = os.path.join(models_dir, "embeddings")
|
||||||
|
embeddings = nodes.filter_files_extensions(nodes.recursive_search(embed_dir), nodes.supported_pt_extensions)
|
||||||
|
|
||||||
|
return web.json_response(list(map(lambda a: os.path.splitext(a)[0].lower(), embeddings)))
|
||||||
|
|
||||||
@routes.get("/extensions")
|
@routes.get("/extensions")
|
||||||
async def get_extensions(request):
|
async def get_extensions(request):
|
||||||
files = glob.glob(os.path.join(self.web_root, 'extensions/**/*.js'), recursive=True)
|
files = glob.glob(os.path.join(self.web_root, 'extensions/**/*.js'), recursive=True)
|
||||||
|
|
|
@ -106,6 +106,15 @@ class ComfyApi extends EventTarget {
|
||||||
return await resp.json();
|
return await resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of embedding names
|
||||||
|
* @returns An array of script urls to import
|
||||||
|
*/
|
||||||
|
async getEmbeddings() {
|
||||||
|
const resp = await fetch("/embeddings", { cache: "no-store" });
|
||||||
|
return await resp.json();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads node object definitions for the graph
|
* Loads node object definitions for the graph
|
||||||
* @returns The node definitions
|
* @returns The node definitions
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { ComfyWidgets } from "./widgets.js";
|
||||||
import { ComfyUI } from "./ui.js";
|
import { ComfyUI } from "./ui.js";
|
||||||
import { api } from "./api.js";
|
import { api } from "./api.js";
|
||||||
import { defaultGraph } from "./defaultGraph.js";
|
import { defaultGraph } from "./defaultGraph.js";
|
||||||
import { getPngMetadata } from "./pnginfo.js";
|
import { getPngMetadata, importA1111 } from "./pnginfo.js";
|
||||||
|
|
||||||
class ComfyApp {
|
class ComfyApp {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -675,8 +675,12 @@ class ComfyApp {
|
||||||
async handleFile(file) {
|
async handleFile(file) {
|
||||||
if (file.type === "image/png") {
|
if (file.type === "image/png") {
|
||||||
const pngInfo = await getPngMetadata(file);
|
const pngInfo = await getPngMetadata(file);
|
||||||
if (pngInfo && pngInfo.workflow) {
|
if (pngInfo) {
|
||||||
this.loadGraphData(JSON.parse(pngInfo.workflow));
|
if (pngInfo.workflow) {
|
||||||
|
this.loadGraphData(JSON.parse(pngInfo.workflow));
|
||||||
|
} else if (pngInfo.parameters) {
|
||||||
|
importA1111(this.graph, pngInfo.parameters);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (file.type === "application/json" || file.name.endsWith(".json")) {
|
} else if (file.type === "application/json" || file.name.endsWith(".json")) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { api } from "./api.js";
|
||||||
|
|
||||||
export function getPngMetadata(file) {
|
export function getPngMetadata(file) {
|
||||||
return new Promise((r) => {
|
return new Promise((r) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
@ -43,3 +45,262 @@ export function getPngMetadata(file) {
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function importA1111(graph, parameters) {
|
||||||
|
const p = parameters.lastIndexOf("\nSteps:");
|
||||||
|
if (p > -1) {
|
||||||
|
const embeddings = await api.getEmbeddings();
|
||||||
|
const opts = parameters
|
||||||
|
.substr(p)
|
||||||
|
.split(",")
|
||||||
|
.reduce((p, n) => {
|
||||||
|
const s = n.split(":");
|
||||||
|
p[s[0].trim().toLowerCase()] = s[1].trim();
|
||||||
|
return p;
|
||||||
|
}, {});
|
||||||
|
const p2 = parameters.lastIndexOf("\nNegative prompt:", p);
|
||||||
|
if (p2 > -1) {
|
||||||
|
let positive = parameters.substr(0, p2).trim();
|
||||||
|
let negative = parameters.substring(p2 + 18, p).trim();
|
||||||
|
|
||||||
|
const ckptNode = LiteGraph.createNode("CheckpointLoaderSimple");
|
||||||
|
const clipSkipNode = LiteGraph.createNode("CLIPSetLastLayer");
|
||||||
|
const positiveNode = LiteGraph.createNode("CLIPTextEncode");
|
||||||
|
const negativeNode = LiteGraph.createNode("CLIPTextEncode");
|
||||||
|
const samplerNode = LiteGraph.createNode("KSampler");
|
||||||
|
const imageNode = LiteGraph.createNode("EmptyLatentImage");
|
||||||
|
const vaeNode = LiteGraph.createNode("VAEDecode");
|
||||||
|
const vaeLoaderNode = LiteGraph.createNode("VAELoader");
|
||||||
|
const saveNode = LiteGraph.createNode("SaveImage");
|
||||||
|
let hrSamplerNode = null;
|
||||||
|
|
||||||
|
const ceil64 = (v) => Math.ceil(v / 64) * 64;
|
||||||
|
|
||||||
|
function getWidget(node, name) {
|
||||||
|
return node.widgets.find((w) => w.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWidgetValue(node, name, value, isOptionPrefix) {
|
||||||
|
const w = getWidget(node, name);
|
||||||
|
if (isOptionPrefix) {
|
||||||
|
const o = w.options.values.find((w) => w.startsWith(value));
|
||||||
|
if (o) {
|
||||||
|
w.value = o;
|
||||||
|
} else {
|
||||||
|
console.warn(`Unknown value '${value}' for widget '${name}'`, node);
|
||||||
|
w.value = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLoraNodes(clipNode, text, prevClip, prevModel) {
|
||||||
|
const loras = [];
|
||||||
|
text = text.replace(/<lora:([^:]+:[^>]+)>/g, function (m, c) {
|
||||||
|
const s = c.split(":");
|
||||||
|
const weight = parseFloat(s[1]);
|
||||||
|
if (isNaN(weight)) {
|
||||||
|
console.warn("Invalid LORA", m);
|
||||||
|
} else {
|
||||||
|
loras.push({ name: s[0], weight });
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const l of loras) {
|
||||||
|
const loraNode = LiteGraph.createNode("LoraLoader");
|
||||||
|
graph.add(loraNode);
|
||||||
|
setWidgetValue(loraNode, "lora_name", l.name, true);
|
||||||
|
setWidgetValue(loraNode, "strength_model", l.weight);
|
||||||
|
setWidgetValue(loraNode, "strength_clip", l.weight);
|
||||||
|
prevModel.node.connect(prevModel.index, loraNode, 0);
|
||||||
|
prevClip.node.connect(prevClip.index, loraNode, 1);
|
||||||
|
prevModel = { node: loraNode, index: 0 };
|
||||||
|
prevClip = { node: loraNode, index: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
prevClip.node.connect(1, clipNode, 0);
|
||||||
|
prevModel.node.connect(0, samplerNode, 0);
|
||||||
|
if (hrSamplerNode) {
|
||||||
|
prevModel.node.connect(0, hrSamplerNode, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { text, prevModel, prevClip };
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceEmbeddings(text) {
|
||||||
|
return text.replaceAll(
|
||||||
|
new RegExp(
|
||||||
|
"\\b(" + embeddings.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\b|\\b") + ")\\b",
|
||||||
|
"ig"
|
||||||
|
),
|
||||||
|
"embedding:$1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function popOpt(name) {
|
||||||
|
const v = opts[name];
|
||||||
|
delete opts[name];
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
graph.clear();
|
||||||
|
graph.add(ckptNode);
|
||||||
|
graph.add(clipSkipNode);
|
||||||
|
graph.add(positiveNode);
|
||||||
|
graph.add(negativeNode);
|
||||||
|
graph.add(samplerNode);
|
||||||
|
graph.add(imageNode);
|
||||||
|
graph.add(vaeNode);
|
||||||
|
graph.add(vaeLoaderNode);
|
||||||
|
graph.add(saveNode);
|
||||||
|
|
||||||
|
ckptNode.connect(1, clipSkipNode, 0);
|
||||||
|
clipSkipNode.connect(0, positiveNode, 0);
|
||||||
|
clipSkipNode.connect(0, negativeNode, 0);
|
||||||
|
ckptNode.connect(0, samplerNode, 0);
|
||||||
|
positiveNode.connect(0, samplerNode, 1);
|
||||||
|
negativeNode.connect(0, samplerNode, 2);
|
||||||
|
imageNode.connect(0, samplerNode, 3);
|
||||||
|
vaeNode.connect(0, saveNode, 0);
|
||||||
|
samplerNode.connect(0, vaeNode, 0);
|
||||||
|
vaeLoaderNode.connect(0, vaeNode, 1);
|
||||||
|
|
||||||
|
const handlers = {
|
||||||
|
model(v) {
|
||||||
|
setWidgetValue(ckptNode, "ckpt_name", v, true);
|
||||||
|
},
|
||||||
|
"cfg scale"(v) {
|
||||||
|
setWidgetValue(samplerNode, "cfg", +v);
|
||||||
|
},
|
||||||
|
"clip skip"(v) {
|
||||||
|
setWidgetValue(clipSkipNode, "stop_at_clip_layer", -v);
|
||||||
|
},
|
||||||
|
sampler(v) {
|
||||||
|
let name = v.toLowerCase().replace("++", "pp").replaceAll(" ", "_");
|
||||||
|
if (name.includes("karras")) {
|
||||||
|
name = name.replace("karras", "").replace(/_+$/, "");
|
||||||
|
setWidgetValue(samplerNode, "scheduler", "karras");
|
||||||
|
} else {
|
||||||
|
setWidgetValue(samplerNode, "scheduler", "normal");
|
||||||
|
}
|
||||||
|
const w = getWidget(samplerNode, "sampler_name");
|
||||||
|
const o = w.options.values.find((w) => w === name || w === "sample_" + name);
|
||||||
|
if (o) {
|
||||||
|
setWidgetValue(samplerNode, "sampler_name", o);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
size(v) {
|
||||||
|
const wxh = v.split("x");
|
||||||
|
const w = ceil64(+wxh[0]);
|
||||||
|
const h = ceil64(+wxh[1]);
|
||||||
|
const hrUp = popOpt("hires upscale");
|
||||||
|
const hrSz = popOpt("hires resize");
|
||||||
|
let hrMethod = popOpt("hires upscaler");
|
||||||
|
|
||||||
|
setWidgetValue(imageNode, "width", w);
|
||||||
|
setWidgetValue(imageNode, "height", h);
|
||||||
|
|
||||||
|
if (hrUp || hrSz) {
|
||||||
|
let uw, uh;
|
||||||
|
if (hrUp) {
|
||||||
|
uw = w * hrUp;
|
||||||
|
uh = h * hrUp;
|
||||||
|
} else {
|
||||||
|
const s = hrSz.split("x");
|
||||||
|
uw = +s[0];
|
||||||
|
uh = +s[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
let upscaleNode;
|
||||||
|
let latentNode;
|
||||||
|
|
||||||
|
if (hrMethod.startsWith("Latent")) {
|
||||||
|
latentNode = upscaleNode = LiteGraph.createNode("LatentUpscale");
|
||||||
|
graph.add(upscaleNode);
|
||||||
|
samplerNode.connect(0, upscaleNode, 0);
|
||||||
|
|
||||||
|
switch (hrMethod) {
|
||||||
|
case "Latent (nearest-exact)":
|
||||||
|
hrMethod = "nearest-exact";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
setWidgetValue(upscaleNode, "upscale_method", hrMethod, true);
|
||||||
|
} else {
|
||||||
|
const decode = LiteGraph.createNode("VAEDecodeTiled");
|
||||||
|
graph.add(decode);
|
||||||
|
samplerNode.connect(0, decode, 0);
|
||||||
|
vaeLoaderNode.connect(0, decode, 1);
|
||||||
|
|
||||||
|
const upscaleLoaderNode = LiteGraph.createNode("UpscaleModelLoader");
|
||||||
|
graph.add(upscaleLoaderNode);
|
||||||
|
setWidgetValue(upscaleLoaderNode, "model_name", hrMethod, true);
|
||||||
|
|
||||||
|
const modelUpscaleNode = LiteGraph.createNode("ImageUpscaleWithModel");
|
||||||
|
graph.add(modelUpscaleNode);
|
||||||
|
decode.connect(0, modelUpscaleNode, 1);
|
||||||
|
upscaleLoaderNode.connect(0, modelUpscaleNode, 0);
|
||||||
|
|
||||||
|
upscaleNode = LiteGraph.createNode("ImageScale");
|
||||||
|
graph.add(upscaleNode);
|
||||||
|
modelUpscaleNode.connect(0, upscaleNode, 0);
|
||||||
|
|
||||||
|
const vaeEncodeNode = (latentNode = LiteGraph.createNode("VAEEncodeTiled"));
|
||||||
|
graph.add(vaeEncodeNode);
|
||||||
|
upscaleNode.connect(0, vaeEncodeNode, 0);
|
||||||
|
vaeLoaderNode.connect(0, vaeEncodeNode, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
setWidgetValue(upscaleNode, "width", ceil64(uw));
|
||||||
|
setWidgetValue(upscaleNode, "height", ceil64(uh));
|
||||||
|
|
||||||
|
hrSamplerNode = LiteGraph.createNode("KSampler");
|
||||||
|
graph.add(hrSamplerNode);
|
||||||
|
ckptNode.connect(0, hrSamplerNode, 0);
|
||||||
|
positiveNode.connect(0, hrSamplerNode, 1);
|
||||||
|
negativeNode.connect(0, hrSamplerNode, 2);
|
||||||
|
latentNode.connect(0, hrSamplerNode, 3);
|
||||||
|
hrSamplerNode.connect(0, vaeNode, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
steps(v) {
|
||||||
|
setWidgetValue(samplerNode, "steps", +v);
|
||||||
|
},
|
||||||
|
seed(v) {
|
||||||
|
setWidgetValue(samplerNode, "seed", +v);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const opt in opts) {
|
||||||
|
if (opt in handlers) {
|
||||||
|
handlers[opt](popOpt(opt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hrSamplerNode) {
|
||||||
|
setWidgetValue(hrSamplerNode, "steps", getWidget(samplerNode, "steps").value);
|
||||||
|
setWidgetValue(hrSamplerNode, "cfg", getWidget(samplerNode, "cfg").value);
|
||||||
|
setWidgetValue(hrSamplerNode, "scheduler", getWidget(samplerNode, "scheduler").value);
|
||||||
|
setWidgetValue(hrSamplerNode, "sampler_name", getWidget(samplerNode, "sampler_name").value);
|
||||||
|
setWidgetValue(hrSamplerNode, "denoise", +(popOpt("denoising strength") || "1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = createLoraNodes(positiveNode, positive, { node: clipSkipNode, index: 0 }, { node: ckptNode, index: 0 });
|
||||||
|
positive = n.text;
|
||||||
|
n = createLoraNodes(negativeNode, negative, n.prevClip, n.prevModel);
|
||||||
|
negative = n.text;
|
||||||
|
|
||||||
|
setWidgetValue(positiveNode, "text", replaceEmbeddings(positive));
|
||||||
|
setWidgetValue(negativeNode, "text", replaceEmbeddings(negative));
|
||||||
|
|
||||||
|
graph.arrange();
|
||||||
|
|
||||||
|
for (const opt of ["model hash", "ensd"]) {
|
||||||
|
delete opts[opt];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("Unhandled parameters:", opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue