Import ComfyReroute
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { get } from "svelte/store";
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
||||
import { Button } from "@gradio/button";
|
||||
import { Backpack, Gear } from 'radix-icons-svelte';
|
||||
@@ -7,7 +8,7 @@
|
||||
import ComfyApp from "./ComfyApp";
|
||||
import widgetState from "$lib/stores/widgetState";
|
||||
|
||||
import { LGraphNode } from "litegraph.js";
|
||||
import { LGraphNode } from "@litegraph-ts/core";
|
||||
|
||||
let app: ComfyApp = undefined;
|
||||
let uiPane: ComfyUIPane = undefined;
|
||||
@@ -17,7 +18,7 @@
|
||||
}
|
||||
|
||||
function queuePrompt() {
|
||||
const state = uiPane.getState();
|
||||
const state = get(widgetState);
|
||||
console.log("Queuing!", state);
|
||||
app.queuePrompt(0, 1, state);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode } from "litegraph.js";
|
||||
import type { LGraphNodeBase, LConnectionKind, INodeSlot } from "litegraph.js";
|
||||
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink } from "@litegraph-ts/core";
|
||||
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
|
||||
import ComfyAPI from "$lib/api"
|
||||
import { ComfyWidgets } from "$lib/widgets"
|
||||
import defaultGraph from "$lib/defaultGraph"
|
||||
@@ -7,14 +7,31 @@ import { getPngMetadata, importA1111 } from "$lib/pnginfo";
|
||||
import EventEmitter from "events";
|
||||
import type TypedEmitter from "typed-emitter";
|
||||
|
||||
// Import nodes
|
||||
import * as basic from "@litegraph-ts/nodes-basic"
|
||||
import * as nodes from "$lib/nodes/index"
|
||||
|
||||
LiteGraph.catch_exceptions = false;
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
// Load default visibility
|
||||
nodes.ComfyReroute.setDefaultTextVisibility(!!localStorage["Comfy.ComfyReroute.DefaultVisibility"]);
|
||||
}
|
||||
|
||||
type QueueItem = { num: number, batchCount: number }
|
||||
|
||||
type ComfyAppEvents = {
|
||||
configured: (graph: LGraph) => void
|
||||
nodeAdded: (node: LGraphNode) => void
|
||||
nodeRemoved: (node: LGraphNode) => void
|
||||
nodeConnectionChanged: (kind: LConnectionKind, node: LGraphNode, slot: INodeSlot, targetNode: LGraphNode, targetSlot: INodeSlot) => void
|
||||
cleared: () => void
|
||||
configured: (graph: LGraph) => void
|
||||
nodeAdded: (node: LGraphNode) => void
|
||||
nodeRemoved: (node: LGraphNode) => void
|
||||
nodeConnectionChanged: (kind: LConnectionKind, node: LGraphNode, slot: INodeSlot, targetNode: LGraphNode, targetSlot: INodeSlot) => void
|
||||
cleared: () => void
|
||||
}
|
||||
|
||||
interface ComfyGraphNodeExecutable extends LGraphNodeExecutable {
|
||||
comfyClass: string
|
||||
isVirtualNode?: boolean;
|
||||
applyToGraph(workflow: SerializedLGraph<SerializedLGraphNode<LGraphNode>, SerializedLLink, SerializedLGraphGroup>): void;
|
||||
}
|
||||
|
||||
export default class ComfyApp {
|
||||
@@ -32,7 +49,6 @@ export default class ComfyApp {
|
||||
|
||||
constructor() {
|
||||
this.api = new ComfyAPI();
|
||||
this.eventBus.
|
||||
}
|
||||
|
||||
async setup(): Promise<void> {
|
||||
@@ -44,49 +60,49 @@ export default class ComfyApp {
|
||||
this.lCanvas = new LGraphCanvas(this.canvasEl, this.lGraph);
|
||||
this.canvasCtx = this.canvasEl.getContext("2d");
|
||||
|
||||
this.addGraphLifecycleHooks();
|
||||
this.addGraphLifecycleHooks();
|
||||
|
||||
LiteGraph.release_link_on_empty_shows_menu = true;
|
||||
LiteGraph.alt_drag_do_clone_nodes = true;
|
||||
LiteGraph.release_link_on_empty_shows_menu = true;
|
||||
LiteGraph.alt_drag_do_clone_nodes = true;
|
||||
|
||||
this.lGraph.start();
|
||||
this.lGraph.start();
|
||||
|
||||
// await this.#invokeExtensionsAsync("init");
|
||||
await this.registerNodes();
|
||||
// await this.#invokeExtensionsAsync("init");
|
||||
await this.registerNodes();
|
||||
|
||||
// Load previous workflow
|
||||
let restored = false;
|
||||
try {
|
||||
const json = localStorage.getItem("workflow");
|
||||
if (json) {
|
||||
const workflow = JSON.parse(json);
|
||||
this.loadGraphData(workflow);
|
||||
restored = true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error loading previous workflow", err);
|
||||
}
|
||||
// Load previous workflow
|
||||
let restored = false;
|
||||
try {
|
||||
const json = localStorage.getItem("workflow");
|
||||
if (json) {
|
||||
const workflow = JSON.parse(json);
|
||||
this.loadGraphData(workflow);
|
||||
restored = true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error loading previous workflow", err);
|
||||
}
|
||||
|
||||
// We failed to restore a workflow so load the default
|
||||
if (!restored) {
|
||||
this.loadGraphData();
|
||||
}
|
||||
// We failed to restore a workflow so load the default
|
||||
if (!restored) {
|
||||
this.loadGraphData();
|
||||
}
|
||||
|
||||
// Save current workflow automatically
|
||||
setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.lGraph.serialize())), 1000);
|
||||
// Save current workflow automatically
|
||||
setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.lGraph.serialize())), 1000);
|
||||
|
||||
// this.#addDrawNodeHandler();
|
||||
// this.#addDrawGroupsHandler();
|
||||
// this.#addApiUpdateHandlers();
|
||||
this.addDropHandler();
|
||||
// this.#addPasteHandler();
|
||||
// this.#addKeyboardHandler();
|
||||
// this.#addDrawNodeHandler();
|
||||
// this.#addDrawGroupsHandler();
|
||||
// this.#addApiUpdateHandlers();
|
||||
this.addDropHandler();
|
||||
// this.#addPasteHandler();
|
||||
// this.#addKeyboardHandler();
|
||||
|
||||
// await this.#invokeExtensionsAsync("setup");
|
||||
// await this.#invokeExtensionsAsync("setup");
|
||||
|
||||
// Ensure the canvas fills the window
|
||||
// Ensure the canvas fills the window
|
||||
this.resizeCanvas();
|
||||
window.addEventListener("resize", this.resizeCanvas.bind(this));
|
||||
window.addEventListener("resize", this.resizeCanvas.bind(this));
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -165,83 +181,89 @@ export default class ComfyApp {
|
||||
private async registerNodes() {
|
||||
const app = this;
|
||||
|
||||
// Load node definitions from the backend
|
||||
// Load node definitions from the backend
|
||||
const defs = await this.api.getNodeDefs();
|
||||
// await this.#invokeExtensionsAsync("addCustomNodeDefs", defs);
|
||||
// await this.#invokeExtensionsAsync("addCustomNodeDefs", defs);
|
||||
|
||||
// Generate list of known widgets
|
||||
const widgets = ComfyWidgets;
|
||||
// Generate list of known widgets
|
||||
const widgets = ComfyWidgets;
|
||||
// const widgets = Object.assign(
|
||||
// {},
|
||||
// ComfyWidgets,
|
||||
// ...(await this.#invokeExtensionsAsync("getCustomWidgets")).filter(Boolean)
|
||||
// );
|
||||
// {},
|
||||
// ComfyWidgets,
|
||||
// ...(await this.#invokeExtensionsAsync("getCustomWidgets")).filter(Boolean)
|
||||
// );
|
||||
|
||||
// Register a node for each definition
|
||||
for (const nodeId in defs) {
|
||||
const nodeData = defs[nodeId];
|
||||
const node: LGraphNodeBase = Object.assign(
|
||||
function ComfyNode(this: LGraphNode) {
|
||||
var inputs = nodeData["input"]["required"];
|
||||
if (nodeData["input"]["optional"] != undefined){
|
||||
inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"])
|
||||
}
|
||||
const config = { minWidth: 1, minHeight: 1 };
|
||||
for (const inputName in inputs) {
|
||||
const inputData = inputs[inputName];
|
||||
const type = inputData[0];
|
||||
// Register a node for each definition
|
||||
for (const nodeId in defs) {
|
||||
const nodeData = defs[nodeId];
|
||||
|
||||
if(inputData[1]?.forceInput) {
|
||||
this.addInput(inputName, type);
|
||||
} else {
|
||||
if (Array.isArray(type)) {
|
||||
// Enums
|
||||
Object.assign(config, widgets.COMBO(this, inputName, inputData, app) || {});
|
||||
} else if (`${type}:${inputName}` in widgets) {
|
||||
// Support custom widgets by Type:Name
|
||||
Object.assign(config, widgets[`${type}:${inputName}`](this, inputName, inputData, app) || {});
|
||||
} else if (type in widgets) {
|
||||
// Standard type widgets
|
||||
Object.assign(config, widgets[type](this, inputName, inputData, app) || {});
|
||||
} else {
|
||||
// Node connection inputs
|
||||
this.addInput(inputName, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
const ctor = class extends LGraphNode {
|
||||
constructor(title?: string) {
|
||||
super(title);
|
||||
this.type = nodeId; // XXX: workaround dependency in LGraphNode.addInput()
|
||||
(this as any).comfyClass = nodeId;
|
||||
var inputs = nodeData["input"]["required"];
|
||||
if (nodeData["input"]["optional"] != undefined) {
|
||||
inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"])
|
||||
}
|
||||
const config = { minWidth: 1, minHeight: 1 };
|
||||
for (const inputName in inputs) {
|
||||
const inputData = inputs[inputName];
|
||||
const type = inputData[0];
|
||||
|
||||
for (const o in nodeData["output"]) {
|
||||
const output = nodeData["output"][o];
|
||||
const outputName = nodeData["output_name"][o] || output;
|
||||
this.addOutput(outputName, output);
|
||||
}
|
||||
if (inputData[1]?.forceInput) {
|
||||
this.addInput(inputName, type);
|
||||
} else {
|
||||
if (Array.isArray(type)) {
|
||||
// Enums
|
||||
Object.assign(config, widgets.COMBO(this, inputName, inputData, app) || {});
|
||||
} else if (`${type}:${inputName}` in widgets) {
|
||||
// Support custom widgets by Type:Name
|
||||
Object.assign(config, widgets[`${type}:${inputName}`](this, inputName, inputData, app) || {});
|
||||
} else if (type in widgets) {
|
||||
// Standard type widgets
|
||||
Object.assign(config, widgets[type](this, inputName, inputData, app) || {});
|
||||
} else {
|
||||
// Node connection inputs
|
||||
this.addInput(inputName, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const s = this.computeSize();
|
||||
s[0] = Math.max(config.minWidth, s[0] * 1.5);
|
||||
s[1] = Math.max(config.minHeight, s[1]);
|
||||
this.size = s;
|
||||
this.serialize_widgets = true;
|
||||
for (const o in nodeData["output"]) {
|
||||
const output = nodeData["output"][o];
|
||||
const outputName = nodeData["output_name"][o] || output;
|
||||
this.addOutput(outputName, output);
|
||||
}
|
||||
|
||||
// app.#invokeExtensionsAsync("nodeCreated", this);
|
||||
const s = this.computeSize();
|
||||
s[0] = Math.max(config.minWidth, s[0] * 1.5);
|
||||
s[1] = Math.max(config.minHeight, s[1]);
|
||||
this.size = s;
|
||||
this.serialize_widgets = true;
|
||||
|
||||
// app.#invokeExtensionsAsync("nodeCreated", this);
|
||||
|
||||
return this;
|
||||
},
|
||||
{
|
||||
title: nodeData.name,
|
||||
comfyClass: nodeData.name,
|
||||
}
|
||||
);
|
||||
node.prototype.comfyClass = nodeData.name;
|
||||
}
|
||||
}
|
||||
|
||||
// this.#addNodeContextMenuHandler(node);
|
||||
// this.#addDrawBackgroundHandler(node, app);
|
||||
const node: LGraphNodeConstructor = {
|
||||
class: ctor,
|
||||
title: nodeData.name,
|
||||
type: nodeId,
|
||||
desc: `ComfyNode: ${nodeId}`
|
||||
}
|
||||
|
||||
// await this.#invokeExtensionsAsync("beforeRegisterNodeDef", node, nodeData);
|
||||
LiteGraph.registerNodeType(nodeId, node);
|
||||
node.category = nodeData.category;
|
||||
}
|
||||
// this.#addNodeContextMenuHandler(node);
|
||||
// this.#addDrawBackgroundHandler(node, app);
|
||||
|
||||
// await this.#invokeExtensionsAsync("registerCustomNodes");
|
||||
// await this.#invokeExtensionsAsync("beforeRegisterNodeDef", node, nodeData);
|
||||
LiteGraph.registerNodeType(node);
|
||||
node.category = nodeData.category;
|
||||
}
|
||||
|
||||
// await this.#invokeExtensionsAsync("registerCustomNodes");
|
||||
}
|
||||
|
||||
private showDropZone() {
|
||||
@@ -330,12 +352,13 @@ export default class ComfyApp {
|
||||
|
||||
const output = {};
|
||||
// Process nodes in order of execution
|
||||
for (const node of this.lGraph.computeExecutionOrder(false, null)) {
|
||||
for (const node of this.lGraph.computeExecutionOrder<ComfyGraphNodeExecutable>(false, null)) {
|
||||
const fromFrontend = frontendState[node.id];
|
||||
|
||||
const n = workflow.nodes.find((n) => n.id === node.id);
|
||||
|
||||
if (node.isVirtualNode) {
|
||||
if (node.isVirtualNode || !node.comfyClass) {
|
||||
console.debug("Not serializing node: ", node.type)
|
||||
// Don't serialize frontend only nodes but let them make changes
|
||||
if (node.applyToGraph) {
|
||||
node.applyToGraph(workflow);
|
||||
@@ -353,14 +376,12 @@ export default class ComfyApp {
|
||||
|
||||
// Store all widget values
|
||||
if (widgets) {
|
||||
for (const i in widgets) {
|
||||
for (let i = 0; i < widgets.length; i++) {
|
||||
const widget = widgets[i];
|
||||
if (!widget.options || widget.options.serialize !== false) {
|
||||
// TODO serializeValue API
|
||||
let value = widget.serializeValue ? await widget.serializeValue(n, i) : widget.value;
|
||||
if (fromFrontend) {
|
||||
console.log("Set values!", value, fromFrontend[i])
|
||||
value = fromFrontend[i];
|
||||
value = fromFrontend[i].value;
|
||||
}
|
||||
inputs[widget.name] = value
|
||||
}
|
||||
@@ -368,21 +389,21 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
// Store all node links
|
||||
for (let i in node.inputs) {
|
||||
let parent = node.getInputNode(i);
|
||||
for (let i = 0; i < node.inputs.length; i++) {
|
||||
let parent: ComfyGraphNodeExecutable = node.getInputNode(i) as ComfyGraphNodeExecutable;
|
||||
if (parent) {
|
||||
let link = node.getInputLink(i);
|
||||
while (parent && parent.isVirtualNode) {
|
||||
link = parent.getInputLink(link.origin_slot);
|
||||
if (link) {
|
||||
parent = parent.getInputNode(link.origin_slot);
|
||||
parent = parent.getInputNode(link.origin_slot) as ComfyGraphNodeExecutable;
|
||||
} else {
|
||||
parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (link) {
|
||||
inputs[node.inputs[i].name] = [String(link.origin_id), parseInt(link.origin_slot)];
|
||||
inputs[node.inputs[i].name] = [String(link.origin_id), link.origin_slot];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -466,7 +487,7 @@ export default class ComfyApp {
|
||||
if (pngInfo.workflow) {
|
||||
this.loadGraphData(JSON.parse(pngInfo.workflow));
|
||||
} else if (pngInfo.parameters) {
|
||||
importA1111(this.lGraph, pngInfo.parameters);
|
||||
importA1111(this.lGraph, pngInfo.parameters, this.api);
|
||||
}
|
||||
}
|
||||
} else if (file.type === "application/json" || file.name.endsWith(".json")) {
|
||||
@@ -494,18 +515,18 @@ export default class ComfyApp {
|
||||
async refreshComboInNodes() {
|
||||
const defs = await this.api.getNodeDefs();
|
||||
|
||||
for(let nodeNum in this.lGraph._nodes) {
|
||||
for (let nodeNum in this.lGraph._nodes) {
|
||||
const node = this.lGraph._nodes[nodeNum];
|
||||
|
||||
const def = defs[node.type];
|
||||
|
||||
for(const widgetNum in node.widgets) {
|
||||
for (const widgetNum in node.widgets) {
|
||||
const widget = node.widgets[widgetNum]
|
||||
|
||||
if(widget.type == "combo" && def["input"]["required"][widget.name] !== undefined) {
|
||||
if (widget.type == "combo" && def["input"]["required"][widget.name] !== undefined) {
|
||||
widget.options.values = def["input"]["required"][widget.name][0];
|
||||
|
||||
if(!widget.options.values.includes(widget.value)) {
|
||||
if (!widget.options.values.includes(widget.value)) {
|
||||
widget.value = widget.options.values[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { LGraphNode, LGraph } from "litegraph.js";
|
||||
import type { IWidget } from "litegraph.js";
|
||||
import { LGraphNode, LGraph } from "@litegraph-ts/core";
|
||||
import type { IWidget } from "@litegraph-ts/core";
|
||||
import ComfyApp from "./ComfyApp";
|
||||
import ComfyPane from "./ComfyPane.svelte";
|
||||
import widgetState from "$lib/stores/widgetState";
|
||||
|
||||
5
src/lib/nodes/ComfyGraphNode.ts
Normal file
5
src/lib/nodes/ComfyGraphNode.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { LGraphNode } from "@litegraph-ts/core";
|
||||
|
||||
export default class ComfyGraphNode extends LGraphNode {
|
||||
isVirtualNode: boolean = false;
|
||||
}
|
||||
247
src/lib/nodes/ComfyReroute.ts
Normal file
247
src/lib/nodes/ComfyReroute.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
|
||||
export interface ComfyRerouteProperties extends Record<any, any> {
|
||||
showOutputText: boolean;
|
||||
horizontal: boolean;
|
||||
}
|
||||
|
||||
export default class ComfyReroute extends ComfyGraphNode {
|
||||
static defaultVisibility: boolean = true;
|
||||
|
||||
static setDefaultTextVisibility(visible: boolean) {
|
||||
ComfyReroute.defaultVisibility = visible;
|
||||
if (visible) {
|
||||
localStorage["Comfy.ComfyReroute.DefaultVisibility"] = "true";
|
||||
} else {
|
||||
delete localStorage["Comfy.ComfyReroute.DefaultVisibility"];
|
||||
}
|
||||
}
|
||||
|
||||
// This node is purely frontend and does not impact the resulting prompt so should not be serialized
|
||||
override isVirtualNode: boolean = true;
|
||||
|
||||
override titleMode: TitleMode = TitleMode.NO_TITLE;
|
||||
override collapsable: boolean = false;
|
||||
|
||||
override properties: ComfyRerouteProperties = {
|
||||
showOutputText: ComfyReroute.defaultVisibility,
|
||||
horizontal: false
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
inputs: [
|
||||
{ name: "", type: "*" }
|
||||
],
|
||||
outputs: [
|
||||
{ name: "", type: "*" }
|
||||
],
|
||||
}
|
||||
|
||||
constructor(title?: string) {
|
||||
super(title);
|
||||
|
||||
this.properties ||= {} as any;
|
||||
this.properties.showOutputText = ComfyReroute.defaultVisibility;
|
||||
this.properties.horizontal = false;
|
||||
|
||||
if (this.properties.showOutputText) {
|
||||
this.outputs[0].name = "*"
|
||||
}
|
||||
}
|
||||
|
||||
override onConnectionsChange(type: LConnectionKind, slotIndex: number, isConnected: boolean, _link: LLink) {
|
||||
this.applyOrientation();
|
||||
|
||||
// Prevent multiple connections to different types when we have no input
|
||||
if (isConnected && type === LConnectionKind.OUTPUT) {
|
||||
// Ignore wildcard nodes as these will be updated to real types
|
||||
const types = new Set(this.outputs[0].links.map((l) => this.graph.links[l].type).filter((t) => t !== "*"));
|
||||
if (types.size > 1) {
|
||||
for (let i = 0; i < this.outputs[0].links.length - 1; i++) {
|
||||
const linkId = this.outputs[0].links[i];
|
||||
const link = this.graph.links[linkId];
|
||||
const node = this.graph.getNodeById(link.target_id);
|
||||
node.disconnectInput(link.target_slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find root input
|
||||
let currentNode: ComfyReroute = this;
|
||||
let updateNodes: ComfyReroute[] = [];
|
||||
let inputType: SlotType | null = null;
|
||||
let inputNode = null;
|
||||
while (currentNode) {
|
||||
updateNodes.unshift(currentNode);
|
||||
const linkId = currentNode.inputs[0].link;
|
||||
if (linkId !== null) {
|
||||
const link = this.graph.links[linkId];
|
||||
const node = this.graph.getNodeById(link.origin_id);
|
||||
console.warn(node.type)
|
||||
if (node.class === ComfyReroute) {
|
||||
console.log("REROUTE2")
|
||||
if (node === this) {
|
||||
// We've found a circle
|
||||
currentNode.disconnectInput(link.target_slot);
|
||||
currentNode = null;
|
||||
}
|
||||
else {
|
||||
// Move the previous node
|
||||
currentNode = node as ComfyReroute;
|
||||
}
|
||||
} else {
|
||||
// We've found the end
|
||||
inputNode = currentNode;
|
||||
inputType = node.outputs[link.origin_slot]?.type ?? null;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// This path has no input node
|
||||
currentNode = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all outputs
|
||||
const nodes: ComfyReroute[] = [this];
|
||||
let outputType: SlotType | null = null;
|
||||
while (nodes.length) {
|
||||
currentNode = nodes.pop();
|
||||
const outputs = (currentNode.outputs ? currentNode.outputs[0].links : []) || [];
|
||||
if (outputs.length) {
|
||||
for (const linkId of outputs) {
|
||||
const link = this.graph.links[linkId];
|
||||
|
||||
// When disconnecting sometimes the link is still registered
|
||||
if (!link) continue;
|
||||
|
||||
const node = this.graph.getNodeById(link.target_id);
|
||||
|
||||
if (node.class === ComfyReroute) {
|
||||
console.log("REROUTE")
|
||||
// Follow reroute nodes
|
||||
nodes.push(node as ComfyReroute);
|
||||
updateNodes.push(node as ComfyReroute);
|
||||
} else {
|
||||
// We've found an output
|
||||
const nodeOutType = node.inputs && node.inputs[link?.target_slot] && node.inputs[link.target_slot].type ? node.inputs[link.target_slot].type : null;
|
||||
if (inputType && nodeOutType !== inputType) {
|
||||
// The output doesnt match our input so disconnect it
|
||||
node.disconnectInput(link.target_slot);
|
||||
} else {
|
||||
outputType = nodeOutType;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No more outputs for this path
|
||||
}
|
||||
}
|
||||
|
||||
const displayType = inputType || outputType || "*";
|
||||
const color = LGraphCanvas.link_type_colors[displayType];
|
||||
|
||||
// Update the types of each node
|
||||
for (const node of updateNodes) {
|
||||
// If we dont have an input type we are always wildcard but we'll show the output type
|
||||
// This lets you change the output link to a different type and all nodes will update
|
||||
node.outputs[0].type = inputType || "*";
|
||||
(node as any).__outputType = displayType;
|
||||
node.outputs[0].name = node.properties.showOutputText ? String(displayType) : "";
|
||||
node.size = node.computeSize();
|
||||
|
||||
if ("applyOrientation" in node && typeof node.applyOrientation === "function")
|
||||
node.applyOrientation();
|
||||
|
||||
for (const l of node.outputs[0].links || []) {
|
||||
const link = this.graph.links[l];
|
||||
if (link) {
|
||||
link.color = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputNode) {
|
||||
const link = this.graph.links[inputNode.inputs[0].link];
|
||||
if (link) {
|
||||
link.color = color;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override clone(): LGraphNode {
|
||||
const cloned = super.clone.apply(this) as ComfyReroute;
|
||||
cloned.removeOutput(0);
|
||||
cloned.addOutput(this.properties.showOutputText ? "*" : "", "*");
|
||||
cloned.size = cloned.computeSize();
|
||||
return cloned;
|
||||
};
|
||||
|
||||
override getExtraMenuOptions(_, options: ContextMenuItem[]): ContextMenuItem[] | null {
|
||||
options.unshift(
|
||||
{
|
||||
content: (this.properties.showOutputText ? "Hide" : "Show") + " Type",
|
||||
callback: () => {
|
||||
this.properties.showOutputText = !this.properties.showOutputText;
|
||||
if (this.properties.showOutputText) {
|
||||
this.outputs[0].name = (this as any).__outputType || this.outputs[0].type;
|
||||
} else {
|
||||
this.outputs[0].name = "";
|
||||
}
|
||||
this.size = this.computeSize();
|
||||
this.applyOrientation();
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
},
|
||||
},
|
||||
{
|
||||
content: (ComfyReroute.defaultVisibility ? "Hide" : "Show") + " Type By Default",
|
||||
callback: () => {
|
||||
ComfyReroute.setDefaultTextVisibility(!ComfyReroute.defaultVisibility);
|
||||
},
|
||||
},
|
||||
{
|
||||
// naming is inverted with respect to LiteGraphNode.horizontal
|
||||
// LiteGraphNode.horizontal == true means that
|
||||
// each slot in the inputs and outputs are layed out horizontally,
|
||||
// which is the opposite of the visual orientation of the inputs and outputs as a node
|
||||
content: "Set " + (this.properties.horizontal ? "Horizontal" : "Vertical"),
|
||||
callback: () => {
|
||||
this.properties.horizontal = !this.properties.horizontal;
|
||||
this.applyOrientation();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
applyOrientation() {
|
||||
this.horizontal = this.properties.horizontal;
|
||||
if (this.horizontal) {
|
||||
// we correct the input position, because LiteGraphNode.horizontal
|
||||
// doesn't account for title presence
|
||||
// which reroute nodes don't have
|
||||
this.inputs[0].pos = [this.size[0] / 2, 0];
|
||||
} else {
|
||||
delete this.inputs[0].pos;
|
||||
}
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
|
||||
override computeSize(): Vector2 {
|
||||
return [
|
||||
this.properties.showOutputText && this.outputs && this.outputs.length
|
||||
? Math.max(75, LiteGraph.NODE_TEXT_SIZE * this.outputs[0].name.length * 0.6 + 40)
|
||||
: 75,
|
||||
26,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
class: ComfyReroute,
|
||||
title: "Comfy.Reroute",
|
||||
desc: "Reroutes nodes preserving input/output types",
|
||||
type: "utils/reroute"
|
||||
})
|
||||
1
src/lib/nodes/index.ts
Normal file
1
src/lib/nodes/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as ComfyReroute } from "./ComfyReroute"
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LiteGraph, LGraph, LGraphNode } from "litegraph.js"
|
||||
import { LiteGraph, LGraph, LGraphNode } from "@litegraph-ts/core"
|
||||
import type ComfyAPI from "$lib/api"
|
||||
|
||||
class PNGMetadataPromise extends Promise<Record<string, string>> {
|
||||
@@ -17,308 +17,308 @@ class PNGMetadataPromise extends Promise<Record<string, string>> {
|
||||
}
|
||||
|
||||
export function getPngMetadata(file: File): PNGMetadataPromise {
|
||||
return new PNGMetadataPromise((r, _) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event: Event) => {
|
||||
// Get the PNG data as a Uint8Array
|
||||
const pngData = new Uint8Array((event.target as any).result);
|
||||
const dataView = new DataView(pngData.buffer);
|
||||
return new PNGMetadataPromise((r, _) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event: Event) => {
|
||||
// Get the PNG data as a Uint8Array
|
||||
const pngData = new Uint8Array((event.target as any).result);
|
||||
const dataView = new DataView(pngData.buffer);
|
||||
|
||||
// Check that the PNG signature is present
|
||||
if (dataView.getUint32(0) !== 0x89504e47) {
|
||||
console.error("Not a valid PNG file");
|
||||
r();
|
||||
return;
|
||||
}
|
||||
// Check that the PNG signature is present
|
||||
if (dataView.getUint32(0) !== 0x89504e47) {
|
||||
console.error("Not a valid PNG file");
|
||||
r();
|
||||
return;
|
||||
}
|
||||
|
||||
// Start searching for chunks after the PNG signature
|
||||
let offset = 8;
|
||||
let txt_chunks = {};
|
||||
// Loop through the chunks in the PNG file
|
||||
while (offset < pngData.length) {
|
||||
// Get the length of the chunk
|
||||
const length = dataView.getUint32(offset);
|
||||
// Get the chunk type
|
||||
const type = String.fromCharCode(...pngData.slice(offset + 4, offset + 8));
|
||||
if (type === "tEXt") {
|
||||
// Get the keyword
|
||||
let keyword_end = offset + 8;
|
||||
while (pngData[keyword_end] !== 0) {
|
||||
keyword_end++;
|
||||
}
|
||||
const keyword = String.fromCharCode(...pngData.slice(offset + 8, keyword_end));
|
||||
// Get the text
|
||||
const text = String.fromCharCode(...pngData.slice(keyword_end + 1, offset + 8 + length));
|
||||
txt_chunks[keyword] = text;
|
||||
}
|
||||
// Start searching for chunks after the PNG signature
|
||||
let offset = 8;
|
||||
let txt_chunks = {};
|
||||
// Loop through the chunks in the PNG file
|
||||
while (offset < pngData.length) {
|
||||
// Get the length of the chunk
|
||||
const length = dataView.getUint32(offset);
|
||||
// Get the chunk type
|
||||
const type = String.fromCharCode(...pngData.slice(offset + 4, offset + 8));
|
||||
if (type === "tEXt") {
|
||||
// Get the keyword
|
||||
let keyword_end = offset + 8;
|
||||
while (pngData[keyword_end] !== 0) {
|
||||
keyword_end++;
|
||||
}
|
||||
const keyword = String.fromCharCode(...pngData.slice(offset + 8, keyword_end));
|
||||
// Get the text
|
||||
const text = String.fromCharCode(...pngData.slice(keyword_end + 1, offset + 8 + length));
|
||||
txt_chunks[keyword] = text;
|
||||
}
|
||||
|
||||
offset += 12 + length;
|
||||
}
|
||||
offset += 12 + length;
|
||||
}
|
||||
|
||||
r(txt_chunks);
|
||||
};
|
||||
r(txt_chunks);
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
type NodeIndex = { node: LGraphNode, index: number }
|
||||
|
||||
export async function importA1111(graph: LGraph, parameters: string, api: ComfyAPI) {
|
||||
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 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 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;
|
||||
const ceil64 = (v) => Math.ceil(v / 64) * 64;
|
||||
|
||||
function getWidget(node: LGraphNode, name: string) {
|
||||
return node.widgets.find((w) => w.name === name);
|
||||
}
|
||||
function getWidget(node: LGraphNode, name: string) {
|
||||
return node.widgets.find((w) => w.name === name);
|
||||
}
|
||||
|
||||
function setWidgetValue(node: LGraphNode, name: string, value: any, isOptionPrefix: boolean = false) {
|
||||
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 setWidgetValue(node: LGraphNode, name: string, value: any, isOptionPrefix: boolean = false) {
|
||||
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: LGraphNode, text: string, prevClip: NodeIndex, prevModel: NodeIndex) {
|
||||
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 "";
|
||||
});
|
||||
function createLoraNodes(clipNode: LGraphNode, text: string, prevClip: NodeIndex, prevModel: NodeIndex) {
|
||||
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 };
|
||||
}
|
||||
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);
|
||||
}
|
||||
prevClip.node.connect(1, clipNode, 0);
|
||||
prevModel.node.connect(0, samplerNode, 0);
|
||||
if (hrSamplerNode) {
|
||||
prevModel.node.connect(0, hrSamplerNode, 0);
|
||||
}
|
||||
|
||||
return { text, prevModel, prevClip };
|
||||
}
|
||||
return { text, prevModel, prevClip };
|
||||
}
|
||||
|
||||
function replaceEmbeddings(text: string) {
|
||||
return text.replaceAll(
|
||||
new RegExp(
|
||||
"\\b(" + embeddings.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\b|\\b") + ")\\b",
|
||||
"ig"
|
||||
),
|
||||
"embedding:$1"
|
||||
);
|
||||
}
|
||||
function replaceEmbeddings(text: string) {
|
||||
return text.replaceAll(
|
||||
new RegExp(
|
||||
"\\b(" + embeddings.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\b|\\b") + ")\\b",
|
||||
"ig"
|
||||
),
|
||||
"embedding:$1"
|
||||
);
|
||||
}
|
||||
|
||||
function popOpt(name: string) {
|
||||
const v = opts[name];
|
||||
delete opts[name];
|
||||
return v;
|
||||
}
|
||||
function popOpt(name: string) {
|
||||
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);
|
||||
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);
|
||||
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: string) {
|
||||
setWidgetValue(ckptNode, "ckpt_name", v, true);
|
||||
},
|
||||
"cfg scale"(v: number) {
|
||||
setWidgetValue(samplerNode, "cfg", +v);
|
||||
},
|
||||
"clip skip"(v: number) {
|
||||
setWidgetValue(clipSkipNode, "stop_at_clip_layer", -v);
|
||||
},
|
||||
sampler(v: string) {
|
||||
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: string) {
|
||||
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");
|
||||
const handlers = {
|
||||
model(v: string) {
|
||||
setWidgetValue(ckptNode, "ckpt_name", v, true);
|
||||
},
|
||||
"cfg scale"(v: number) {
|
||||
setWidgetValue(samplerNode, "cfg", +v);
|
||||
},
|
||||
"clip skip"(v: number) {
|
||||
setWidgetValue(clipSkipNode, "stop_at_clip_layer", -v);
|
||||
},
|
||||
sampler(v: string) {
|
||||
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: string) {
|
||||
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);
|
||||
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];
|
||||
}
|
||||
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: LGraphNode;
|
||||
let latentNode: LGraphNode;
|
||||
let upscaleNode: LGraphNode;
|
||||
let latentNode: LGraphNode;
|
||||
|
||||
if (hrMethod.startsWith("Latent")) {
|
||||
latentNode = upscaleNode = LiteGraph.createNode("LatentUpscale");
|
||||
graph.add(upscaleNode);
|
||||
samplerNode.connect(0, upscaleNode, 0);
|
||||
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);
|
||||
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 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);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
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));
|
||||
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: number) {
|
||||
setWidgetValue(samplerNode, "steps", +v);
|
||||
},
|
||||
seed(v: number) {
|
||||
setWidgetValue(samplerNode, "seed", +v);
|
||||
},
|
||||
};
|
||||
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: number) {
|
||||
setWidgetValue(samplerNode, "steps", +v);
|
||||
},
|
||||
seed(v: number) {
|
||||
setWidgetValue(samplerNode, "seed", +v);
|
||||
},
|
||||
};
|
||||
|
||||
for (const opt in opts) {
|
||||
if (opt in handlers) {
|
||||
handlers[opt](popOpt(opt));
|
||||
}
|
||||
}
|
||||
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"));
|
||||
}
|
||||
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;
|
||||
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));
|
||||
setWidgetValue(positiveNode, "text", replaceEmbeddings(positive));
|
||||
setWidgetValue(negativeNode, "text", replaceEmbeddings(negative));
|
||||
|
||||
graph.arrange();
|
||||
graph.arrange();
|
||||
|
||||
for (const opt of ["model hash", "ensd"]) {
|
||||
delete opts[opt];
|
||||
}
|
||||
for (const opt of ["model hash", "ensd"]) {
|
||||
delete opts[opt];
|
||||
}
|
||||
|
||||
console.warn("Unhandled parameters:", opts);
|
||||
}
|
||||
}
|
||||
console.warn("Unhandled parameters:", opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
import type { LGraph, LGraphNode, IWidget } from "litegraph.js";
|
||||
import type { LGraph, LGraphNode, IWidget } from "@litegraph-ts/core";
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
|
||||
export type WidgetUIState = {
|
||||
@@ -64,10 +64,10 @@ function configureFinished(graph: LGraph) {
|
||||
}
|
||||
|
||||
export default
|
||||
{
|
||||
...store,
|
||||
nodeAdded,
|
||||
nodeRemoved,
|
||||
configureFinished,
|
||||
clear
|
||||
} as WidgetStateStore;
|
||||
{
|
||||
...store,
|
||||
nodeAdded,
|
||||
nodeRemoved,
|
||||
configureFinished,
|
||||
clear
|
||||
} as WidgetStateStore;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IWidget, LGraphNode } from "litegraph.js";
|
||||
import type { IWidget, LGraphNode } from "@litegraph-js/core";
|
||||
import type ComfyApp from "$lib/components/ComfyApp";
|
||||
|
||||
export interface WidgetData {
|
||||
@@ -28,7 +28,7 @@ function getNumberDefaults(inputData: any, defaultStep: number): NumberDefaults
|
||||
|
||||
const FLOAT: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any): WidgetData => {
|
||||
const { val, config } = getNumberDefaults(inputData, 0.5);
|
||||
return { widget: node.addWidget("number", inputName, val, () => {}, config) };
|
||||
return { widget: node.addWidget("number", inputName, val, () => { }, config) };
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ const INT: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any)
|
||||
"number",
|
||||
inputName,
|
||||
val,
|
||||
function (v) {
|
||||
function(v) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
},
|
||||
@@ -55,7 +55,7 @@ const STRING: WidgetFactory = (node: LGraphNode, inputName: string, inputData: a
|
||||
// if (multiline) {
|
||||
// return addMultilineWidget(node, inputName, { defaultVal, ...inputData[1] }, app);
|
||||
// } else {
|
||||
return { widget: node.addWidget("text", inputName, defaultVal, () => {}, { multiline }) };
|
||||
return { widget: node.addWidget("text", inputName, defaultVal, () => { }, { multiline }) };
|
||||
// }
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ const COMBO: WidgetFactory = (node: LGraphNode, inputName: string, inputData: an
|
||||
if (inputData[1] && inputData[1].default) {
|
||||
defaultValue = inputData[1].default;
|
||||
}
|
||||
return { widget: node.addWidget("combo", inputName, defaultValue, () => {}, { values: type }) };
|
||||
return { widget: node.addWidget("combo", inputName, defaultValue, () => { }, { values: type }) };
|
||||
}
|
||||
|
||||
const IMAGEUPLOAD: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any, app): WidgetData => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import ComfyApp from "$lib/components/ComfyApp.svelte"
|
||||
import "litegraph.js/css/litegraph.css";
|
||||
import "@litegraph-ts/core/css/litegraph.css";
|
||||
</script>
|
||||
|
||||
<ComfyApp/>
|
||||
|
||||
@@ -9,8 +9,8 @@ type OutputProps = {}
|
||||
// contain a `PageLoad` type with a `params` and `data` object that matches our route.
|
||||
// You need to run the dev server or `svelte-kit sync` to generate them.
|
||||
export const load: PageLoad<OutputProps> = async ({
|
||||
params,
|
||||
data,
|
||||
params,
|
||||
data,
|
||||
}) => {
|
||||
return {}
|
||||
return {}
|
||||
}
|
||||
|
||||
1604
src/types/litegraph.js/litegraph.d.ts
vendored
1604
src/types/litegraph.js/litegraph.d.ts
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user