From bc4128f07aa29fc50b00ba57c6e16ced6fd34e77 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Thu, 11 May 2023 00:14:25 -0500 Subject: [PATCH] Image compare widget --- package.json | 1 + pnpm-lock.yaml | 7 ++ src/lib/api.ts | 6 ++ src/lib/components/ComfyApp.ts | 14 ++++ src/lib/components/ImageComparison.svelte | 32 ++++++++ src/lib/nodes/ComfyWidgetNodes.ts | 90 +++++++++++++++++++++++ src/lib/utils.ts | 13 +++- src/lib/widgets/ImageCompareWidget.svelte | 80 ++++++++++++++++++++ 8 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 src/lib/components/ImageComparison.svelte create mode 100644 src/lib/widgets/ImageCompareWidget.svelte diff --git a/package.json b/package.json index dda1c0a..53e65a9 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "events": "^3.3.0", "framework7": "^8.0.3", "framework7-svelte": "^8.0.3", + "img-comparison-slider": "^8.0.0", "pollen-css": "^4.6.2", "radix-icons-svelte": "^1.2.1", "svelte-preprocess": "^5.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90abd9f..a1b4366 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,6 +79,9 @@ importers: framework7-svelte: specifier: ^8.0.3 version: 8.0.3 + img-comparison-slider: + specifier: ^8.0.0 + version: 8.0.0 pollen-css: specifier: ^4.6.2 version: 4.6.2 @@ -4189,6 +4192,10 @@ packages: engines: {node: '>= 4'} dev: true + /img-comparison-slider@8.0.0: + resolution: {integrity: sha512-ZOKkdN/+W/U/2LFEwrZuxRVbIwQK1GyEKhTETfsy55/bmBoNfM81MnQsc1j81Q50dkwTKjuecicsnp3O7lBRqQ==} + dev: false + /immutable@4.3.0: resolution: {integrity: sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==} diff --git a/src/lib/api.ts b/src/lib/api.ts index 159d455..fa7fb35 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -119,6 +119,12 @@ export default class ComfyAPI extends EventTarget { case "executed": this.dispatchEvent(new CustomEvent("executed", { detail: msg.data })); break; + case "execution_cached": + this.dispatchEvent(new CustomEvent("execution_cached", { detail: msg.data })); + break; + case "execution_error": + this.dispatchEvent(new CustomEvent("execution_error", { detail: msg.data })); + break; default: if (this.registered.has(msg.type)) { this.dispatchEvent(new CustomEvent(msg.type, { detail: msg.data })); diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 93b8673..7d309d5 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -352,6 +352,15 @@ export default class ComfyApp { } }); + this.api.addEventListener("execution_cached", ({ detail }: CustomEvent) => { + // TODO detail.nodes + }); + + this.api.addEventListener("execution_error", ({ detail }: CustomEvent) => { + queueState.update(s => { s.progress = null; s.runningNodeId = null; return s; }) + notify(`Execution error: ${detail.message}`, { type: "error", timeout: 10000 }) + }); + this.api.init(); } @@ -447,6 +456,11 @@ export default class ComfyApp { state = structuredClone(blankGraph) } await this.deserialize(state) + uiState.update(s => { + s.uiUnlocked = true; + s.uiEditMode = "widgets"; + return s; + }) } /** diff --git a/src/lib/components/ImageComparison.svelte b/src/lib/components/ImageComparison.svelte new file mode 100644 index 0000000..8124a91 --- /dev/null +++ b/src/lib/components/ImageComparison.svelte @@ -0,0 +1,32 @@ + + + + + + + diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts index d7dea9d..b64928e 100644 --- a/src/lib/nodes/ComfyWidgetNodes.ts +++ b/src/lib/nodes/ComfyWidgetNodes.ts @@ -17,6 +17,7 @@ import ButtonWidget from "$lib/widgets/ButtonWidget.svelte"; import CheckboxWidget from "$lib/widgets/CheckboxWidget.svelte"; import RadioWidget from "$lib/widgets/RadioWidget.svelte"; import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte"; +import ImageCompareWidget from "$lib/widgets/ImageCompareWidget.svelte"; export type AutoConfigOptions = { includeProperties?: Set | null, @@ -963,3 +964,92 @@ LiteGraph.registerNodeType({ desc: "Widget that lets you upload images into ComfyUI's input folder", type: "ui/image_upload" }) + +export interface ComfyImageCompareNodeProperties extends ComfyWidgetProperties { +} + +export type FileNameOrGalleryData = string | GalleryOutputEntry; +export type ImageCompareData = [FileNameOrGalleryData, FileNameOrGalleryData] + +export class ComfyImageCompareNode extends ComfyWidgetNode { + override properties: ComfyImageCompareNodeProperties = { + defaultValue: [], + tags: [], + } + + static slotLayout: SlotLayout = { + inputs: [ + { name: "store", type: BuiltInSlotType.ACTION }, + { name: "left_image", type: "string" }, + { name: "right_image", type: "string" }, + ], + outputs: [ + ] + } + + override svelteComponentType = ImageCompareWidget; + override defaultValue: ImageCompareData = ["", ""]; + override outputIndex = null; + override changedIndex = 3; + override storeActionName = "store"; + override saveUserState = false; + + constructor(name?: string) { + super(name, ["", ""]) + } + + override onExecute() { + const valueA = this.getInputData(1) + const valueB = this.getInputData(2) + let current = get(this.value) + let changed = false; + if (valueA != null && current[0] != valueA) { + current[0] = valueA + changed = true; + } + if (valueB != null && current[1] != valueB) { + current[1] = valueB + changed = true; + } + if (changed) + this.value.set(current) + } + + override parseValue(value: any): ImageCompareData { + if (value == null) { + return ["", ""] + } + else if (typeof value === "string" && value !== "") { // Single filename + const prevValue = get(this.value) + prevValue.push(value) + if (prevValue.length > 2) + prevValue.splice(0, 1) + return prevValue as ImageCompareData + } + else if (typeof value === "object" && "images" in value && value.images.length > 0) { + const output = value as GalleryOutput + const prevValue = get(this.value) + prevValue.push(output.images[0].filename) + if (prevValue.length > 2) + prevValue.splice(0, 1) + return prevValue as ImageCompareData + } + else if (Array.isArray(value) && typeof value[0] === "string" && typeof value[1] === "string") { + return value as ImageCompareData + } + else { + return ["", ""] + } + } + + override formatValue(value: GradioFileData[]): string { + return `Images: ${value?.length || 0}` + } +} + +LiteGraph.registerNodeType({ + class: ComfyImageCompareNode, + title: "UI.ImageCompare", + desc: "Widget that lets you compare two images", + type: "ui/image_compare" +}) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 335a305..62e72a0 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -123,7 +123,6 @@ export const debounce = (callback: Function, wait = 250) => { export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileData[] { return output.images.map(r => { - // TODO configure backend URL const url = `http://${location.hostname}:8188` // TODO make configurable const params = new URLSearchParams(r) const fileData: GradioFileData = { @@ -136,6 +135,18 @@ export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileDat }); } +export function convertFilenameToComfyURL(filename: string, + subfolder: string = "", + type: "input" | "output" | "temp" = "output"): string { + const params = new URLSearchParams({ + filename, + subfolder, + type + }) + const url = `http://${location.hostname}:8188` // TODO make configurable + return url + "/view?" + params +} + export function jsonToJsObject(json: string): string { // Try to parse, to see if it's real JSON JSON.parse(json); diff --git a/src/lib/widgets/ImageCompareWidget.svelte b/src/lib/widgets/ImageCompareWidget.svelte new file mode 100644 index 0000000..049748e --- /dev/null +++ b/src/lib/widgets/ImageCompareWidget.svelte @@ -0,0 +1,80 @@ + + +
+ + + + + {#if leftUrl && leftUrl != ""} + {@const props = { slot: "first" }} + Left + {/if} + {#if rightUrl && leftUrl != ""} + {@const props = { slot: "second" }} + Right + {/if} + + + + +
+ +