TEMP refactor file passing
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import type { Progress, SerializedPrompt, SerializedPromptInputs, SerializedPromptInputsAll, SerializedPromptOutput, SerializedPromptOutputs } from "./components/ComfyApp";
|
import type { Progress, SerializedPrompt, SerializedPromptInputs, SerializedPromptInputsAll, SerializedPromptOutput, SerializedPromptOutputs } from "./components/ComfyApp";
|
||||||
import type TypedEmitter from "typed-emitter";
|
import type TypedEmitter from "typed-emitter";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import type { GalleryOutput, GalleryOutputEntry } from "./nodes/ComfyWidgetNodes";
|
import type { ComfyExecutionResult, ComfyImageLocation } from "./nodes/ComfyWidgetNodes";
|
||||||
import type { SerializedLGraph, UUID } from "@litegraph-ts/core";
|
import type { SerializedLGraph, UUID } from "@litegraph-ts/core";
|
||||||
import type { SerializedLayoutState } from "./stores/layoutState";
|
import type { SerializedLayoutState } from "./stores/layoutState";
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ export type ComfyPromptPNGInfo = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ComfyBoxPromptExtraData = ComfyUIPromptExtraData & {
|
export type ComfyBoxPromptExtraData = ComfyUIPromptExtraData & {
|
||||||
thumbnails?: GalleryOutputEntry[],
|
thumbnails?: ComfyImageLocation[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComfyUIPromptExtraData = {
|
export type ComfyUIPromptExtraData = {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { download, jsonToJsObject, promptToGraphVis, range, workflowToGraphVis }
|
|||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
import configState from "$lib/stores/configState";
|
import configState from "$lib/stores/configState";
|
||||||
import { blankGraph } from "$lib/defaultGraph";
|
import { blankGraph } from "$lib/defaultGraph";
|
||||||
import type { GalleryOutput } from "$lib/nodes/ComfyWidgetNodes";
|
import type { ComfyExecutionResult } from "$lib/nodes/ComfyWidgetNodes";
|
||||||
|
|
||||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ export type SerializedPrompt = {
|
|||||||
output: SerializedPromptInputsAll
|
output: SerializedPromptInputsAll
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedPromptOutputs = Record<NodeID, GalleryOutput>
|
export type SerializedPromptOutputs = Record<NodeID, ComfyExecutionResult>
|
||||||
|
|
||||||
export type Progress = {
|
export type Progress = {
|
||||||
value: number,
|
value: number,
|
||||||
@@ -347,7 +347,7 @@ export default class ComfyApp {
|
|||||||
this.lGraph.setDirtyCanvas(true, false);
|
this.lGraph.setDirtyCanvas(true, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.api.addEventListener("executed", (promptID: PromptID, nodeID: NodeID, output: GalleryOutput) => {
|
this.api.addEventListener("executed", (promptID: PromptID, nodeID: NodeID, output: ComfyExecutionResult) => {
|
||||||
this.nodeOutputs[nodeID] = output;
|
this.nodeOutputs[nodeID] = output;
|
||||||
const node = this.lGraph.getNodeById(nodeID) as ComfyGraphNode;
|
const node = this.lGraph.getNodeById(nodeID) as ComfyGraphNode;
|
||||||
if (node?.onExecuted) {
|
if (node?.onExecuted) {
|
||||||
@@ -409,7 +409,8 @@ export default class ComfyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setColor("IMAGE", "rebeccapurple")
|
setColor("IMAGE", "rebeccapurple")
|
||||||
setColor("COMFY_IMAGE_FILE", "chartreuse")
|
setColor("COMFYBOX_IMAGES", "lime")
|
||||||
|
setColor("COMFYBOX_IMAGE", "green")
|
||||||
setColor(BuiltInSlotType.EVENT, "lightseagreen")
|
setColor(BuiltInSlotType.EVENT, "lightseagreen")
|
||||||
setColor(BuiltInSlotType.ACTION, "lightseagreen")
|
setColor(BuiltInSlotType.ACTION, "lightseagreen")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import UploadText from "$lib/components/gradio/app/UploadText.svelte";
|
import UploadText from "$lib/components/gradio/app/UploadText.svelte";
|
||||||
import type { GalleryOutputEntry } from "$lib/nodes/ComfyWidgetNodes";
|
import type { ComfyImageLocation } from "$lib/nodes/ComfyWidgetNodes";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
import { convertComfyOutputEntryToGradio, convertComfyOutputToComfyURL, type ComfyUploadImageAPIResponse } from "$lib/utils";
|
import { convertComfyOutputEntryToGradio, convertComfyOutputToComfyURL, type ComfyUploadImageAPIResponse } from "$lib/utils";
|
||||||
import { Block, BlockLabel } from "@gradio/atoms";
|
import { Block, BlockLabel } from "@gradio/atoms";
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
import { ModifyUpload, Upload } from "@gradio/upload";
|
import { ModifyUpload, Upload } from "@gradio/upload";
|
||||||
import { createEventDispatcher, tick } from "svelte";
|
import { createEventDispatcher, tick } from "svelte";
|
||||||
|
|
||||||
export let value: GalleryOutputEntry[] | null = null;
|
export let value: ComfyImageLocation[] | null = null;
|
||||||
export let imgWidth: number = 0;
|
export let imgWidth: number = 0;
|
||||||
export let imgHeight: number = 0;
|
export let imgHeight: number = 0;
|
||||||
export let imgElem: HTMLImageElement | null = null
|
export let imgElem: HTMLImageElement | null = null
|
||||||
@@ -28,9 +28,9 @@
|
|||||||
let uploaded: boolean = false;
|
let uploaded: boolean = false;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
change: GalleryOutputEntry[];
|
change: ComfyImageLocation[];
|
||||||
uploading: undefined;
|
uploading: undefined;
|
||||||
uploaded: GalleryOutputEntry[];
|
uploaded: ComfyImageLocation[];
|
||||||
upload_error: any;
|
upload_error: any;
|
||||||
clear: undefined;
|
clear: undefined;
|
||||||
}>();
|
}>();
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
|
|
||||||
interface GradioUploadResponse {
|
interface GradioUploadResponse {
|
||||||
error?: string;
|
error?: string;
|
||||||
files?: Array<GalleryOutputEntry>;
|
files?: Array<ComfyImageLocation>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function upload_files(root: string, files: Array<File>): Promise<GradioUploadResponse> {
|
async function upload_files(root: string, files: Array<File>): Promise<GradioUploadResponse> {
|
||||||
|
|||||||
@@ -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 { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWidget, type SerializedLGraphNode, type SlotLayout, type PropertyLayout } from "@litegraph-ts/core";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
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 { NotifyOptions } from "$lib/notify";
|
||||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
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 {
|
export class ComfyQueueEvents extends ComfyGraphNode {
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
@@ -63,7 +63,7 @@ LiteGraph.registerNodeType({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export interface ComfyStoreImagesActionProperties extends ComfyGraphNodeProperties {
|
export interface ComfyStoreImagesActionProperties extends ComfyGraphNodeProperties {
|
||||||
images: GalleryOutput | null
|
images: ComfyExecutionResult | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyStoreImagesAction extends ComfyGraphNode {
|
export class ComfyStoreImagesAction extends ComfyGraphNode {
|
||||||
@@ -90,7 +90,7 @@ export class ComfyStoreImagesAction extends ComfyGraphNode {
|
|||||||
if (action !== "store" || !param || !("images" in param))
|
if (action !== "store" || !param || !("images" in param))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.setProperty("images", param as GalleryOutput)
|
this.setProperty("images", param as ComfyExecutionResult)
|
||||||
this.setOutputData(0, this.properties.images)
|
this.setOutputData(0, this.properties.images)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,7 +223,7 @@ export class ComfyNotifyAction extends ComfyGraphNode {
|
|||||||
// native notifications.
|
// native notifications.
|
||||||
if (param != null && typeof param === "object") {
|
if (param != null && typeof param === "object") {
|
||||||
if ("images" in param) {
|
if ("images" in param) {
|
||||||
const output = param as GalleryOutput;
|
const output = param as ComfyExecutionResult;
|
||||||
const converted = convertComfyOutputToGradio(output);
|
const converted = convertComfyOutputToGradio(output);
|
||||||
if (converted.length > 0)
|
if (converted.length > 0)
|
||||||
options.imageUrl = converted[0].data;
|
options.imageUrl = converted[0].data;
|
||||||
@@ -581,86 +581,6 @@ LiteGraph.registerNodeType({
|
|||||||
type: "events/no_change"
|
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 {
|
export interface ComfySetPromptThumbnailsActionProperties extends ComfyGraphNodeProperties {
|
||||||
defaultFolderType: string | null
|
defaultFolderType: string | null
|
||||||
}
|
}
|
||||||
@@ -679,12 +599,12 @@ export class ComfySetPromptThumbnailsAction extends ComfyGraphNode {
|
|||||||
|
|
||||||
_value: any = null;
|
_value: any = null;
|
||||||
|
|
||||||
override getPromptThumbnails(): GalleryOutputEntry[] | null {
|
override getPromptThumbnails(): ComfyImageLocation[] | null {
|
||||||
const data = this.getInputData(0)
|
const data = this.getInputData(0)
|
||||||
|
|
||||||
const folderType = this.properties.folderType || "input";
|
const folderType = this.properties.folderType || "input";
|
||||||
|
|
||||||
const convertString = (s: string): GalleryOutputEntry => {
|
const convertString = (s: string): ComfyImageLocation => {
|
||||||
return { filename: data, subfolder: "", type: folderType }
|
return { filename: data, subfolder: "", type: folderType }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,13 +613,13 @@ export class ComfySetPromptThumbnailsAction extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
else if (data != null && typeof data === "object") {
|
else if (data != null && typeof data === "object") {
|
||||||
if ("filename" in data && "type" in data)
|
if ("filename" in data && "type" in data)
|
||||||
return [data as GalleryOutputEntry];
|
return [data as ComfyImageLocation];
|
||||||
}
|
}
|
||||||
else if (Array.isArray(data) && data.length > 0) {
|
else if (Array.isArray(data) && data.length > 0) {
|
||||||
if (typeof data[0] === "string")
|
if (typeof data[0] === "string")
|
||||||
return data.map(convertString)
|
return data.map(convertString)
|
||||||
else if (typeof data[0] === "object" && "filename" in data[0] && "type" in data[0])
|
else if (typeof data[0] === "object" && "filename" in data[0] && "type" in data[0])
|
||||||
return data as GalleryOutputEntry[]
|
return data as ComfyImageLocation[]
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas";
|
import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas";
|
||||||
import ComfyGraphNode from "./ComfyGraphNode";
|
import ComfyGraphNode from "./ComfyGraphNode";
|
||||||
import ComfyWidgets from "$lib/widgets"
|
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 { BuiltInSlotType, type SerializedLGraphNode } from "@litegraph-ts/core";
|
||||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||||
import type { ComfyInputConfig } 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)
|
console.warn("onExecuted outputs", outputData)
|
||||||
this.triggerSlot(0, outputData)
|
this.triggerSlot(0, outputData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
|||||||
import type ComfyWidget from "$lib/components/widgets/ComfyWidget";
|
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 { 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 { 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 type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
import { get } from "svelte/store";
|
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.
|
* Triggered when the backend sends a finished output back with this node's ID.
|
||||||
* Valid for output nodes like SaveImage and PreviewImage.
|
* 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
|
* 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
|
* provide any thumbnails for use with the prompt queue. Useful for HR Fix
|
||||||
* or img2img workloads.
|
* or img2img workloads.
|
||||||
*/
|
*/
|
||||||
getPromptThumbnails?(): GalleryOutputEntry[] | null
|
getPromptThumbnails?(): ComfyImageLocation[] | null
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Allows you to manually specify an auto-config for certain input slot
|
* Allows you to manually specify an auto-config for certain input slot
|
||||||
|
|||||||
@@ -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"
|
|
||||||
})
|
|
||||||
32
src/lib/nodes/ComfyImageToFilepath.ts
Normal file
32
src/lib/nodes/ComfyImageToFilepath.ts
Normal 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"
|
||||||
|
})
|
||||||
@@ -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 ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
||||||
import type { SvelteComponentDev } from "svelte/internal";
|
import type { SvelteComponentDev } from "svelte/internal";
|
||||||
import { Watch } from "@litegraph-ts/nodes-basic";
|
import { Watch } from "@litegraph-ts/nodes-basic";
|
||||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||||
import { writable, type Unsubscriber, type Writable, get } from "svelte/store";
|
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 layoutState from "$lib/stores/layoutState";
|
||||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||||
import queueState from "$lib/stores/queueState";
|
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 RadioWidget from "$lib/widgets/RadioWidget.svelte";
|
||||||
import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
|
import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
|
||||||
import ImageEditorWidget from "$lib/widgets/ImageEditorWidget.svelte";
|
import ImageEditorWidget from "$lib/widgets/ImageEditorWidget.svelte";
|
||||||
|
import type { NodeID } from "$lib/api";
|
||||||
|
|
||||||
export type AutoConfigOptions = {
|
export type AutoConfigOptions = {
|
||||||
includeProperties?: Set<string> | null,
|
includeProperties?: Set<string> | null,
|
||||||
@@ -91,7 +92,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
|
|
||||||
// TODO these are bad, create override methods instead
|
// TODO these are bad, create override methods instead
|
||||||
// input slots
|
// input slots
|
||||||
inputIndex: number = 0;
|
inputIndex: number | null = null;
|
||||||
storeActionName: string | null = "store";
|
storeActionName: string | null = "store";
|
||||||
|
|
||||||
// output slots
|
// 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)
|
* Logic to run if this widget can be treated as output (slider, combo, text)
|
||||||
*/
|
*/
|
||||||
override onExecute(param: any, options: object) {
|
override onExecute(param: any, options: object) {
|
||||||
if (this.copyFromInputLink) {
|
if (this.inputIndex != null) {
|
||||||
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 != null) { // TODO can "null" be a legitimate value here?
|
if (data != null) { // TODO can "null" be a legitimate value here?
|
||||||
@@ -201,9 +202,11 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.outputIndex != null) {
|
||||||
if (this.outputs.length >= this.outputIndex) {
|
if (this.outputs.length >= this.outputIndex) {
|
||||||
this.setOutputData(this.outputIndex, get(this.value))
|
this.setOutputData(this.outputIndex, get(this.value))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for (const propName in this.shownOutputProperties) {
|
for (const propName in this.shownOutputProperties) {
|
||||||
const data = this.shownOutputProperties[propName]
|
const data = this.shownOutputProperties[propName]
|
||||||
this.setOutputData(data.index, this.properties[propName])
|
this.setOutputData(data.index, this.properties[propName])
|
||||||
@@ -265,7 +268,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.setWidgetTitle) {
|
if (options.setWidgetTitle) {
|
||||||
const widget = layoutState.findLayoutForNode(this.id)
|
const widget = layoutState.findLayoutForNode(this.id as NodeID)
|
||||||
if (widget && input.name !== "") {
|
if (widget && input.name !== "") {
|
||||||
widget.attrs.title = input.name;
|
widget.attrs.title = input.name;
|
||||||
}
|
}
|
||||||
@@ -284,7 +287,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notifyPropsChanged() {
|
notifyPropsChanged() {
|
||||||
const layoutEntry = layoutState.findLayoutEntryForNode(this.id)
|
const layoutEntry = layoutState.findLayoutEntryForNode(this.id as NodeID)
|
||||||
if (layoutEntry && layoutEntry.parent) {
|
if (layoutEntry && layoutEntry.parent) {
|
||||||
layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1)
|
layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1)
|
||||||
}
|
}
|
||||||
@@ -596,14 +599,20 @@ LiteGraph.registerNodeType({
|
|||||||
})
|
})
|
||||||
|
|
||||||
/** Raw output as received from ComfyUI's backend */
|
/** Raw output as received from ComfyUI's backend */
|
||||||
export type GalleryOutput = {
|
export interface ComfyExecutionResult {
|
||||||
images: GalleryOutputEntry[]
|
// 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 */
|
/** Raw output entry as received from ComfyUI's backend */
|
||||||
export type GalleryOutputEntry = {
|
export type ComfyImageLocation = {
|
||||||
|
/* Filename with extension in the subfolder. */
|
||||||
filename: string,
|
filename: string,
|
||||||
|
/* Subfolder in the containing folder. */
|
||||||
subfolder: string,
|
subfolder: string,
|
||||||
|
/* Base ComfyUI folder where the image is located. */
|
||||||
type: ComfyUploadImageType
|
type: ComfyUploadImageType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,7 +621,7 @@ export interface ComfyGalleryProperties extends ComfyWidgetProperties {
|
|||||||
updateMode: "replace" | "append",
|
updateMode: "replace" | "append",
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
export class ComfyGalleryNode extends ComfyWidgetNode<ComfyBoxImageMetadata[]> {
|
||||||
override properties: ComfyGalleryProperties = {
|
override properties: ComfyGalleryProperties = {
|
||||||
tags: [],
|
tags: [],
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
@@ -623,14 +632,11 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
inputs: [
|
inputs: [
|
||||||
{ name: "images", type: "OUTPUT" },
|
{ name: "images", type: "OUTPUT" },
|
||||||
{ name: "store", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } },
|
{ name: "store", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } }
|
||||||
{ name: "clear", type: BuiltInSlotType.ACTION }
|
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
|
{ name: "images", type: "COMFYBOX_IMAGES" },
|
||||||
{ name: "selected_index", type: "number" },
|
{ 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 svelteComponentType = GalleryWidget
|
||||||
override defaultValue = []
|
override defaultValue = []
|
||||||
override copyFromInputLink = false;
|
override inputIndex = null;
|
||||||
override saveUserState = false;
|
override saveUserState = false;
|
||||||
override outputIndex = null;
|
override outputIndex = null;
|
||||||
override changedIndex = null;
|
override changedIndex = null;
|
||||||
@@ -663,53 +669,30 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imageSize: Vector2 = [1, 1]
|
|
||||||
|
|
||||||
override onExecute() {
|
override onExecute() {
|
||||||
const index = this.properties.index;
|
this.setOutputData(0, get(this.value))
|
||||||
|
this.setOutputData(1, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override onAction(action: any, param: any, options: { action_call?: string }) {
|
override onAction(action: any, param: any, options: { action_call?: string }) {
|
||||||
super.onAction(action, param, options)
|
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}`
|
return `Images: ${value?.length || 0}`
|
||||||
}
|
}
|
||||||
|
|
||||||
override parseValue(param: any): GradioFileData[] {
|
override parseValue(param: any): ComfyBoxImageMetadata[] {
|
||||||
if (!(typeof param === "object" && "images" in param)) {
|
const meta = parseWhateverIntoImageMetadata(param) || [];
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = param as GalleryOutput
|
console.debug("[ComfyGalleryNode] Received output!", param)
|
||||||
console.debug("[ComfyGalleryNode] Received output!", data)
|
|
||||||
|
|
||||||
const galleryItems: GradioFileData[] = convertComfyOutputToGradio(data)
|
|
||||||
|
|
||||||
if (this.properties.updateMode === "append") {
|
if (this.properties.updateMode === "append") {
|
||||||
const currentValue = get(this.value)
|
const currentValue = get(this.value)
|
||||||
return currentValue.concat(galleryItems)
|
return currentValue.concat(meta)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return galleryItems;
|
return meta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -890,7 +873,7 @@ export interface ComfyImageUploadProperties extends ComfyWidgetProperties {
|
|||||||
fileCount: "single" | "multiple" // gradio File component format
|
fileCount: "single" | "multiple" // gradio File component format
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyImageUploadNode extends ComfyWidgetNode<GalleryOutputEntry[]> {
|
export class ComfyImageUploadNode extends ComfyWidgetNode<ComfyBoxImageMetadata[]> {
|
||||||
override properties: ComfyImageUploadProperties = {
|
override properties: ComfyImageUploadProperties = {
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -902,72 +885,27 @@ export class ComfyImageUploadNode extends ComfyWidgetNode<GalleryOutputEntry[]>
|
|||||||
{ name: "store", type: BuiltInSlotType.ACTION }
|
{ name: "store", type: BuiltInSlotType.ACTION }
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
{ name: "filename", type: "string" }, // TODO support batches
|
{ name: "images", type: "COMFYBOX_IMAGES" }, // TODO support batches
|
||||||
{ name: "width", type: "number" },
|
|
||||||
{ name: "height", type: "number" },
|
|
||||||
{ name: "image_count", type: "number" },
|
|
||||||
{ name: "changed", type: BuiltInSlotType.EVENT },
|
{ name: "changed", type: BuiltInSlotType.EVENT },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
override svelteComponentType = ImageUploadWidget;
|
override svelteComponentType = ImageUploadWidget;
|
||||||
override defaultValue = [];
|
override defaultValue = [];
|
||||||
override outputIndex = null;
|
override outputIndex = 0;
|
||||||
override changedIndex = 4;
|
override changedIndex = 1;
|
||||||
override storeActionName = "store";
|
override storeActionName = "store";
|
||||||
override saveUserState = false;
|
override saveUserState = false;
|
||||||
|
|
||||||
imageSize: Vector2 = [0, 0];
|
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
super(name, [])
|
super(name, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
override onExecute(param: any, options: object) {
|
override parseValue(value: any): ComfyBoxImageMetadata[] {
|
||||||
super.onExecute(param, options);
|
return parseWhateverIntoImageMetadata(value) || []
|
||||||
|
|
||||||
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[] {
|
override formatValue(value: ComfyImageLocation[]): string {
|
||||||
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 {
|
|
||||||
return `Images: ${value?.length || 0}`
|
return `Images: ${value?.length || 0}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -979,13 +917,13 @@ LiteGraph.registerNodeType({
|
|||||||
type: "ui/image_upload"
|
type: "ui/image_upload"
|
||||||
})
|
})
|
||||||
|
|
||||||
export type FileNameOrGalleryData = string | GalleryOutputEntry;
|
export type FileNameOrGalleryData = string | ComfyImageLocation;
|
||||||
export type MultiImageData = FileNameOrGalleryData[];
|
export type MultiImageData = FileNameOrGalleryData[];
|
||||||
|
|
||||||
export interface ComfyImageEditorNodeProperties extends ComfyWidgetProperties {
|
export interface ComfyImageEditorNodeProperties extends ComfyWidgetProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyImageEditorNode extends ComfyWidgetNode<GalleryOutputEntry[]> {
|
export class ComfyImageEditorNode extends ComfyWidgetNode<ComfyBoxImageMetadata[]> {
|
||||||
override properties: ComfyImageEditorNodeProperties = {
|
override properties: ComfyImageEditorNodeProperties = {
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -996,18 +934,16 @@ export class ComfyImageEditorNode extends ComfyWidgetNode<GalleryOutputEntry[]>
|
|||||||
{ name: "store", type: BuiltInSlotType.ACTION }
|
{ name: "store", type: BuiltInSlotType.ACTION }
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
{ name: "filename", type: "string" }, // TODO support batches
|
{ name: "images", type: "COMFYBOX_IMAGES" }, // TODO support batches
|
||||||
{ name: "width", type: "number" },
|
|
||||||
{ name: "height", type: "number" },
|
|
||||||
{ name: "image_count", type: "number" },
|
|
||||||
{ name: "changed", type: BuiltInSlotType.EVENT },
|
{ name: "changed", type: BuiltInSlotType.EVENT },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
override svelteComponentType = ImageEditorWidget;
|
override svelteComponentType = ImageEditorWidget;
|
||||||
override defaultValue: GalleryOutputEntry[] = [];
|
override defaultValue = [];
|
||||||
override outputIndex = null;
|
override outputIndex = null;
|
||||||
override changedIndex = 4;
|
override inputIndex = null;
|
||||||
|
override changedIndex = 1;
|
||||||
override storeActionName = "store";
|
override storeActionName = "store";
|
||||||
override saveUserState = false;
|
override saveUserState = false;
|
||||||
|
|
||||||
@@ -1015,50 +951,8 @@ export class ComfyImageEditorNode extends ComfyWidgetNode<GalleryOutputEntry[]>
|
|||||||
super(name, [])
|
super(name, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
imageSize: Vector2 = [0, 0];
|
override parseValue(value: any): ComfyBoxImageMetadata[] {
|
||||||
|
return parseWhateverIntoImageMetadata(value) || [];
|
||||||
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 formatValue(value: GradioFileData[]): string {
|
override formatValue(value: GradioFileData[]): string {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ComfyAPIHistoryEntry, ComfyAPIHistoryItem, ComfyAPIHistoryResponse, ComfyAPIQueueResponse, ComfyAPIStatusResponse, ComfyBoxPromptExtraData, NodeID, PromptID } from "$lib/api";
|
import type { ComfyAPIHistoryEntry, ComfyAPIHistoryItem, ComfyAPIHistoryResponse, ComfyAPIQueueResponse, ComfyAPIStatusResponse, ComfyBoxPromptExtraData, NodeID, PromptID } from "$lib/api";
|
||||||
import type { Progress, SerializedPromptInputsAll, SerializedPromptOutputs } from "$lib/components/ComfyApp";
|
import type { Progress, SerializedPromptInputsAll, SerializedPromptOutputs } from "$lib/components/ComfyApp";
|
||||||
import type { GalleryOutput } from "$lib/nodes/ComfyWidgetNodes";
|
import type { ComfyExecutionResult } from "$lib/nodes/ComfyWidgetNodes";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
import { get, writable, type Writable } from "svelte/store";
|
import { get, writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ type QueueStateOps = {
|
|||||||
executionError: (promptID: PromptID, message: string) => void,
|
executionError: (promptID: PromptID, message: string) => void,
|
||||||
progressUpdated: (progress: Progress) => void
|
progressUpdated: (progress: Progress) => void
|
||||||
afterQueued: (promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) => void
|
afterQueued: (promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) => void
|
||||||
onExecuted: (promptID: PromptID, nodeID: NodeID, output: GalleryOutput) => void
|
onExecuted: (promptID: PromptID, nodeID: NodeID, output: ComfyExecutionResult) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QueueEntry = {
|
export type QueueEntry = {
|
||||||
@@ -257,7 +257,7 @@ function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromp
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function onExecuted(promptID: PromptID, nodeID: NodeID, output: GalleryOutput) {
|
function onExecuted(promptID: PromptID, nodeID: NodeID, output: ComfyExecutionResult) {
|
||||||
console.debug("[queueState] onExecuted", promptID, nodeID, output)
|
console.debug("[queueState] onExecuted", promptID, nodeID, output)
|
||||||
store.update(s => {
|
store.update(s => {
|
||||||
const [index, entry, queue] = findEntryInPending(promptID)
|
const [index, entry, queue] = findEntryInPending(promptID)
|
||||||
|
|||||||
109
src/lib/utils.ts
109
src/lib/utils.ts
@@ -6,7 +6,7 @@ import { get } from "svelte/store"
|
|||||||
import layoutState from "$lib/stores/layoutState"
|
import layoutState from "$lib/stores/layoutState"
|
||||||
import type { SvelteComponentDev } from "svelte/internal";
|
import type { SvelteComponentDev } from "svelte/internal";
|
||||||
import type { SerializedLGraph } from "@litegraph-ts/core";
|
import type { SerializedLGraph } from "@litegraph-ts/core";
|
||||||
import type { FileNameOrGalleryData, GalleryOutput, GalleryOutputEntry } from "./nodes/ComfyWidgetNodes";
|
import type { FileNameOrGalleryData, ComfyExecutionResult, ComfyImageLocation } from "./nodes/ComfyWidgetNodes";
|
||||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||||
|
|
||||||
export function clamp(n: number, min: number, max: number): number {
|
export function clamp(n: number, min: number, max: number): number {
|
||||||
@@ -125,11 +125,11 @@ export const debounce = (callback: Function, wait = 250) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileData[] {
|
export function convertComfyOutputToGradio(output: ComfyExecutionResult): GradioFileData[] {
|
||||||
return output.images.map(convertComfyOutputEntryToGradio);
|
return output.images.map(convertComfyOutputEntryToGradio);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertComfyOutputEntryToGradio(r: GalleryOutputEntry): GradioFileData {
|
export function convertComfyOutputEntryToGradio(r: ComfyImageLocation): GradioFileData {
|
||||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
const url = `http://${location.hostname}:8188` // TODO make configurable
|
||||||
const params = new URLSearchParams(r)
|
const params = new URLSearchParams(r)
|
||||||
const fileData: GradioFileData = {
|
const fileData: GradioFileData = {
|
||||||
@@ -156,7 +156,7 @@ export function convertGradioFileDataToComfyURL(image: GradioFileData, type: Com
|
|||||||
return `${baseUrl}/view?${params}`
|
return `${baseUrl}/view?${params}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertGradioFileDataToComfyOutput(fileData: GradioFileData, type: ComfyUploadImageType = "input"): GalleryOutputEntry {
|
export function convertGradioFileDataToComfyOutput(fileData: GradioFileData, type: ComfyUploadImageType = "input"): ComfyImageLocation {
|
||||||
if (!fileData.is_file)
|
if (!fileData.is_file)
|
||||||
throw "Can't convert blob data to comfy output!"
|
throw "Can't convert blob data to comfy output!"
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ export interface ComfyUploadImageAPIResponse {
|
|||||||
/*
|
/*
|
||||||
* Uploads an image into ComfyUI's `input` folder.
|
* Uploads an image into ComfyUI's `input` folder.
|
||||||
*/
|
*/
|
||||||
export async function uploadImageToComfyUI(blob: Blob, filename: string, type: ComfyUploadImageType, subfolder: string = "", overwrite: boolean = false): Promise<GalleryOutputEntry> {
|
export async function uploadImageToComfyUI(blob: Blob, filename: string, type: ComfyUploadImageType, subfolder: string = "", overwrite: boolean = false): Promise<ComfyImageLocation> {
|
||||||
console.debug("[utils] Uploading image to ComfyUI", filename, blob.size)
|
console.debug("[utils] Uploading image to ComfyUI", filename, blob.size)
|
||||||
|
|
||||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
const url = `http://${location.hostname}:8188` // TODO make configurable
|
||||||
@@ -232,19 +232,90 @@ export async function uploadImageToComfyUI(blob: Blob, filename: string, type: C
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copies an *EXISTING* image in a ComfyUI image folder into a different folder,
|
* Convenient type for passing around image filepaths and their metadata with
|
||||||
* for use with LoadImage etc.
|
* wires. Needs to be converted to a filename for use with LoadImage.
|
||||||
|
*
|
||||||
|
* Litegraph type is COMFYBOX_IMAGE. The array type is COMFYBOX_IMAGES.
|
||||||
*/
|
*/
|
||||||
export async function reuploadImageToComfyUI(data: GalleryOutputEntry, type: ComfyUploadImageType): Promise<GalleryOutputEntry> {
|
export type ComfyBoxImageMetadata = {
|
||||||
if (data.type === type)
|
/* For easy structural type detection */
|
||||||
return data
|
isComfyBoxImageMetadata: true,
|
||||||
|
/* Pointer to where this image resides in ComfyUI. */
|
||||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
comfyUIFile: ComfyImageLocation,
|
||||||
const params = new URLSearchParams(data)
|
/* Readable name of the image. */
|
||||||
|
name: string
|
||||||
console.debug("[utils] Reuploading image into to ComfyUI input folder", data)
|
/* Tags applicable to this image, like ["mask"]. */
|
||||||
|
tags: string[],
|
||||||
return fetch(url + "/view?" + params)
|
/* Image width. */
|
||||||
.then((r) => r.blob())
|
width?: number,
|
||||||
.then((blob) => uploadImageToComfyUI(blob, data.filename, type))
|
/* Image height. */
|
||||||
|
height?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isComfyBoxImageMetadata(value: any): value is ComfyBoxImageMetadata {
|
||||||
|
return value && typeof value === "object" && (value as any).isComfyBoxImageMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isComfyExecutionResult(value: any): value is ComfyExecutionResult {
|
||||||
|
return value && typeof value === "object" && Array.isArray(value.images)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filenameToComfyBoxMetadata(filename: string, type: ComfyUploadImageType, subfolder: string = ""): ComfyBoxImageMetadata {
|
||||||
|
return {
|
||||||
|
isComfyBoxImageMetadata: true,
|
||||||
|
comfyUIFile: {
|
||||||
|
filename,
|
||||||
|
subfolder,
|
||||||
|
type
|
||||||
|
},
|
||||||
|
name: "Filename",
|
||||||
|
tags: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function comfyFileToComfyBoxMetadata(comfyUIFile: ComfyImageLocation): ComfyBoxImageMetadata {
|
||||||
|
return {
|
||||||
|
isComfyBoxImageMetadata: true,
|
||||||
|
comfyUIFile,
|
||||||
|
name: "File",
|
||||||
|
tags: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converts a ComfyUI file into an annotated filepath. Backend nodes like
|
||||||
|
* LoadImage support syntax like "subfolder/image.png[output]" to specify which
|
||||||
|
* image folder to load from.
|
||||||
|
*/
|
||||||
|
export function comfyFileToAnnotatedFilepath(comfyUIFile: ComfyImageLocation): string {
|
||||||
|
let path = ""
|
||||||
|
if (comfyUIFile.subfolder != "")
|
||||||
|
path = comfyUIFile.subfolder + "/";
|
||||||
|
|
||||||
|
path += `${comfyUIFile.filename}[${comfyUIFile.type}]`
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function executionResultToImageMetadata(result: ComfyExecutionResult): ComfyBoxImageMetadata[] {
|
||||||
|
return result.images.map(comfyFileToComfyBoxMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseWhateverIntoImageMetadata(param: any): ComfyBoxImageMetadata[] | null {
|
||||||
|
let meta: ComfyBoxImageMetadata[] | null = null
|
||||||
|
|
||||||
|
if (isComfyBoxImageMetadata(param)) {
|
||||||
|
meta = [param];
|
||||||
|
}
|
||||||
|
if (Array.isArray(param) && !param.every(isComfyBoxImageMetadata)) {
|
||||||
|
meta = param
|
||||||
|
}
|
||||||
|
else if (isComfyExecutionResult(param)) {
|
||||||
|
meta = executionResultToImageMetadata(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function comfyBoxImageToComfyURL(image: ComfyBoxImageMetadata): string {
|
||||||
|
return convertComfyOutputToComfyURL(image.comfyUIFile)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,13 @@
|
|||||||
import type { ComfyGalleryNode } from "$lib/nodes/ComfyWidgetNodes";
|
import type { ComfyGalleryNode } from "$lib/nodes/ComfyWidgetNodes";
|
||||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||||
import type { SelectData as GradioSelectData } from "@gradio/utils";
|
import type { SelectData as GradioSelectData } from "@gradio/utils";
|
||||||
import { clamp } from "$lib/utils";
|
import { clamp, comfyBoxImageToComfyURL, type ComfyBoxImageMetadata } from "$lib/utils";
|
||||||
import { f7 } from "framework7-svelte";
|
import { f7 } from "framework7-svelte";
|
||||||
|
|
||||||
export let widget: WidgetLayout | null = null;
|
export let widget: WidgetLayout | null = null;
|
||||||
export let isMobile: boolean = false;
|
export let isMobile: boolean = false;
|
||||||
let node: ComfyGalleryNode | null = null;
|
let node: ComfyGalleryNode | null = null;
|
||||||
let nodeValue: Writable<GradioFileData[]> | null = null;
|
let nodeValue: Writable<ComfyBoxImageMetadata[]> | null = null;
|
||||||
let propsChanged: Writable<number> | null = null;
|
let propsChanged: Writable<number> | null = null;
|
||||||
let option: number | null = null;
|
let option: number | null = null;
|
||||||
let imageWidth: number = 1;
|
let imageWidth: number = 1;
|
||||||
@@ -154,8 +154,10 @@
|
|||||||
<div class="wrapper comfy-image-widget" style={widget.attrs.style || ""} bind:this={element}>
|
<div class="wrapper comfy-image-widget" style={widget.attrs.style || ""} bind:this={element}>
|
||||||
<Block variant="solid" padding={false}>
|
<Block variant="solid" padding={false}>
|
||||||
{#if $nodeValue && $nodeValue.length > 0}
|
{#if $nodeValue && $nodeValue.length > 0}
|
||||||
|
{@const value = $nodeValue[$nodeValue.length-1]}
|
||||||
|
{@const url = comfyBoxImageToComfyURL(value)}
|
||||||
<StaticImage
|
<StaticImage
|
||||||
value={$nodeValue[$nodeValue.length-1].data}
|
value={url}
|
||||||
show_label={widget.attrs.title != ""}
|
show_label={widget.attrs.title != ""}
|
||||||
label={widget.attrs.title}
|
label={widget.attrs.title}
|
||||||
bind:imageWidth
|
bind:imageWidth
|
||||||
@@ -167,11 +169,12 @@
|
|||||||
</Block>
|
</Block>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
{@const images = $nodeValue.map(comfyBoxImageToComfyURL)}
|
||||||
<div class="wrapper comfy-gallery-widget gradio-gallery" style={widget.attrs.style || ""} bind:this={element}>
|
<div class="wrapper comfy-gallery-widget gradio-gallery" style={widget.attrs.style || ""} bind:this={element}>
|
||||||
<Block variant="solid" padding={false}>
|
<Block variant="solid" padding={false}>
|
||||||
<div class="padding">
|
<div class="padding">
|
||||||
<Gallery
|
<Gallery
|
||||||
bind:value={$nodeValue}
|
value={images}
|
||||||
label={widget.attrs.title}
|
label={widget.attrs.title}
|
||||||
show_label={widget.attrs.title !== ""}
|
show_label={widget.attrs.title !== ""}
|
||||||
{style}
|
{style}
|
||||||
|
|||||||
@@ -4,19 +4,19 @@
|
|||||||
import { get, type Writable, writable } from "svelte/store";
|
import { get, type Writable, writable } from "svelte/store";
|
||||||
import Modal from "$lib/components/Modal.svelte";
|
import Modal from "$lib/components/Modal.svelte";
|
||||||
import { Button } from "@gradio/button";
|
import { Button } from "@gradio/button";
|
||||||
import type { ComfyImageEditorNode, GalleryOutputEntry, MultiImageData } from "$lib/nodes/ComfyWidgetNodes";
|
import type { ComfyImageEditorNode, ComfyImageLocation, MultiImageData } from "$lib/nodes/ComfyWidgetNodes";
|
||||||
import { Embed as Klecks, KL, KlApp, klHistory, type KlAppOptionsEmbed } from "klecks";
|
import { Embed as Klecks, KL, KlApp, klHistory, type KlAppOptionsEmbed } from "klecks";
|
||||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||||
|
|
||||||
import "klecks/style/style.scss";
|
import "klecks/style/style.scss";
|
||||||
import ImageUpload from "$lib/components/ImageUpload.svelte";
|
import ImageUpload from "$lib/components/ImageUpload.svelte";
|
||||||
import { uploadImageToComfyUI, type ComfyUploadImageAPIResponse, convertComfyOutputToComfyURL } from "$lib/utils";
|
import { uploadImageToComfyUI, type ComfyUploadImageAPIResponse, convertComfyOutputToComfyURL, type ComfyBoxImageMetadata, comfyFileToComfyBoxMetadata } from "$lib/utils";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
|
|
||||||
export let widget: WidgetLayout | null = null;
|
export let widget: WidgetLayout | null = null;
|
||||||
export let isMobile: boolean = false;
|
export let isMobile: boolean = false;
|
||||||
let node: ComfyImageEditorNode | null = null;
|
let node: ComfyImageEditorNode | null = null;
|
||||||
let nodeValue: Writable<GalleryOutputEntry[]> | null = null;
|
let nodeValue: Writable<ComfyBoxImageMetadata[]> | null = null;
|
||||||
let attrsChanged: Writable<number> | null = null;
|
let attrsChanged: Writable<number> | null = null;
|
||||||
|
|
||||||
let imgWidth: number = 0;
|
let imgWidth: number = 0;
|
||||||
@@ -24,14 +24,16 @@
|
|||||||
|
|
||||||
$: widget && setNodeValue(widget);
|
$: widget && setNodeValue(widget);
|
||||||
|
|
||||||
$: if (!(node && $nodeValue && $nodeValue.length > 0)) {
|
$: if ($nodeValue && $nodeValue.length > 0) {
|
||||||
node.imageSize = [0, 0]
|
// TODO improve
|
||||||
}
|
if (imgWidth > 0 && imgHeight > 0) {
|
||||||
else if (imgWidth > 0 && imgHeight > 0) {
|
$nodeValue[0].width = imgWidth
|
||||||
node.imageSize = [imgWidth, imgHeight]
|
$nodeValue[0].height = imgHeight
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
node.imageSize = [0, 0]
|
$nodeValue[0].width = 0
|
||||||
|
$nodeValue[0].height = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNodeValue(widget: WidgetLayout) {
|
function setNodeValue(widget: WidgetLayout) {
|
||||||
@@ -125,8 +127,9 @@
|
|||||||
const blob = kl.getPNG();
|
const blob = kl.getPNG();
|
||||||
|
|
||||||
await uploadImageToComfyUI(blob, FILENAME, "input")
|
await uploadImageToComfyUI(blob, FILENAME, "input")
|
||||||
.then((entry: GalleryOutputEntry) => {
|
.then((entry: ComfyImageLocation) => {
|
||||||
$nodeValue = [entry] // TODO more than one image
|
const meta: ComfyBoxImageMetadata = comfyFileToComfyBoxMetadata(entry);
|
||||||
|
$nodeValue = [meta] // TODO more than one image
|
||||||
notify("Saved image to ComfyUI!", { type: "success" })
|
notify("Saved image to ComfyUI!", { type: "success" })
|
||||||
onSuccess();
|
onSuccess();
|
||||||
})
|
})
|
||||||
@@ -191,7 +194,7 @@
|
|||||||
status = "uploading"
|
status = "uploading"
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUploaded(e: CustomEvent<GalleryOutputEntry[]>) {
|
function onUploaded(e: CustomEvent<ComfyImageLocation[]>) {
|
||||||
uploadError = null;
|
uploadError = null;
|
||||||
status = "uploaded"
|
status = "uploaded"
|
||||||
$nodeValue = e.detail;
|
$nodeValue = e.detail;
|
||||||
@@ -207,7 +210,7 @@
|
|||||||
uploadError = e.detail
|
uploadError = e.detail
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChange(e: CustomEvent<GalleryOutputEntry[]>) {
|
function onChange(e: CustomEvent<ComfyImageLocation[]>) {
|
||||||
// $nodeValue = e.detail;
|
// $nodeValue = e.detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,27 +2,30 @@
|
|||||||
import ImageUpload from "$lib/components/ImageUpload.svelte"
|
import ImageUpload from "$lib/components/ImageUpload.svelte"
|
||||||
import type { WidgetLayout } from "$lib/stores/layoutState";
|
import type { WidgetLayout } from "$lib/stores/layoutState";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { ComfyGalleryNode, ComfyImageUploadNode, GalleryOutputEntry, MultiImageData } from "$lib/nodes/ComfyWidgetNodes";
|
import type { ComfyGalleryNode, ComfyImageUploadNode, ComfyImageLocation, MultiImageData } from "$lib/nodes/ComfyWidgetNodes";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
|
import { comfyFileToComfyBoxMetadata, type ComfyBoxImageMetadata } from "$lib/utils";
|
||||||
|
|
||||||
export let widget: WidgetLayout | null = null;
|
export let widget: WidgetLayout | null = null;
|
||||||
export let isMobile: boolean = false;
|
export let isMobile: boolean = false;
|
||||||
let node: ComfyImageUploadNode | null = null;
|
let node: ComfyImageUploadNode | null = null;
|
||||||
let nodeValue: Writable<GalleryOutputEntry[]> | null = null;
|
let nodeValue: Writable<ComfyBoxImageMetadata[]> | null = null;
|
||||||
let propsChanged: Writable<number> | null = null;
|
let propsChanged: Writable<number> | null = null;
|
||||||
let imgWidth: number = 1;
|
let imgWidth: number = 1;
|
||||||
let imgHeight: number = 1;
|
let imgHeight: number = 1;
|
||||||
|
|
||||||
$: widget && setNodeValue(widget);
|
$: widget && setNodeValue(widget);
|
||||||
|
|
||||||
$: if (!(node && $nodeValue && $nodeValue.length > 0)) {
|
$: if ($nodeValue && $nodeValue.length > 0) {
|
||||||
node.imageSize = [0, 0]
|
// TODO improve
|
||||||
}
|
if (imgWidth > 0 && imgHeight > 0) {
|
||||||
else if (imgWidth > 0 && imgHeight > 0) {
|
$nodeValue[0].width = imgWidth
|
||||||
node.imageSize = [imgWidth, imgHeight]
|
$nodeValue[0].height = imgHeight
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
node.imageSize = [0, 0]
|
$nodeValue[0].width = 0
|
||||||
|
$nodeValue[0].height = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNodeValue(widget: WidgetLayout) {
|
function setNodeValue(widget: WidgetLayout) {
|
||||||
@@ -38,14 +41,14 @@
|
|||||||
$nodeValue = []
|
$nodeValue = []
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChange(e: CustomEvent<GalleryOutputEntry[]>) {
|
function onChange(e: CustomEvent<ComfyImageLocation[]>) {
|
||||||
console.warn("ONCHANGE!!!", e.detail)
|
console.warn("ONCHANGE!!!", e.detail)
|
||||||
$nodeValue = e.detail || []
|
$nodeValue = (e.detail || []).map(comfyFileToComfyBoxMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUploaded(e: CustomEvent<GalleryOutputEntry[]>) {
|
function onUploaded(e: CustomEvent<ComfyImageLocation[]>) {
|
||||||
console.warn("ONUPLOADED!!!", e.detail)
|
console.warn("ONUPLOADED!!!", e.detail)
|
||||||
$nodeValue = e.detail || []
|
$nodeValue = (e.detail || []).map(comfyFileToComfyBoxMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUploadError(e: CustomEvent<any>) {
|
function onUploadError(e: CustomEvent<any>) {
|
||||||
@@ -54,7 +57,7 @@
|
|||||||
$nodeValue = []
|
$nodeValue = []
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClear(e: CustomEvent<GalleryOutputEntry[]>) {
|
function onClear(e: CustomEvent<ComfyImageLocation[]>) {
|
||||||
console.warn("ONCLEAR!!!", e.detail)
|
console.warn("ONCLEAR!!!", e.detail)
|
||||||
$nodeValue = []
|
$nodeValue = []
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user