TEMP refactor file passing

This commit is contained in:
space-nuko
2023-05-13 16:19:42 -05:00
parent 0656ae1d3a
commit 05bcce5573
14 changed files with 241 additions and 555 deletions

View File

@@ -5,10 +5,10 @@ import queueState from "$lib/stores/queueState";
import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWidget, type SerializedLGraphNode, type SlotLayout, type PropertyLayout } from "@litegraph-ts/core";
import { get } from "svelte/store";
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
import type { ComfyWidgetNode, GalleryOutput, GalleryOutputEntry } from "./ComfyWidgetNodes";
import type { ComfyWidgetNode, ComfyExecutionResult, ComfyImageLocation } from "./ComfyWidgetNodes";
import type { NotifyOptions } from "$lib/notify";
import type { FileData as GradioFileData } from "@gradio/upload";
import { convertComfyOutputToGradio, reuploadImageToComfyUI, type ComfyUploadImageAPIResponse } from "$lib/utils";
import { convertComfyOutputToGradio, type ComfyUploadImageAPIResponse } from "$lib/utils";
export class ComfyQueueEvents extends ComfyGraphNode {
static slotLayout: SlotLayout = {
@@ -63,7 +63,7 @@ LiteGraph.registerNodeType({
})
export interface ComfyStoreImagesActionProperties extends ComfyGraphNodeProperties {
images: GalleryOutput | null
images: ComfyExecutionResult | null
}
export class ComfyStoreImagesAction extends ComfyGraphNode {
@@ -90,7 +90,7 @@ export class ComfyStoreImagesAction extends ComfyGraphNode {
if (action !== "store" || !param || !("images" in param))
return;
this.setProperty("images", param as GalleryOutput)
this.setProperty("images", param as ComfyExecutionResult)
this.setOutputData(0, this.properties.images)
}
}
@@ -223,7 +223,7 @@ export class ComfyNotifyAction extends ComfyGraphNode {
// native notifications.
if (param != null && typeof param === "object") {
if ("images" in param) {
const output = param as GalleryOutput;
const output = param as ComfyExecutionResult;
const converted = convertComfyOutputToGradio(output);
if (converted.length > 0)
options.imageUrl = converted[0].data;
@@ -581,86 +581,6 @@ LiteGraph.registerNodeType({
type: "events/no_change"
})
export interface ComfyUploadImageActionProperties extends ComfyGraphNodeProperties {
folderType: "output" | "temp"
lastUploadedImageFile: string | null
}
export class ComfyUploadImageAction extends ComfyGraphNode {
override properties: ComfyUploadImageActionProperties = {
tags: [],
folderType: "output",
lastUploadedImageFile: null
}
static slotLayout: SlotLayout = {
inputs: [
{ name: "filename", type: "string" },
{ name: "trigger", type: BuiltInSlotType.ACTION }
],
outputs: [
{ name: "input_filename", type: "string" },
{ name: "uploaded", type: BuiltInSlotType.EVENT }
],
}
private _promise = null;
displayWidget: ITextWidget;
constructor(title?: string) {
super(title);
this.displayWidget = this.addWidget<ITextWidget>(
"text",
"File",
this.properties.lastUploadedImageFile,
"lastUploadedImageFile"
);
this.displayWidget.disabled = true;
}
override onExecute() {
this.setOutputData(0, this.properties.lastUploadedImageFile)
}
override onAction(action: any, param: any) {
if (action !== "trigger" || this._promise != null)
return;
const filename = this.getInputData(0)
if (typeof filename !== "string" || !filename) {
return;
}
const data: GalleryOutputEntry = {
filename,
subfolder: "",
type: this.properties.folderType || "output"
}
this._promise = reuploadImageToComfyUI(data, "input")
.then((entry: GalleryOutputEntry) => {
console.debug("[UploadImageAction] Succeeded", entry)
this.properties.lastUploadedImageFile = entry.filename;
this.triggerSlot(1, this.properties.lastUploadedImageFile);
this._promise = null;
})
.catch((e) => {
console.error("Error uploading:", e)
notify(`Error uploading image to ComfyUi: ${e}`, { type: "error", timeout: 10000 })
this.properties.lastUploadedImageFile = null;
this._promise = null;
})
}
}
LiteGraph.registerNodeType({
class: ComfyUploadImageAction,
title: "Comfy.UploadImageAction",
desc: "Uploads an image from the specified ComfyUI folder into its input folder",
type: "actions/store_images"
})
export interface ComfySetPromptThumbnailsActionProperties extends ComfyGraphNodeProperties {
defaultFolderType: string | null
}
@@ -679,12 +599,12 @@ export class ComfySetPromptThumbnailsAction extends ComfyGraphNode {
_value: any = null;
override getPromptThumbnails(): GalleryOutputEntry[] | null {
override getPromptThumbnails(): ComfyImageLocation[] | null {
const data = this.getInputData(0)
const folderType = this.properties.folderType || "input";
const convertString = (s: string): GalleryOutputEntry => {
const convertString = (s: string): ComfyImageLocation => {
return { filename: data, subfolder: "", type: folderType }
}
@@ -693,13 +613,13 @@ export class ComfySetPromptThumbnailsAction extends ComfyGraphNode {
}
else if (data != null && typeof data === "object") {
if ("filename" in data && "type" in data)
return [data as GalleryOutputEntry];
return [data as ComfyImageLocation];
}
else if (Array.isArray(data) && data.length > 0) {
if (typeof data[0] === "string")
return data.map(convertString)
else if (typeof data[0] === "object" && "filename" in data[0] && "type" in data[0])
return data as GalleryOutputEntry[]
return data as ComfyImageLocation[]
}
return null;
}

View File

@@ -1,7 +1,7 @@
import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas";
import ComfyGraphNode from "./ComfyGraphNode";
import ComfyWidgets from "$lib/widgets"
import type { ComfyWidgetNode, GalleryOutput } from "./ComfyWidgetNodes";
import type { ComfyWidgetNode, ComfyExecutionResult } from "./ComfyWidgetNodes";
import { BuiltInSlotType, type SerializedLGraphNode } from "@litegraph-ts/core";
import type IComfyInputSlot from "$lib/IComfyInputSlot";
import type { ComfyInputConfig } from "$lib/IComfyInputSlot";
@@ -110,7 +110,7 @@ export class ComfyBackendNode extends ComfyGraphNode {
}
}
override onExecuted(outputData: GalleryOutput) {
override onExecuted(outputData: ComfyExecutionResult) {
console.warn("onExecuted outputs", outputData)
this.triggerSlot(0, outputData)
}

View File

@@ -3,7 +3,7 @@ import type { SerializedPrompt } from "$lib/components/ComfyApp";
import type ComfyWidget from "$lib/components/widgets/ComfyWidget";
import { LGraph, LGraphNode, LLink, LiteGraph, NodeMode, type INodeInputSlot, type SerializedLGraphNode, type Vector2, type INodeOutputSlot, LConnectionKind, type SlotType, LGraphCanvas, getStaticPropertyOnInstance, type PropertyLayout, type SlotLayout } from "@litegraph-ts/core";
import type { SvelteComponentDev } from "svelte/internal";
import type { ComfyWidgetNode, GalleryOutput, GalleryOutputEntry } from "./ComfyWidgetNodes";
import type { ComfyWidgetNode, ComfyExecutionResult, ComfyImageLocation } from "./ComfyWidgetNodes";
import type IComfyInputSlot from "$lib/IComfyInputSlot";
import uiState from "$lib/stores/uiState";
import { get } from "svelte/store";
@@ -48,14 +48,14 @@ export default class ComfyGraphNode extends LGraphNode {
* Triggered when the backend sends a finished output back with this node's ID.
* Valid for output nodes like SaveImage and PreviewImage.
*/
onExecuted?(output: GalleryOutput): void;
onExecuted?(output: ComfyExecutionResult): void;
/*
* When a prompt is queued, this will be called on the node if it can
* provide any thumbnails for use with the prompt queue. Useful for HR Fix
* or img2img workloads.
*/
getPromptThumbnails?(): GalleryOutputEntry[] | null
getPromptThumbnails?(): ComfyImageLocation[] | null
/*
* Allows you to manually specify an auto-config for certain input slot

View File

@@ -1,241 +0,0 @@
import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout, clamp, type PropertyLayout, type IComboWidget, type SerializedLGraphNode } from "@litegraph-ts/core";
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
import type { GalleryOutput, GalleryOutputEntry } from "./ComfyWidgetNodes";
import { reuploadImageToComfyUI, type ComfyUploadImageAPIResponse } from "$lib/utils";
export interface ComfyImageCacheNodeProperties extends ComfyGraphNodeProperties {
images: GalleryOutput | null,
index: number,
filenames: Record<number, { filename: string | null, status: ImageCacheState }>,
genNumber: number,
updateMode: "replace" | "append"
}
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 = {
tags: [],
images: null,
index: 0,
filenames: {},
genNumber: 0,
updateMode: "replace"
}
static slotLayout: SlotLayout = {
inputs: [
{ name: "images", type: "OUTPUT" },
{ name: "index", type: "number" },
{ name: "store", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } },
{ name: "clear", type: BuiltInSlotType.ACTION }
],
outputs: [
{ name: "filename", type: "string" },
{ name: "state", type: "string" },
]
}
static propertyLayout: PropertyLayout = [
{ name: "updateMode", defaultValue: "replace", type: "enum", options: { values: ["replace", "append"] } }
]
override saveUserState = false;
private _uploadPromise: Promise<void> | null = null;
stateWidget: ITextWidget;
filenameWidget: ITextWidget;
modeWidget: IComboWidget;
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;
this.modeWidget = this.addWidget<IComboWidget>(
"combo",
"Mode",
this.properties.updateMode,
null,
{ property: "updateMode", values: ["replace", "append"] }
);
}
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
}
else if (property === "updateMode") {
this.modeWidget.value = value;
}
this.updateWidgets()
}
private updateWidgets() {
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`
}
else {
this.filenameWidget.value = `No files 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)
}
override stripUserState(o: SerializedLGraphNode) {
super.stripUserState(o);
o.properties.images = null
o.properties.index = 0
o.properties.filenames = {}
o.properties.genNumber = 0
}
private setIndex(newIndex: number, force: boolean = false) {
if (newIndex === this.properties.index && !force)
return;
if (!this.properties.images || newIndex < 0 || newIndex >= this.properties.images.images.length) {
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 promise = reuploadImageToComfyUI(data, "input")
.then((entry: GalleryOutputEntry) => {
console.debug("Gottem", entry)
if (lastGenNumber === this.properties.genNumber) {
this.properties.filenames[newIndex] = { filename: entry.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, param: any) {
if (action === "clear") {
this.setProperty("images", null)
this.setProperty("filenames", {})
this.setProperty("index", 0)
this.updateWidgets();
return
}
if (param && "images" in param) {
this.setProperty("genNumber", this.properties.genNumber + 1)
const output = param as GalleryOutput;
if (this.properties.updateMode === "append" && this.properties.images != null) {
const newImages = this.properties.images.images.concat(output.images)
this.properties.images.images = newImages
this.setProperty("images", this.properties.images)
}
else {
this.setProperty("images", param as GalleryOutput)
this.setProperty("filenames", {})
}
console.debug("[ComfyImageCacheNode] Received output!", output, this.properties.updateMode, this.properties.images)
this.setIndex(0, true)
}
this.updateWidgets();
}
}
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"
})

View File

@@ -0,0 +1,32 @@
import { LiteGraph, type SlotLayout } from "@litegraph-ts/core";
import ComfyGraphNode from "./ComfyGraphNode";
import { comfyFileToAnnotatedFilepath, isComfyBoxImageMetadata } from "$lib/utils";
export class ComfyImageToFilepath extends ComfyGraphNode {
static slotLayout: SlotLayout = {
inputs: [
{ name: "image", type: "COMFYBOX_IMAGE" },
],
outputs: [
{ name: "filepath", type: "string" },
]
}
override onExecute() {
const data = this.getInputData(0)
if (data == null || !isComfyBoxImageMetadata(data)) {
this.setOutputData(0, null)
return;
}
const path = comfyFileToAnnotatedFilepath(data.comfyUIFile);
this.setOutputData(0, path);
}
}
LiteGraph.registerNodeType({
class: ComfyImageToFilepath,
title: "Comfy.ImageToFilepath",
desc: "Converts ComfyBox image metadata to an annotated filepath like \"image.png[output]\" for use with ComfyUI.",
type: "images/file_to_filepath"
})

View File

@@ -1,10 +1,10 @@
import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, LGraph, type INodeInputSlot, type ITextWidget, type INodeOutputSlot, type SerializedLGraphNode, BuiltInSlotType, type PropertyLayout, type IComboWidget, NodeMode, type INumberWidget } from "@litegraph-ts/core";
import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, LGraph, type INodeInputSlot, type ITextWidget, type INodeOutputSlot, type SerializedLGraphNode, BuiltInSlotType, type PropertyLayout, type IComboWidget, NodeMode, type INumberWidget, type UUID } from "@litegraph-ts/core";
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
import type { SvelteComponentDev } from "svelte/internal";
import { Watch } from "@litegraph-ts/nodes-basic";
import type IComfyInputSlot from "$lib/IComfyInputSlot";
import { writable, type Unsubscriber, type Writable, get } from "svelte/store";
import { clamp, convertComfyOutputToGradio, range, type ComfyUploadImageType } from "$lib/utils"
import { clamp, convertComfyOutputToGradio, range, type ComfyUploadImageType, isComfyBoxImageMetadata, filenameToComfyBoxMetadata, type ComfyBoxImageMetadata, isComfyExecutionResult, executionResultToImageMetadata, parseWhateverIntoImageMetadata } from "$lib/utils"
import layoutState from "$lib/stores/layoutState";
import type { FileData as GradioFileData } from "@gradio/upload";
import queueState from "$lib/stores/queueState";
@@ -18,6 +18,7 @@ import CheckboxWidget from "$lib/widgets/CheckboxWidget.svelte";
import RadioWidget from "$lib/widgets/RadioWidget.svelte";
import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
import ImageEditorWidget from "$lib/widgets/ImageEditorWidget.svelte";
import type { NodeID } from "$lib/api";
export type AutoConfigOptions = {
includeProperties?: Set<string> | null,
@@ -91,7 +92,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
// TODO these are bad, create override methods instead
// input slots
inputIndex: number = 0;
inputIndex: number | null = null;
storeActionName: string | null = "store";
// output slots
@@ -193,7 +194,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
* Logic to run if this widget can be treated as output (slider, combo, text)
*/
override onExecute(param: any, options: object) {
if (this.copyFromInputLink) {
if (this.inputIndex != null) {
if (this.inputs.length >= this.inputIndex) {
const data = this.getInputData(this.inputIndex)
if (data != null) { // TODO can "null" be a legitimate value here?
@@ -201,8 +202,10 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
}
}
}
if (this.outputs.length >= this.outputIndex) {
this.setOutputData(this.outputIndex, get(this.value))
if (this.outputIndex != null) {
if (this.outputs.length >= this.outputIndex) {
this.setOutputData(this.outputIndex, get(this.value))
}
}
for (const propName in this.shownOutputProperties) {
const data = this.shownOutputProperties[propName]
@@ -265,7 +268,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
}
if (options.setWidgetTitle) {
const widget = layoutState.findLayoutForNode(this.id)
const widget = layoutState.findLayoutForNode(this.id as NodeID)
if (widget && input.name !== "") {
widget.attrs.title = input.name;
}
@@ -284,7 +287,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
}
notifyPropsChanged() {
const layoutEntry = layoutState.findLayoutEntryForNode(this.id)
const layoutEntry = layoutState.findLayoutEntryForNode(this.id as NodeID)
if (layoutEntry && layoutEntry.parent) {
layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1)
}
@@ -596,14 +599,20 @@ LiteGraph.registerNodeType({
})
/** Raw output as received from ComfyUI's backend */
export type GalleryOutput = {
images: GalleryOutputEntry[]
export interface ComfyExecutionResult {
// Technically this response can contain arbitrary data, but "images" is the
// most frequently used as it's output by LoadImage and PreviewImage, the
// only two output nodes in base ComfyUI.
images: ComfyImageLocation[] | null,
}
/** Raw output entry as received from ComfyUI's backend */
export type GalleryOutputEntry = {
export type ComfyImageLocation = {
/* Filename with extension in the subfolder. */
filename: string,
/* Subfolder in the containing folder. */
subfolder: string,
/* Base ComfyUI folder where the image is located. */
type: ComfyUploadImageType
}
@@ -612,7 +621,7 @@ export interface ComfyGalleryProperties extends ComfyWidgetProperties {
updateMode: "replace" | "append",
}
export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
export class ComfyGalleryNode extends ComfyWidgetNode<ComfyBoxImageMetadata[]> {
override properties: ComfyGalleryProperties = {
tags: [],
defaultValue: [],
@@ -623,14 +632,11 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
static slotLayout: SlotLayout = {
inputs: [
{ name: "images", type: "OUTPUT" },
{ name: "store", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } },
{ name: "clear", type: BuiltInSlotType.ACTION }
{ name: "store", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } }
],
outputs: [
{ name: "images", type: "COMFYBOX_IMAGES" },
{ name: "selected_index", type: "number" },
{ name: "width", type: "number" },
{ name: "height", type: "number" },
{ name: "filename", type: "string" },
]
}
@@ -640,7 +646,7 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
override svelteComponentType = GalleryWidget
override defaultValue = []
override copyFromInputLink = false;
override inputIndex = null;
override saveUserState = false;
override outputIndex = null;
override changedIndex = null;
@@ -663,53 +669,30 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
}
}
imageSize: Vector2 = [1, 1]
override onExecute() {
const index = this.properties.index;
this.setOutputData(0, index)
this.setOutputData(1, this.imageSize[0])
this.setOutputData(2, this.imageSize[1])
let filename: string | null = null;
if (index != null) {
const entry = get(this.value)[index];
if (entry)
filename = entry.name
}
this.setOutputData(3, filename)
this.setOutputData(0, get(this.value))
this.setOutputData(1, this.properties.index)
}
override onAction(action: any, param: any, options: { action_call?: string }) {
super.onAction(action, param, options)
if (action === "clear") {
this.setValue([])
}
}
override formatValue(value: GradioFileData[] | null): string {
override formatValue(value: ComfyBoxImageMetadata[] | null): string {
return `Images: ${value?.length || 0}`
}
override parseValue(param: any): GradioFileData[] {
if (!(typeof param === "object" && "images" in param)) {
return []
}
override parseValue(param: any): ComfyBoxImageMetadata[] {
const meta = parseWhateverIntoImageMetadata(param) || [];
const data = param as GalleryOutput
console.debug("[ComfyGalleryNode] Received output!", data)
const galleryItems: GradioFileData[] = convertComfyOutputToGradio(data)
console.debug("[ComfyGalleryNode] Received output!", param)
if (this.properties.updateMode === "append") {
const currentValue = get(this.value)
return currentValue.concat(galleryItems)
return currentValue.concat(meta)
}
else {
return galleryItems;
return meta;
}
}
@@ -890,7 +873,7 @@ export interface ComfyImageUploadProperties extends ComfyWidgetProperties {
fileCount: "single" | "multiple" // gradio File component format
}
export class ComfyImageUploadNode extends ComfyWidgetNode<GalleryOutputEntry[]> {
export class ComfyImageUploadNode extends ComfyWidgetNode<ComfyBoxImageMetadata[]> {
override properties: ComfyImageUploadProperties = {
defaultValue: [],
tags: [],
@@ -902,72 +885,27 @@ export class ComfyImageUploadNode extends ComfyWidgetNode<GalleryOutputEntry[]>
{ name: "store", type: BuiltInSlotType.ACTION }
],
outputs: [
{ name: "filename", type: "string" }, // TODO support batches
{ name: "width", type: "number" },
{ name: "height", type: "number" },
{ name: "image_count", type: "number" },
{ name: "images", type: "COMFYBOX_IMAGES" }, // TODO support batches
{ name: "changed", type: BuiltInSlotType.EVENT },
]
}
override svelteComponentType = ImageUploadWidget;
override defaultValue = [];
override outputIndex = null;
override changedIndex = 4;
override outputIndex = 0;
override changedIndex = 1;
override storeActionName = "store";
override saveUserState = false;
imageSize: Vector2 = [0, 0];
constructor(name?: string) {
super(name, [])
}
override onExecute(param: any, options: object) {
super.onExecute(param, options);
const value = get(this.value)
if (value.length > 0) {
this.setOutputData(0, value[0].filename) // TODO when ComfyUI LoadImage supports loading an image batch
this.setOutputData(1, this.imageSize[0])
this.setOutputData(2, this.imageSize[1])
this.setOutputData(3, value.length)
}
else {
this.setOutputData(0, "")
this.setOutputData(1, 0)
this.setOutputData(2, 0)
this.setOutputData(3, 0)
}
override parseValue(value: any): ComfyBoxImageMetadata[] {
return parseWhateverIntoImageMetadata(value) || []
}
override parseValue(value: any): GalleryOutputEntry[] {
if (value == null)
return []
const isComfyImageSpec = (value: any): boolean => {
return value && typeof value === "object" && "filename" in value && "type" in value
}
if (typeof value === "string") {
// Single filename
return [{ filename: value, subfolder: "", type: "input" }]
}
else if (isComfyImageSpec(value)) {
// Single ComfyUI file
return [value]
}
else if (Array.isArray(value)) {
if (value.every(v => typeof v === "string"))
return value.map(filename => { return { filename, subfolder: "", type: "input" } })
else if (value.every(isComfyImageSpec))
return value
}
return []
}
override formatValue(value: GalleryOutputEntry[]): string {
override formatValue(value: ComfyImageLocation[]): string {
return `Images: ${value?.length || 0}`
}
}
@@ -979,13 +917,13 @@ LiteGraph.registerNodeType({
type: "ui/image_upload"
})
export type FileNameOrGalleryData = string | GalleryOutputEntry;
export type FileNameOrGalleryData = string | ComfyImageLocation;
export type MultiImageData = FileNameOrGalleryData[];
export interface ComfyImageEditorNodeProperties extends ComfyWidgetProperties {
}
export class ComfyImageEditorNode extends ComfyWidgetNode<GalleryOutputEntry[]> {
export class ComfyImageEditorNode extends ComfyWidgetNode<ComfyBoxImageMetadata[]> {
override properties: ComfyImageEditorNodeProperties = {
defaultValue: [],
tags: [],
@@ -996,18 +934,16 @@ export class ComfyImageEditorNode extends ComfyWidgetNode<GalleryOutputEntry[]>
{ name: "store", type: BuiltInSlotType.ACTION }
],
outputs: [
{ name: "filename", type: "string" }, // TODO support batches
{ name: "width", type: "number" },
{ name: "height", type: "number" },
{ name: "image_count", type: "number" },
{ name: "images", type: "COMFYBOX_IMAGES" }, // TODO support batches
{ name: "changed", type: BuiltInSlotType.EVENT },
]
}
override svelteComponentType = ImageEditorWidget;
override defaultValue: GalleryOutputEntry[] = [];
override defaultValue = [];
override outputIndex = null;
override changedIndex = 4;
override inputIndex = null;
override changedIndex = 1;
override storeActionName = "store";
override saveUserState = false;
@@ -1015,50 +951,8 @@ export class ComfyImageEditorNode extends ComfyWidgetNode<GalleryOutputEntry[]>
super(name, [])
}
imageSize: Vector2 = [0, 0];
override onExecute(param: any, options: object) {
super.onExecute(param, options);
const value = get(this.value)
if (value.length > 0) {
this.setOutputData(0, value[0].filename) // TODO when ComfyUI LoadImage supports loading an image batch
this.setOutputData(1, this.imageSize[0])
this.setOutputData(2, this.imageSize[1])
this.setOutputData(3, value.length)
}
else {
this.setOutputData(0, "")
this.setOutputData(1, 0)
this.setOutputData(2, 0)
this.setOutputData(3, 0)
}
}
override parseValue(value: any): GalleryOutputEntry[] {
if (value == null)
return []
const isComfyImageSpec = (value: any): boolean => {
return value && typeof value === "object" && "filename" in value && "type" in value
}
if (typeof value === "string") {
// Single filename
return [{ filename: value, subfolder: "", type: "input" }]
}
else if (isComfyImageSpec(value)) {
// Single ComfyUI file
return [value]
}
else if (Array.isArray(value)) {
if (value.every(v => typeof v === "string"))
return value.map(filename => { return { filename, subfolder: "", type: "input" } })
else if (value.every(isComfyImageSpec))
return value
}
return []
override parseValue(value: any): ComfyBoxImageMetadata[] {
return parseWhateverIntoImageMetadata(value) || [];
}
override formatValue(value: GradioFileData[]): string {