From c3ab3aa69afac96e89f23f16e672605cd133a368 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Sun, 21 May 2023 11:10:10 -0500 Subject: [PATCH] Start vanilla workflow conversion, better PNG parser based on catbox userscript code --- src/lib/api.ts | 12 +- src/lib/components/ComfyApp.ts | 54 ++++-- src/lib/components/ComfyQueue.svelte | 7 +- src/lib/components/ComfyWorkflowsView.svelte | 4 +- src/lib/components/PromptDisplay.svelte | 7 +- src/lib/convertVanillaWorkflow.ts | 51 ++++++ src/lib/pnginfo.ts | 183 ++++++++++++++----- src/lib/stores/layoutStates.ts | 22 ++- tsconfig.json | 1 + 9 files changed, 267 insertions(+), 74 deletions(-) create mode 100644 src/lib/convertVanillaWorkflow.ts diff --git a/src/lib/api.ts b/src/lib/api.ts index 3d0a960..3c1f413 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,4 +1,4 @@ -import type { Progress, SerializedPrompt, SerializedPromptInputsForNode, SerializedPromptInputsAll, SerializedPromptOutputs } from "./components/ComfyApp"; +import type { Progress, SerializedPrompt, SerializedPromptInputsForNode, SerializedPromptInputsAll, SerializedPromptOutputs, SerializedAppState } from "./components/ComfyApp"; import type TypedEmitter from "typed-emitter"; import EventEmitter from "events"; import type { ComfyImageLocation } from "$lib/utils"; @@ -57,10 +57,14 @@ export type ComfyAPIHistoryResponse = { error?: string } +export type SerializedComfyBoxPromptData = { + subgraphs: string[] +} + export type ComfyPromptPNGInfo = { - workflow: SerializedLGraph, - comfyBoxLayout: SerializedLayoutState, - comfyBoxSubgraphs: string[], + workflow?: SerializedLGraph, // ComfyUI format + comfyBoxWorkflow: SerializedAppState, + comfyBoxPrompt: SerializedComfyBoxPromptData, } export type ComfyBoxPromptExtraData = ComfyUIPromptExtraData & { diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index a713d31..173cfe4 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -1,7 +1,7 @@ import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType, type INodeInputSlot, type NodeID, type NodeTypeSpec, type NodeTypeOpts, type SlotIndex, type UUID } from "@litegraph-ts/core"; import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core"; import ComfyAPI, { type ComfyAPIStatusResponse, type ComfyBoxPromptExtraData, type ComfyPromptRequest, type ComfyNodeID, type PromptID } from "$lib/api" -import { getPngMetadata, importA1111 } from "$lib/pnginfo"; +import { getPngMetadata, importA1111, parsePNGMetadata } from "$lib/pnginfo"; import EventEmitter from "events"; import type TypedEmitter from "typed-emitter"; @@ -44,6 +44,7 @@ import selectionState from "$lib/stores/selectionState"; import layoutStates from "$lib/stores/layoutStates"; import { ComfyWorkflow, type WorkflowAttributes, type WorkflowInstID } from "$lib/stores/workflowState"; import workflowState from "$lib/stores/workflowState"; +import convertVanillaWorkflow from "$lib/convertVanillaWorkflow"; export const COMFYBOX_SERIAL_VERSION = 1; @@ -72,8 +73,10 @@ export type A1111PromptAndInfo = { * Represents a single workflow that can be loaded into the program from JSON. */ export type SerializedAppState = { - /** Program identifier, should always be "ComfyBox" */ - createdBy: "ComfyBox", + /** For easy structural typing use */ + comfyBoxWorkflow: true, + /** Program identifier, should be something like "ComfyBox" or "ComfyUI" */ + createdBy: string, /** Serial version, should be incremented on breaking changes */ version: number, /** Commit hash if found */ @@ -138,6 +141,14 @@ type CanvasState = { canvas: ComfyGraphCanvas, } +function isComfyBoxWorkflow(data: any): data is SerializedAppState { + return data != null && (typeof data === "object") && data.comfyBoxWorkflow; +} + +function isVanillaWorkflow(data: any): data is SerializedLGraph { + return data != null && (typeof data === "object") && data.last_node_id != null; +} + export default class ComfyApp { api: ComfyAPI; @@ -237,6 +248,7 @@ export default class ComfyApp { const canvas = this.lCanvas.serialize(); return { + comfyBoxWorkflow: true, createdBy: "ComfyBox", version: COMFYBOX_SERIAL_VERSION, commitHash: __GIT_COMMIT_HASH__, @@ -399,7 +411,7 @@ export default class ComfyApp { } catch (error) { } } - if (workflow && workflow.createdBy === "ComfyBox") { + if (workflow && typeof workflow.createdBy === "string") { this.openWorkflow(workflow); } else { @@ -537,6 +549,13 @@ export default class ComfyApp { return workflow; } + async openVanillaWorkflow(data: SerializedLGraph) { + const converted = convertVanillaWorkflow(data) + console.info("WORKFLWO", converted) + notify("Converted ComfyUI workflow to ComfyBox format.", { type: "info" }) + // await this.openWorkflow(JSON.parse(pngInfo.workflow)); + } + setActiveWorkflow(id: WorkflowInstID) { const index = get(workflowState).openedWorkflows.findIndex(w => w.id === id) if (index === -1) @@ -695,7 +714,7 @@ export default class ComfyApp { } const p = this.graphToPrompt(workflow, tag); - const l = workflow.layout.serialize(); + const wf = this.serialize(workflow) console.debug(graphToGraphVis(workflow.graph)) console.debug(promptToGraphVis(p)) @@ -704,9 +723,10 @@ export default class ComfyApp { const extraData: ComfyBoxPromptExtraData = { extra_pnginfo: { - workflow: p.workflow, - comfyBoxLayout: l, - comfyBoxSubgraphs: [tag], + comfyBoxWorkflow: wf, + comfyBoxPrompt: { + subgraphs: [tag] + } }, thumbnails } @@ -761,10 +781,14 @@ export default class ComfyApp { */ async handleFile(file: File) { if (file.type === "image/png") { - const pngInfo = await getPngMetadata(file); + const buffer = await file.arrayBuffer(); + const pngInfo = await parsePNGMetadata(buffer); if (pngInfo) { - if (pngInfo.comfyBoxConfig) { - await this.openWorkflow(JSON.parse(pngInfo.comfyBoxConfig)); + if (pngInfo.comfyBoxWorkflow) { + await this.openWorkflow(JSON.parse(pngInfo.comfyBoxWorkflow)); + } else if (pngInfo.workflow) { + const workflow = JSON.parse(pngInfo.workflow); + await this.openVanillaWorkflow(workflow); } else if (pngInfo.parameters) { const parsed = parseA1111(pngInfo.parameters) if ("error" in parsed) { @@ -787,7 +811,13 @@ export default class ComfyApp { } else if (file.type === "application/json" || file.name.endsWith(".json")) { const reader = new FileReader(); reader.onload = async () => { - await this.openWorkflow(JSON.parse(reader.result as string)); + const result = JSON.parse(reader.result as string) + if (isComfyBoxWorkflow(result)) { + await this.openWorkflow(result); + } + else if (isVanillaWorkflow(result)) { + await this.openVanillaWorkflow(result); + } }; reader.readAsText(file); } diff --git a/src/lib/components/ComfyQueue.svelte b/src/lib/components/ComfyQueue.svelte index bdf0b2b..60315c9 100644 --- a/src/lib/components/ComfyQueue.svelte +++ b/src/lib/components/ComfyQueue.svelte @@ -69,7 +69,7 @@ dateStr = formatDate(date); } - const subgraphs: string[] | null = entry.extraData?.extra_pnginfo?.comfyBoxSubgraphs; + const subgraphs: string[] | null = entry.extraData?.extra_pnginfo?.comfyBoxPrompt?.subgraphs; let message = "Prompt"; if (entry.workflowID != null) { @@ -208,7 +208,7 @@
- +
{#if _entries.length > 0} {#each _entries as entry} @@ -305,8 +305,9 @@