From 4d90623505b8f6a4a17c03dbd31e439595753842 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Sat, 13 May 2023 13:28:34 -0500 Subject: [PATCH] Refactor image upload widget --- src/lib/components/ImageUpload.svelte | 50 +++++++++++----------- src/lib/nodes/ComfyWidgetNodes.ts | 32 ++++++++++---- src/lib/utils.ts | 24 ++++++++++- src/lib/widgets/ImageEditorWidget.svelte | 53 ++++++++++++++++++++++-- src/lib/widgets/ImageUploadWidget.svelte | 32 +++++++------- 5 files changed, 133 insertions(+), 58 deletions(-) diff --git a/src/lib/components/ImageUpload.svelte b/src/lib/components/ImageUpload.svelte index f1667da..2fd505c 100644 --- a/src/lib/components/ImageUpload.svelte +++ b/src/lib/components/ImageUpload.svelte @@ -7,10 +7,10 @@ import UploadText from "$lib/components/gradio/app/UploadText.svelte"; import { tick } from "svelte"; import notify from "$lib/notify"; - import type { ComfyUploadImageAPIResponse } from "$lib/utils"; + import { convertComfyOutputToComfyURL, type ComfyUploadImageAPIResponse, converGradioFileDataToComfyURL } from "$lib/utils"; + import type { GalleryOutputEntry, MultiImageData } from "$lib/nodes/ComfyWidgetNodes"; - export let value: GradioFileData[] | null = null; - export let isMobile: boolean = false; + export let value: GalleryOutputEntry[] | null = null; export let imgWidth: number = 0; export let imgHeight: number = 0; export let imgElem: HTMLImageElement | null = null @@ -21,19 +21,23 @@ // let propsChanged: Writable | null = null; let dragging = false; let pending_upload = false; - let old_value: Array | null = null; + let old_value: GradioFileData[] | null = null; let _value: GradioFileData[] | null = null; const root = "comf" const root_url = "https//ComfyUI!" const dispatch = createEventDispatcher<{ - change: GradioFileData[]; + change: GalleryOutputEntry[]; upload: undefined; clear: undefined; }>(); - $: _value = normalise_file(value, root, root_url); + if (value) { + _value = null + if (imgElem) + imgElem.src = convertComfyOutputToComfyURL(value[0]) + } $: if (!(_value && _value.length > 0 && imgElem)) { imgWidth = 1 @@ -41,7 +45,6 @@ } function onChange() { - value = _value || [] dispatch("change", value) } @@ -55,7 +58,7 @@ interface GradioUploadResponse { error?: string; - files?: Array; + files?: Array; } async function upload_files(root: string, files: Array): Promise { @@ -85,7 +88,12 @@ } else { // bare filename of image - files.push((r as ComfyUploadImageAPIResponse).name) + const resp = r as ComfyUploadImageAPIResponse; + files.push({ + filename: resp.name, + subfolder: "", + type: "input" + }) } } @@ -133,21 +141,12 @@ } pending_upload = false; - _value.forEach( - (file_data: GradioFileData, i: number) => { - if (response.files) { - file_data.orig_name = file_data.name; - file_data.name = response.files[i]; - file_data.is_file = true; - } - } - ); if (response.error) { notify(response.error, { type: "error" }) } - value = normalise_file(_value, root, root_url) as GradioFileData[]; + value = response.files; onChange(); onUpload(); }); @@ -164,14 +163,13 @@ function handle_clear(_e: CustomEvent) { _value = null; + value = []; onChange(); onClear(); } - function getImageUrl(image: GradioFileData) { - const baseUrl = `http://${location.hostname}:8188` // TODO make configurable - const params = new URLSearchParams({ filename: image.name, subfolder: "", type: "input" }) - return `${baseUrl}/view?${params}` + function convertGradioUpload(e: CustomEvent) { + _value = e.detail } @@ -194,7 +192,7 @@ {#if _value && _value.length > 0 && !pending_upload} {@const firstImage = _value[0]} - {firstImage.orig_name} (value = detail)} + on:change={convertGradioUpload} on:load={handle_upload} bind:dragging on:clear on:select parse_to_data_url={false} - > + > {/if} diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts index f9eae2f..a461ab4 100644 --- a/src/lib/nodes/ComfyWidgetNodes.ts +++ b/src/lib/nodes/ComfyWidgetNodes.ts @@ -890,7 +890,7 @@ export interface ComfyImageUploadProperties extends ComfyWidgetProperties { fileCount: "single" | "multiple" // gradio File component format } -export class ComfyImageUploadNode extends ComfyWidgetNode> { +export class ComfyImageUploadNode extends ComfyWidgetNode { override properties: ComfyImageUploadProperties = { defaultValue: [], tags: [], @@ -923,24 +923,38 @@ export class ComfyImageUploadNode extends ComfyWidgetNode> super(name, []) } - override parseValue(value: any): GradioFileData[] { + override parseValue(value: any): GalleryOutputEntry[] { if (value == null) return [] - if (typeof value === "string" && value !== "") { // Single filename - return [{ name: value, data: value, orig_name: value, is_file: true }] + const isComfyImageSpec = (value: any): boolean => { + return value && typeof value === "object" && "filename" in value && "type" in value } - else { - return [] + + 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 onExecute(param: any, options: object) { super.onExecute(param, options); const value = get(this.value) - if (value.length > 0 && value[0].name) { - this.setOutputData(0, value[0].name) // TODO when ComfyUI LoadImage supports loading an image batch + 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) @@ -953,7 +967,7 @@ export class ComfyImageUploadNode extends ComfyWidgetNode> } } - override formatValue(value: GradioFileData[]): string { + override formatValue(value: GalleryOutputEntry[]): string { return `Images: ${value?.length || 0}` } } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b2fbb82..6e06d7c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -6,7 +6,7 @@ import { get } from "svelte/store" import layoutState from "$lib/stores/layoutState" import type { SvelteComponentDev } from "svelte/internal"; import type { SerializedLGraph } from "@litegraph-ts/core"; -import type { GalleryOutput, GalleryOutputEntry } from "./nodes/ComfyWidgetNodes"; +import type { FileNameOrGalleryData, GalleryOutput, GalleryOutputEntry } from "./nodes/ComfyWidgetNodes"; import type { FileData as GradioFileData } from "@gradio/upload"; export function clamp(n: number, min: number, max: number): number { @@ -139,12 +139,32 @@ export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileDat }); } -export function convertComfyOutputToComfyURL(output: GalleryOutputEntry): string { +export function convertComfyOutputToComfyURL(output: FileNameOrGalleryData): string { + if (typeof output === "string") + return output; + const params = new URLSearchParams(output) const url = `http://${location.hostname}:8188` // TODO make configurable return url + "/view?" + params } +export function converGradioFileDataToComfyURL(image: GradioFileData, type: "input" | "output" | "temp" = "input"): string { + const baseUrl = `http://${location.hostname}:8188` // TODO make configurable + const params = new URLSearchParams({ filename: image.name, subfolder: "", type }) + return `${baseUrl}/view?${params}` +} + +export function convertGradioFileDataToComfyOutput(fileData: GradioFileData, type: "input" | "output" | "temp" = "input"): GalleryOutputEntry { + if (!fileData.is_file) + throw "Can't convert blob data to comfy output!" + + return { + filename: fileData.name, + subfolder: "", + type + } +} + export function convertFilenameToComfyURL(filename: string, subfolder: string = "", type: "input" | "output" | "temp" = "output"): string { diff --git a/src/lib/widgets/ImageEditorWidget.svelte b/src/lib/widgets/ImageEditorWidget.svelte index 0cdc2bd..88a6652 100644 --- a/src/lib/widgets/ImageEditorWidget.svelte +++ b/src/lib/widgets/ImageEditorWidget.svelte @@ -4,10 +4,14 @@ import { get, type Writable, writable } from "svelte/store"; import Modal from "$lib/components/Modal.svelte"; import { Button } from "@gradio/button"; - import type { ComfyImageEditorNode, MultiImageData } from "$lib/nodes/ComfyWidgetNodes"; + import type { ComfyImageEditorNode, GalleryOutputEntry, MultiImageData } from "$lib/nodes/ComfyWidgetNodes"; import { Embed as Klecks, KL, KlApp, klHistory, type KlAppOptionsEmbed } from "klecks"; + import type { FileData as GradioFileData } from "@gradio/upload"; import "klecks/style/style.scss"; + import ImageUpload from "$lib/components/ImageUpload.svelte"; + import { uploadImageToComfyUI, type ComfyUploadImageAPIResponse } from "$lib/utils"; + import notify from "$lib/notify"; export let widget: WidgetLayout | null = null; export let isMobile: boolean = false; @@ -17,6 +21,10 @@ let leftUrl: string = "" let rightUrl: string = "" + let imgElem: HTMLImageElement | null = null + let imgWidth: number = 0; + let imgHeight: number = 0; + $: widget && setNodeValue(widget); function setNodeValue(widget: WidgetLayout) { @@ -65,8 +73,32 @@ showModal = false; } - function submitKlecksToComfyUI(onSuccess: () => void, onError: () => void) { - const data = kl.getPNG(); + const FILENAME: string = "ComfyUITemp.png"; + const SUBFOLDER: string = "ComfyBox_Editor"; + + async function submitKlecksToComfyUI(onSuccess: () => void, onError: () => void) { + const blob = kl.getPNG(); + + const formData = new FormData(); + formData.append("image", blob, FILENAME); + + const entry: GalleryOutputEntry = { + filename: FILENAME, + subfolder: SUBFOLDER, + type: "input" + } + + await uploadImageToComfyUI(entry) + .then((resp: ComfyUploadImageAPIResponse) => { + entry.filename = resp.name; + $nodeValue = [entry] + onSuccess(); + }) + .catch(err => { + notify(`Failed to upload image from editor: ${err}`, { type: "error", timeout: 10000 }) + $nodeValue = [] + onError(); + }) } function generateBlankImage(fill: string = "#fff"): HTMLCanvasElement { @@ -110,6 +142,10 @@ kl.klApp?.out("yo"); }, 1000); } + + function onUploadChanged(e: CustomEvent) { + + }
@@ -123,6 +159,17 @@
+ Image editor.