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 @@
+
+
+
+
+