Image cache node
This commit is contained in:
Submodule litegraph updated: 115bef46a5...4500dec3b9
@@ -45,6 +45,7 @@
|
|||||||
"@litegraph-ts/nodes-basic": "workspace:*",
|
"@litegraph-ts/nodes-basic": "workspace:*",
|
||||||
"@litegraph-ts/nodes-events": "workspace:*",
|
"@litegraph-ts/nodes-events": "workspace:*",
|
||||||
"@litegraph-ts/nodes-math": "workspace:*",
|
"@litegraph-ts/nodes-math": "workspace:*",
|
||||||
|
"@litegraph-ts/nodes-strings": "workspace:*",
|
||||||
"@litegraph-ts/tsconfig": "workspace:*",
|
"@litegraph-ts/tsconfig": "workspace:*",
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
||||||
"@tsconfig/svelte": "^4.0.1",
|
"@tsconfig/svelte": "^4.0.1",
|
||||||
|
|||||||
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
@@ -43,6 +43,9 @@ importers:
|
|||||||
'@litegraph-ts/nodes-math':
|
'@litegraph-ts/nodes-math':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:litegraph/packages/nodes-math
|
version: link:litegraph/packages/nodes-math
|
||||||
|
'@litegraph-ts/nodes-strings':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:litegraph/packages/nodes-strings
|
||||||
'@litegraph-ts/tsconfig':
|
'@litegraph-ts/tsconfig':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:litegraph/packages/tsconfig
|
version: link:litegraph/packages/tsconfig
|
||||||
@@ -805,6 +808,22 @@ importers:
|
|||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.3.1
|
version: 4.3.1
|
||||||
|
|
||||||
|
litegraph/packages/nodes-strings:
|
||||||
|
dependencies:
|
||||||
|
'@litegraph-ts/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../core
|
||||||
|
devDependencies:
|
||||||
|
'@litegraph-ts/tsconfig':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../tsconfig
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.0.3
|
||||||
|
version: 5.0.3
|
||||||
|
vite:
|
||||||
|
specifier: ^4.2.1
|
||||||
|
version: 4.3.1
|
||||||
|
|
||||||
litegraph/packages/tsconfig: {}
|
litegraph/packages/tsconfig: {}
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|||||||
@@ -231,9 +231,9 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
override onNodeSelected(node: LGraphNode) {
|
override onSelectionChange(nodes: Record<number, LGraphNode>) {
|
||||||
const ls = get(layoutState)
|
const ls = get(layoutState)
|
||||||
ls.currentSelectionNodes = [node]
|
ls.currentSelectionNodes = Object.values(nodes)
|
||||||
ls.currentSelection = []
|
ls.currentSelection = []
|
||||||
layoutState.set(ls)
|
layoutState.set(ls)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import type TypedEmitter from "typed-emitter";
|
|||||||
import "@litegraph-ts/nodes-basic"
|
import "@litegraph-ts/nodes-basic"
|
||||||
import "@litegraph-ts/nodes-events"
|
import "@litegraph-ts/nodes-events"
|
||||||
import "@litegraph-ts/nodes-math"
|
import "@litegraph-ts/nodes-math"
|
||||||
|
import "@litegraph-ts/nodes-strings"
|
||||||
|
import "$lib/nodes/index"
|
||||||
import * as nodes from "$lib/nodes/index"
|
import * as nodes from "$lib/nodes/index"
|
||||||
|
|
||||||
import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas";
|
import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas";
|
||||||
@@ -25,7 +27,7 @@ import ComfyGraph from "$lib/ComfyGraph";
|
|||||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
import { promptToGraphVis, toGraphVis } from "$lib/utils";
|
import { promptToGraphVis } from "$lib/utils";
|
||||||
|
|
||||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||||
|
|
||||||
@@ -578,9 +580,15 @@ export default class ComfyApp {
|
|||||||
try {
|
try {
|
||||||
while (this.queueItems.length) {
|
while (this.queueItems.length) {
|
||||||
({ num, batchCount } = this.queueItems.pop());
|
({ num, batchCount } = this.queueItems.pop());
|
||||||
console.log(`Queue get! ${num} ${batchCount}`);
|
console.debug(`Queue get! ${num} ${batchCount} ${tag}`);
|
||||||
|
|
||||||
for (let i = 0; i < batchCount; i++) {
|
for (let i = 0; i < batchCount; i++) {
|
||||||
|
for (const node of this.lGraph._nodes_in_order) {
|
||||||
|
if ("beforeQueued" in node) {
|
||||||
|
(node as ComfyGraphNode).beforeQueued();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const p = await this.graphToPrompt(tag);
|
const p = await this.graphToPrompt(tag);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -41,6 +41,13 @@
|
|||||||
targetType = ""
|
targetType = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validNodeProperty(spec: AttributesSpec, node: LGraphNode): boolean {
|
||||||
|
if (spec.validNodeTypes) {
|
||||||
|
return spec.validNodeTypes.indexOf(node.type) !== -1;
|
||||||
|
}
|
||||||
|
return spec.name in node.properties
|
||||||
|
}
|
||||||
|
|
||||||
function updateAttribute(entry: AttributesSpec, value: any) {
|
function updateAttribute(entry: AttributesSpec, value: any) {
|
||||||
if (target) {
|
if (target) {
|
||||||
const name = entry.name
|
const name = entry.name
|
||||||
@@ -157,7 +164,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if node}
|
{:else if node}
|
||||||
{#if spec.location === "nodeProps" && spec.name in node.properties}
|
{#if spec.location === "nodeProps" && validNodeProperty(spec, node)}
|
||||||
<div class="props-entry">
|
<div class="props-entry">
|
||||||
{#if spec.type === "string"}
|
{#if spec.type === "string"}
|
||||||
<TextBox
|
<TextBox
|
||||||
|
|||||||
@@ -5,17 +5,18 @@ import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
|||||||
import { toast } from '@zerodevx/svelte-toast'
|
import { toast } from '@zerodevx/svelte-toast'
|
||||||
import type { GalleryOutput } from "./ComfyWidgetNodes";
|
import type { GalleryOutput } from "./ComfyWidgetNodes";
|
||||||
|
|
||||||
export interface ComfyAfterQueuedEventProperties extends Record<any, any> {
|
export interface ComfyQueueEventsProperties extends Record<any, any> {
|
||||||
prompt: SerializedPrompt
|
prompt: SerializedPrompt | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyAfterQueuedEvent extends ComfyGraphNode {
|
export class ComfyQueueEvents extends ComfyGraphNode {
|
||||||
override properties: ComfyAfterQueuedEventProperties = {
|
override properties: ComfyQueueEventsProperties = {
|
||||||
prompt: null
|
prompt: null
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
outputs: [
|
outputs: [
|
||||||
|
{ name: "beforeQueued", type: BuiltInSlotType.EVENT },
|
||||||
{ name: "afterQueued", type: BuiltInSlotType.EVENT },
|
{ name: "afterQueued", type: BuiltInSlotType.EVENT },
|
||||||
{ name: "prompt", type: "*" }
|
{ name: "prompt", type: "*" }
|
||||||
],
|
],
|
||||||
@@ -23,17 +24,22 @@ export class ComfyAfterQueuedEvent extends ComfyGraphNode {
|
|||||||
|
|
||||||
override onPropertyChanged(property: string, value: any, prevValue?: any) {
|
override onPropertyChanged(property: string, value: any, prevValue?: any) {
|
||||||
if (property === "value") {
|
if (property === "value") {
|
||||||
this.setOutputData(0, this.properties.prompt)
|
this.setOutputData(2, this.properties.prompt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override onExecute() {
|
override onExecute() {
|
||||||
this.setOutputData(0, this.properties.prompt)
|
this.setOutputData(2, this.properties.prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
override beforeQueued() {
|
||||||
|
this.setProperty("value", null)
|
||||||
|
this.triggerSlot(0, "bang")
|
||||||
}
|
}
|
||||||
|
|
||||||
override afterQueued(p: SerializedPrompt) {
|
override afterQueued(p: SerializedPrompt) {
|
||||||
this.setProperty("value", p)
|
this.setProperty("value", p)
|
||||||
this.triggerSlot(0, "bang")
|
this.triggerSlot(1, "bang")
|
||||||
}
|
}
|
||||||
|
|
||||||
override onSerialize(o: SerializedLGraphNode) {
|
override onSerialize(o: SerializedLGraphNode) {
|
||||||
@@ -43,19 +49,21 @@ export class ComfyAfterQueuedEvent extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
LiteGraph.registerNodeType({
|
||||||
class: ComfyAfterQueuedEvent,
|
class: ComfyQueueEvents,
|
||||||
title: "Comfy.AfterQueuedEvent",
|
title: "Comfy.QueueEvents",
|
||||||
desc: "Triggers a 'bang' event when a prompt is queued.",
|
desc: "Triggers a 'bang' event when a prompt is queued.",
|
||||||
type: "actions/after_queued"
|
type: "actions/queue_events"
|
||||||
})
|
})
|
||||||
|
|
||||||
export interface ComfyOnExecutedEventProperties extends Record<any, any> {
|
export interface ComfyOnExecutedEventProperties extends Record<any, any> {
|
||||||
images: GalleryOutput | null
|
images: GalleryOutput | null,
|
||||||
|
filename: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyOnExecutedEvent extends ComfyGraphNode {
|
export class ComfyOnExecutedEvent extends ComfyGraphNode {
|
||||||
override properties: ComfyOnExecutedEventProperties = {
|
override properties: ComfyOnExecutedEventProperties = {
|
||||||
images: null
|
images: null,
|
||||||
|
filename: null
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
@@ -63,7 +71,7 @@ export class ComfyOnExecutedEvent extends ComfyGraphNode {
|
|||||||
{ name: "images", type: "IMAGE" }
|
{ name: "images", type: "IMAGE" }
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
{ name: "images", type: "IMAGE" },
|
{ name: "images", type: "OUTPUT" },
|
||||||
{ name: "onExecuted", type: BuiltInSlotType.EVENT },
|
{ name: "onExecuted", type: BuiltInSlotType.EVENT },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -76,6 +84,7 @@ export class ComfyOnExecutedEvent extends ComfyGraphNode {
|
|||||||
override receiveOutput(output: any) {
|
override receiveOutput(output: any) {
|
||||||
if (output && "images" in output) {
|
if (output && "images" in output) {
|
||||||
this.setProperty("images", output as GalleryOutput)
|
this.setProperty("images", output as GalleryOutput)
|
||||||
|
this.setOutputData(0, this.properties.images)
|
||||||
this.triggerSlot(1, "bang")
|
this.triggerSlot(1, "bang")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,3 +211,43 @@ LiteGraph.registerNodeType({
|
|||||||
desc: "Displays a message.",
|
desc: "Displays a message.",
|
||||||
type: "actions/notify"
|
type: "actions/notify"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export interface ComfyExecuteSubgraphActionProperties extends Record<any, any> {
|
||||||
|
tag: string | null,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ComfyExecuteSubgraphAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfyExecuteSubgraphActionProperties = {
|
||||||
|
tag: null
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "execute", type: BuiltInSlotType.ACTION },
|
||||||
|
{ name: "tag", type: "string" }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExecute() {
|
||||||
|
const tag = this.getInputData(1)
|
||||||
|
if (tag)
|
||||||
|
this.setProperty("tag", tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
const tag = this.getInputData(1) || this.properties.tag;
|
||||||
|
|
||||||
|
const app = (window as any)?.app;
|
||||||
|
if (!app)
|
||||||
|
return;
|
||||||
|
|
||||||
|
app.queuePrompt(0, 1, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyExecuteSubgraphAction,
|
||||||
|
title: "Comfy.ExecuteSubgraphAction",
|
||||||
|
desc: "Runs a part of the graph based on a tag",
|
||||||
|
type: "actions/execute_subgraph"
|
||||||
|
})
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export type DefaultWidgetLayout = {
|
|||||||
export default class ComfyGraphNode extends LGraphNode {
|
export default class ComfyGraphNode extends LGraphNode {
|
||||||
isBackendNode?: boolean;
|
isBackendNode?: boolean;
|
||||||
|
|
||||||
|
beforeQueued?(): void;
|
||||||
afterQueued?(prompt: SerializedPrompt): void;
|
afterQueued?(prompt: SerializedPrompt): void;
|
||||||
onExecuted?(output: any): void;
|
onExecuted?(output: any): void;
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ export default class ComfyGraphNode extends LGraphNode {
|
|||||||
for (let index = 0; index < this.inputs.length; index++) {
|
for (let index = 0; index < this.inputs.length; index++) {
|
||||||
const input = this.inputs[index]
|
const input = this.inputs[index]
|
||||||
const serInput = o.inputs[index]
|
const serInput = o.inputs[index]
|
||||||
if ("widgetNodeType" in serInput) {
|
if (serInput && "widgetNodeType" in serInput) {
|
||||||
const comfyInput = input as IComfyInputSlot
|
const comfyInput = input as IComfyInputSlot
|
||||||
const ty: string = serInput.widgetNodeType as any
|
const ty: string = serInput.widgetNodeType as any
|
||||||
const widgetNode = Object.values(LiteGraph.registered_node_types)
|
const widgetNode = Object.values(LiteGraph.registered_node_types)
|
||||||
|
|||||||
211
src/lib/nodes/ComfyImageCacheNode.ts
Normal file
211
src/lib/nodes/ComfyImageCacheNode.ts
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout, clamp } from "@litegraph-ts/core";
|
||||||
|
import ComfyGraphNode from "./ComfyGraphNode";
|
||||||
|
import type { GalleryOutput } from "./ComfyWidgetNodes";
|
||||||
|
|
||||||
|
export interface ComfyImageCacheNodeProperties extends Record<any, any> {
|
||||||
|
images: GalleryOutput | null,
|
||||||
|
index: number,
|
||||||
|
filenames: Record<number, { filename: string | null, status: ImageCacheState }>,
|
||||||
|
genNumber: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageCacheState = "none" | "uploading" | "failed" | "cached"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A node that can act as both an input and output image node by uploading
|
||||||
|
* the output file into ComfyUI's input folder.
|
||||||
|
*/
|
||||||
|
export default class ComfyImageCacheNode extends ComfyGraphNode {
|
||||||
|
override properties: ComfyImageCacheNodeProperties = {
|
||||||
|
images: null,
|
||||||
|
index: 0,
|
||||||
|
filenames: {},
|
||||||
|
genNumber: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "images", type: "OUTPUT" },
|
||||||
|
{ name: "index", type: "number" },
|
||||||
|
{ name: "store", type: BuiltInSlotType.ACTION },
|
||||||
|
{ name: "clear", type: BuiltInSlotType.ACTION }
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{ name: "filename", type: "string" },
|
||||||
|
{ name: "state", type: "string" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private _uploadPromise: Promise<void> | null = null;
|
||||||
|
private _state: ImageCacheState = "none"
|
||||||
|
|
||||||
|
stateWidget: ITextWidget;
|
||||||
|
filenameWidget: ITextWidget;
|
||||||
|
|
||||||
|
constructor(name?: string) {
|
||||||
|
super(name)
|
||||||
|
this.stateWidget = this.addWidget<ITextWidget>(
|
||||||
|
"text",
|
||||||
|
"State",
|
||||||
|
"none"
|
||||||
|
);
|
||||||
|
this.stateWidget.disabled = true;
|
||||||
|
|
||||||
|
this.filenameWidget = this.addWidget<ITextWidget>(
|
||||||
|
"text",
|
||||||
|
"File",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
this.filenameWidget.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override onPropertyChanged(property: string, value: any, prevValue?: any) {
|
||||||
|
if (property === "images") {
|
||||||
|
if (value != null)
|
||||||
|
this.properties.index = clamp(this.properties.index, 0, value.length)
|
||||||
|
else
|
||||||
|
this.properties.index = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.properties.filenames && this.properties.images) {
|
||||||
|
const fileCount = this.properties.images.images.length;
|
||||||
|
const cachedCount = Object.keys(this.properties.filenames).length
|
||||||
|
console.warn(cachedCount, this.properties.filenames)
|
||||||
|
this.filenameWidget.value = `${fileCount} files, ${cachedCount} cached`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExecute() {
|
||||||
|
const index = this.getInputData(1)
|
||||||
|
if (typeof index === "number")
|
||||||
|
this.setIndex(index)
|
||||||
|
|
||||||
|
const existing = this.properties.filenames[this.properties.index]
|
||||||
|
let state = "none"
|
||||||
|
if (existing)
|
||||||
|
state = existing.status
|
||||||
|
|
||||||
|
this.stateWidget.value = state
|
||||||
|
|
||||||
|
let filename = null
|
||||||
|
if (this.properties.index in this.properties.filenames)
|
||||||
|
filename = this.properties.filenames[this.properties.index].filename
|
||||||
|
|
||||||
|
this.setOutputData(0, filename)
|
||||||
|
this.setOutputData(1, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
private setIndex(newIndex: number, force: boolean = false) {
|
||||||
|
console.debug("[ComfyImageCacheNode] setIndex", newIndex, force)
|
||||||
|
|
||||||
|
if (newIndex === this.properties.index && !force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!this.properties.images || newIndex < 0 || newIndex >= this.properties.images.images.length) {
|
||||||
|
console.debug("[ComfyImageCacheNode] invalid indexes", newIndex, this.properties.images)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setProperty("index", newIndex)
|
||||||
|
|
||||||
|
const data = this.properties.images.images[newIndex]
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.properties.filenames ||= {}
|
||||||
|
const existing = this.properties.filenames[newIndex]
|
||||||
|
|
||||||
|
if (existing != null && existing.status === "cached") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastGenNumber = this.properties.genNumber
|
||||||
|
|
||||||
|
// ComfyUI's LoadImage node only operates on files in its input
|
||||||
|
// folder. Usually we're dealing with an image in either the output
|
||||||
|
// folder (SaveImage) or the temp folder (PreviewImage). So we have
|
||||||
|
// to copy the image into ComfyUI's input folder first by using
|
||||||
|
// their upload API.
|
||||||
|
|
||||||
|
if (data.subfolder === "input") {
|
||||||
|
// Already in the correct folder for use by LoadImage
|
||||||
|
this.properties.filenames[newIndex] = { filename: data.filename, status: "cached" }
|
||||||
|
this.onPropertyChanged("filenames", this.properties.filenames)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.properties.filenames[newIndex] = { filename: null, status: "uploading" }
|
||||||
|
this.onPropertyChanged("filenames", this.properties.filenames)
|
||||||
|
const url = "http://localhost:8188" // TODO make configurable
|
||||||
|
const params = new URLSearchParams(data)
|
||||||
|
|
||||||
|
const promise = fetch(url + "/view?" + params)
|
||||||
|
.then((r) => r.blob())
|
||||||
|
.then((blob) => {
|
||||||
|
console.debug("Fetchin", url, params)
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("image", blob, data.filename);
|
||||||
|
return fetch(
|
||||||
|
new Request(url + "/upload/image", {
|
||||||
|
body: formData,
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((json) => {
|
||||||
|
console.debug("Gottem", json)
|
||||||
|
if (lastGenNumber === this.properties.genNumber) {
|
||||||
|
this.properties.filenames[newIndex] = { filename: data.filename, status: "cached" }
|
||||||
|
this.onPropertyChanged("filenames", this.properties.filenames)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.warn("[ComfyImageCacheNode] New generation since index switched!")
|
||||||
|
}
|
||||||
|
this._uploadPromise = null;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("Error uploading:", e)
|
||||||
|
if (lastGenNumber === this.properties.genNumber) {
|
||||||
|
this.properties.filenames[newIndex] = { filename: null, status: "failed" }
|
||||||
|
this.onPropertyChanged("filenames", this.properties.filenames)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.warn("[ComfyImageCacheNode] New generation since index switched!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this._uploadPromise)
|
||||||
|
this._uploadPromise.then(() => promise)
|
||||||
|
else
|
||||||
|
this._uploadPromise = promise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any) {
|
||||||
|
if (action === "clear") {
|
||||||
|
this.setProperty("images", null)
|
||||||
|
this.setProperty("filenames", {})
|
||||||
|
this.setProperty("index", 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const link = this.getInputLink(0)
|
||||||
|
|
||||||
|
if (link.data && "images" in link.data) {
|
||||||
|
this.setProperty("genNumber", this.properties.genNumber + 1)
|
||||||
|
this.setProperty("images", link.data as GalleryOutput)
|
||||||
|
this.setProperty("filenames", {})
|
||||||
|
console.debug("[ComfyImageCacheNode] Received output!", link.data)
|
||||||
|
this.setIndex(0, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyImageCacheNode,
|
||||||
|
title: "Comfy.ImageCache",
|
||||||
|
desc: "Allows reusing a previously output image by uploading it into ComfyUI's input folder.",
|
||||||
|
type: "image/cache"
|
||||||
|
})
|
||||||
@@ -119,7 +119,6 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
if (this.inputs.length >= this.inputIndex) {
|
if (this.inputs.length >= this.inputIndex) {
|
||||||
const data = this.getInputData(this.inputIndex)
|
const data = this.getInputData(this.inputIndex)
|
||||||
if (data) { // TODO can "null" be a legitimate value here?
|
if (data) { // TODO can "null" be a legitimate value here?
|
||||||
console.log(data)
|
|
||||||
this.setValue(data)
|
this.setValue(data)
|
||||||
const input = this.getInputLink(this.inputIndex)
|
const input = this.getInputLink(this.inputIndex)
|
||||||
input.data = null;
|
input.data = null;
|
||||||
@@ -407,7 +406,7 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
inputs: [
|
inputs: [
|
||||||
{ name: "images", type: "IMAGE" },
|
{ name: "images", type: "OUTPUT" },
|
||||||
{ name: "store", type: BuiltInSlotType.ACTION }
|
{ name: "store", type: BuiltInSlotType.ACTION }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export { default as ComfyReroute } from "./ComfyReroute"
|
export { default as ComfyReroute } from "./ComfyReroute"
|
||||||
export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes"
|
export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes"
|
||||||
export { ComfyAfterQueuedEvent, ComfyCopyAction, ComfySwapAction, ComfyNotifyAction, ComfyOnExecutedEvent } from "./ComfyActionNodes"
|
export { ComfyQueueEvents, ComfyCopyAction, ComfySwapAction, ComfyNotifyAction, ComfyOnExecutedEvent, ComfyExecuteSubgraphAction } from "./ComfyActionNodes"
|
||||||
export { default as ComfyValueControl } from "./ComfyValueControl"
|
export { default as ComfyValueControl } from "./ComfyValueControl"
|
||||||
export { default as ComfySelector } from "./ComfySelector"
|
export { default as ComfySelector } from "./ComfySelector"
|
||||||
|
export { default as ComfyImageCacheNode } from "./ComfyImageCacheNode"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export type Attributes = {
|
|||||||
classes: string,
|
classes: string,
|
||||||
blockVariant?: "block" | "hidden",
|
blockVariant?: "block" | "hidden",
|
||||||
hidden?: boolean,
|
hidden?: boolean,
|
||||||
|
disabled?: boolean,
|
||||||
flexGrow?: number
|
flexGrow?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ export type AttributesSpec = {
|
|||||||
|
|
||||||
values?: string[],
|
values?: string[],
|
||||||
hidden?: boolean,
|
hidden?: boolean,
|
||||||
|
validNodeTypes?: string[],
|
||||||
|
|
||||||
serialize?: (arg: any) => string,
|
serialize?: (arg: any) => string,
|
||||||
deserialize?: (arg: string) => any,
|
deserialize?: (arg: string) => any,
|
||||||
@@ -73,6 +75,12 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
location: "widget",
|
location: "widget",
|
||||||
editable: true
|
editable: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "disabled",
|
||||||
|
type: "boolean",
|
||||||
|
location: "widget",
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "direction",
|
name: "direction",
|
||||||
type: "enum",
|
type: "enum",
|
||||||
@@ -104,6 +112,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
{
|
{
|
||||||
categoryName: "behavior",
|
categoryName: "behavior",
|
||||||
specs: [
|
specs: [
|
||||||
|
// Node variables
|
||||||
{
|
{
|
||||||
name: "tags",
|
name: "tags",
|
||||||
type: "string",
|
type: "string",
|
||||||
@@ -116,24 +125,40 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
return arg.split(",").map(s => s.trim())
|
return arg.split(",").map(s => s.trim())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Range
|
||||||
{
|
{
|
||||||
name: "min",
|
name: "min",
|
||||||
type: "number",
|
type: "number",
|
||||||
location: "nodeProps",
|
location: "nodeProps",
|
||||||
editable: true,
|
editable: true,
|
||||||
|
validNodeTypes: ["ui/slider"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "max",
|
name: "max",
|
||||||
type: "number",
|
type: "number",
|
||||||
location: "nodeProps",
|
location: "nodeProps",
|
||||||
editable: true
|
editable: true,
|
||||||
|
validNodeTypes: ["ui/slider"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "step",
|
name: "step",
|
||||||
type: "number",
|
type: "number",
|
||||||
location: "nodeProps",
|
location: "nodeProps",
|
||||||
editable: true,
|
editable: true,
|
||||||
|
validNodeTypes: ["ui/slider"],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Button
|
||||||
|
{
|
||||||
|
name: "message",
|
||||||
|
type: "string",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
validNodeTypes: ["ui/button"],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Workflow
|
||||||
{
|
{
|
||||||
name: "defaultWorkflow",
|
name: "defaultWorkflow",
|
||||||
type: "string",
|
type: "string",
|
||||||
@@ -531,8 +556,6 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
|||||||
attrsChanged: writable(false)
|
attrsChanged: writable(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
dragItem.attrs.flexGrow = 100;
|
|
||||||
|
|
||||||
const dragEntry: DragItemEntry = {
|
const dragEntry: DragItemEntry = {
|
||||||
dragItem,
|
dragItem,
|
||||||
children: [],
|
children: [],
|
||||||
|
|||||||
@@ -30,7 +30,11 @@
|
|||||||
|
|
||||||
<div class="wrapper gradio-button">
|
<div class="wrapper gradio-button">
|
||||||
{#if node !== null}
|
{#if node !== null}
|
||||||
<Button on:click={onClick} variant="primary" {style}>
|
<Button
|
||||||
|
disabled={widget.attrs.disabled}
|
||||||
|
on:click={onClick}
|
||||||
|
variant="primary"
|
||||||
|
{style}>
|
||||||
{widget.attrs.title}
|
{widget.attrs.title}
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
<Select
|
<Select
|
||||||
bind:value={option}
|
bind:value={option}
|
||||||
items={node.properties.values}
|
items={node.properties.values}
|
||||||
disabled={node.properties.values.length === 0}
|
disabled={widget.attrs.disabled || node.properties.values.length === 0}
|
||||||
clearable={false}
|
clearable={false}
|
||||||
showChevron={true}
|
showChevron={true}
|
||||||
on:change
|
on:change
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
{#if node !== null && option !== null}
|
{#if node !== null && option !== null}
|
||||||
<Range
|
<Range
|
||||||
bind:value={option}
|
bind:value={option}
|
||||||
|
disabled={widget.attrs.disabled}
|
||||||
minimum={node.properties.min}
|
minimum={node.properties.min}
|
||||||
maximum={node.properties.max}
|
maximum={node.properties.max}
|
||||||
step={node.properties.step}
|
step={node.properties.step}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
<TextBox
|
<TextBox
|
||||||
bind:value={$nodeValue}
|
bind:value={$nodeValue}
|
||||||
label={widget.attrs.title}
|
label={widget.attrs.title}
|
||||||
|
disabled={widget.attrs.disabled}
|
||||||
lines={node.properties.multiline ? 5 : 1}
|
lines={node.properties.multiline ? 5 : 1}
|
||||||
max_lines={node.properties.multiline ? 5 : 1}
|
max_lines={node.properties.multiline ? 5 : 1}
|
||||||
show_label={true}
|
show_label={true}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import FullReload from 'vite-plugin-full-reload';
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
plugins: [
|
plugins: [
|
||||||
|
FullReload(["src/**/*.{js,ts,svelte}"]),
|
||||||
svelte(),
|
svelte(),
|
||||||
// FullReload(["src/**/*.{js,ts,svelte}"])
|
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias:{
|
alias: {
|
||||||
'$lib': resolve(__dirname, './src/lib'),
|
'$lib': resolve(__dirname, './src/lib'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -28,9 +28,9 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
|
|
||||||
// hmr: {
|
// hmr: {
|
||||||
// clientPort: 443,
|
// clientPort: 443,
|
||||||
// },
|
// },
|
||||||
// fs: {
|
// fs: {
|
||||||
// allow: [
|
// allow: [
|
||||||
// "src",
|
// "src",
|
||||||
|
|||||||
Reference in New Issue
Block a user