temp journey view

This commit is contained in:
space-nuko
2023-06-02 13:00:17 -05:00
parent 26ab7989c8
commit e3f98374f0
6 changed files with 195 additions and 33 deletions

View File

@@ -34,6 +34,8 @@
"svelte-check": "^3.2.0", "svelte-check": "^3.2.0",
"svelte-dnd-action": "^0.9.22", "svelte-dnd-action": "^0.9.22",
"typescript": "^5.0.3", "typescript": "^5.0.3",
"@types/cytoscape": "^3.19.9",
"@types/dompurify": "^3.0.2",
"vite": "^4.3.8", "vite": "^4.3.8",
"vite-plugin-glsl": "^1.1.2", "vite-plugin-glsl": "^1.1.2",
"vite-plugin-static-copy": "^0.14.0", "vite-plugin-static-copy": "^0.14.0",
@@ -74,13 +76,14 @@
"@litegraph-ts/tsconfig": "workspace:*", "@litegraph-ts/tsconfig": "workspace:*",
"@sveltejs/vite-plugin-svelte": "^2.1.1", "@sveltejs/vite-plugin-svelte": "^2.1.1",
"@tsconfig/svelte": "^4.0.1", "@tsconfig/svelte": "^4.0.1",
"@types/dompurify": "^3.0.2",
"canvas-to-svg": "^1.0.3", "canvas-to-svg": "^1.0.3",
"cm6-theme-basic-dark": "^0.2.0", "cm6-theme-basic-dark": "^0.2.0",
"cm6-theme-basic-light": "^0.2.0", "cm6-theme-basic-light": "^0.2.0",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"csv": "^6.3.0", "csv": "^6.3.0",
"csv-parse": "^5.3.10", "csv-parse": "^5.3.10",
"cytoscape": "^3.25.0",
"cytoscape-dagre": "^2.5.0",
"dompurify": "^3.0.3", "dompurify": "^3.0.3",
"events": "^3.3.0", "events": "^3.3.0",
"framework7": "^8.0.3", "framework7": "^8.0.3",

57
pnpm-lock.yaml generated
View File

@@ -97,9 +97,6 @@ importers:
'@tsconfig/svelte': '@tsconfig/svelte':
specifier: ^4.0.1 specifier: ^4.0.1
version: 4.0.1 version: 4.0.1
'@types/dompurify':
specifier: ^3.0.2
version: 3.0.2
canvas-to-svg: canvas-to-svg:
specifier: ^1.0.3 specifier: ^1.0.3
version: 1.0.3 version: 1.0.3
@@ -118,6 +115,12 @@ importers:
csv-parse: csv-parse:
specifier: ^5.3.10 specifier: ^5.3.10
version: 5.3.10 version: 5.3.10
cytoscape:
specifier: ^3.25.0
version: 3.25.0
cytoscape-dagre:
specifier: ^2.5.0
version: 2.5.0(cytoscape@3.25.0)
dompurify: dompurify:
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.3 version: 3.0.3
@@ -188,6 +191,12 @@ importers:
'@floating-ui/dom': '@floating-ui/dom':
specifier: ^1.2.8 specifier: ^1.2.8
version: 1.2.8 version: 1.2.8
'@types/cytoscape':
specifier: ^3.19.9
version: 3.19.9
'@types/dompurify':
specifier: ^3.0.2
version: 3.0.2
'@zerodevx/svelte-toast': '@zerodevx/svelte-toast':
specifier: ^0.9.3 specifier: ^0.9.3
version: 0.9.3(svelte@3.59.1) version: 0.9.3(svelte@3.59.1)
@@ -2368,6 +2377,10 @@ packages:
resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==} resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==}
dev: true dev: true
/@types/cytoscape@3.19.9:
resolution: {integrity: sha512-oqCx0ZGiBO0UESbjgq052vjDAy2X53lZpMrWqiweMpvVwKw/2IiYDdzPFK6+f4tMfdv9YKEM9raO5bAZc3UYBg==}
dev: true
/@types/d3-dsv@3.0.0: /@types/d3-dsv@3.0.0:
resolution: {integrity: sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==} resolution: {integrity: sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==}
@@ -2395,7 +2408,7 @@ packages:
resolution: {integrity: sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==} resolution: {integrity: sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==}
dependencies: dependencies:
'@types/trusted-types': 2.0.3 '@types/trusted-types': 2.0.3
dev: false dev: true
/@types/estree@1.0.1: /@types/estree@1.0.1:
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
@@ -2482,7 +2495,7 @@ packages:
/@types/trusted-types@2.0.3: /@types/trusted-types@2.0.3:
resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==} resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==}
dev: false dev: true
/@types/uuid@9.0.1: /@types/uuid@9.0.1:
resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==} resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==}
@@ -3324,6 +3337,23 @@ packages:
stream-transform: 3.2.6 stream-transform: 3.2.6
dev: false dev: false
/cytoscape-dagre@2.5.0(cytoscape@3.25.0):
resolution: {integrity: sha512-VG2Knemmshop4kh5fpLO27rYcyUaaDkRw+6PiX4bstpB+QFt0p2oauMrsjVbUamGWQ6YNavh7x2em2uZlzV44g==}
peerDependencies:
cytoscape: ^3.2.22
dependencies:
cytoscape: 3.25.0
dagre: 0.8.5
dev: false
/cytoscape@3.25.0:
resolution: {integrity: sha512-7MW3Iz57mCUo6JQCho6CmPBCbTlJr7LzyEtIkutG255HLVd4XuBg2I9BkTZLI/e4HoaOB/BiAzXuQybQ95+r9Q==}
engines: {node: '>=0.10'}
dependencies:
heap: 0.2.7
lodash: 4.17.21
dev: false
/d3-array@3.2.2: /d3-array@3.2.2:
resolution: {integrity: sha512-yEEyEAbDrF8C6Ob2myOBLjwBLck1Z89jMGFee0oPsn95GqjerpaOA4ch+vc2l0FNFFwMD5N7OCSEN5eAlsUbgQ==} resolution: {integrity: sha512-yEEyEAbDrF8C6Ob2myOBLjwBLck1Z89jMGFee0oPsn95GqjerpaOA4ch+vc2l0FNFFwMD5N7OCSEN5eAlsUbgQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -3474,6 +3504,13 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: false dev: false
/dagre@0.8.5:
resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==}
dependencies:
graphlib: 2.1.8
lodash: 4.17.21
dev: false
/dashdash@1.14.1: /dashdash@1.14.1:
resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
@@ -4465,6 +4502,12 @@ packages:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
dev: true dev: true
/graphlib@2.1.8:
resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==}
dependencies:
lodash: 4.17.21
dev: false
/happy-dom@9.18.3: /happy-dom@9.18.3:
resolution: {integrity: sha512-b7iMGYeIXvUryNultA0AHEVU0FPpb2djJ/xSVlMDfP7HG4z7FomdqkCEpWtSv1zDL+t1gRUoBbpqFCoUBvjYtg==} resolution: {integrity: sha512-b7iMGYeIXvUryNultA0AHEVU0FPpb2djJ/xSVlMDfP7HG4z7FomdqkCEpWtSv1zDL+t1gRUoBbpqFCoUBvjYtg==}
dependencies: dependencies:
@@ -4511,6 +4554,10 @@ packages:
dependencies: dependencies:
function-bind: 1.1.1 function-bind: 1.1.1
/heap@0.2.7:
resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==}
dev: false
/htm@3.1.1: /htm@3.1.1:
resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==} resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==}
dev: false dev: false

View File

@@ -0,0 +1,57 @@
<!--
A "journey" is like browser history for prompts, except organized in a
tree-like graph. It lets you save incremental changes to your workflow and
jump between past and present sets of parameters.
-->
<script lang="ts">
import Graph from './graph/Graph.svelte'
import GraphNode from './graph/GraphNode.svelte'
import GraphEdge from './graph/GraphEdge.svelte'
import type ComfyApp from './ComfyApp';
export let app: ComfyApp;
const nodes = [
{ id: 'N1', label: 'Start' },
{ id: 'N2', label: '4' },
{ id: 'N4', label: '8' },
{ id: 'N5', label: '15' },
{ id: 'N3', label: '16' },
{ id: 'N6', label: '23' },
{ id: 'N7', label: '42' },
{ id: 'N8', label: 'End' }
]
const edges = [
{ id: 'E1', source: 'N1', target: 'N2' },
{ id: 'E2', source: 'N2', target: 'N3' },
{ id: 'E3', source: 'N3', target: 'N6' },
{ id: 'E4', source: 'N2', target: 'N4' },
{ id: 'E5', source: 'N4', target: 'N5' },
{ id: 'E6', source: 'N5', target: 'N4', label: '2' },
{ id: 'E7', source: 'N5', target: 'N6' },
{ id: 'E8', source: 'N6', target: 'N7' },
{ id: 'E9', source: 'N7', target: 'N7', label: '3' },
{ id: 'E10', source: 'N7', target: 'N8' }
]
</script>
<div class="journey-view">
<Graph>
{#each nodes as node}
<GraphNode node={node}/>
{/each}
{#each edges as edge}
<GraphEdge edge={edge}/>
{/each}
</Graph>
</div>
<style lang="scss">
.journey-view {
width: 100%;
height: 100%;
}
</style>

View File

@@ -1,5 +1,5 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
export type ComfyPaneMode = "none" | "activeWorkflow" | "graph" | "properties" | "templates" | "queue" export type ComfyPaneMode = "none" | "activeWorkflow" | "graph" | "properties" | "templates" | "queue" | "journey"
</script> </script>
<script lang="ts"> <script lang="ts">
@@ -8,15 +8,17 @@
*/ */
import workflowState from "$lib/stores/workflowState"; import workflowState from "$lib/stores/workflowState";
import type ComfyApp from "./ComfyApp"; import type ComfyApp from "./ComfyApp";
import { Sliders2, BoxSeam, LayoutTextSidebarReverse } from "svelte-bootstrap-icons"; import { SvelteComponent } from "svelte";
import { capitalize } from "$lib/utils";
import { Sliders2, BoxSeam, LayoutTextSidebarReverse, Signpost2 } from "svelte-bootstrap-icons";
import ComfyBoxWorkflowView from "./ComfyBoxWorkflowView.svelte"; import ComfyBoxWorkflowView from "./ComfyBoxWorkflowView.svelte";
import ComfyGraphView from "./ComfyGraphView.svelte"; import ComfyGraphView from "./ComfyGraphView.svelte";
import ComfyProperties from "./ComfyProperties.svelte"; import ComfyProperties from "./ComfyProperties.svelte";
import ComfyQueue from "./ComfyQueue.svelte"; import ComfyQueue from "./ComfyQueue.svelte";
import ComfyTemplates from "./ComfyTemplates.svelte"; import ComfyTemplates from "./ComfyTemplates.svelte";
import { SvelteComponent } from "svelte"; import ComfyJourneyView from "./ComfyJourneyView.svelte";
import { capitalize } from "$lib/utils";
export let app: ComfyApp export let app: ComfyApp
export let mode: ComfyPaneMode = "none"; export let mode: ComfyPaneMode = "none";
@@ -25,6 +27,7 @@
const MODES: [ComfyPaneMode, typeof SvelteComponent][] = [ const MODES: [ComfyPaneMode, typeof SvelteComponent][] = [
["properties", Sliders2], ["properties", Sliders2],
["templates", BoxSeam], ["templates", BoxSeam],
["journey", Signpost2],
["queue", LayoutTextSidebarReverse] ["queue", LayoutTextSidebarReverse]
] ]
@@ -46,8 +49,10 @@
<ComfyTemplates {app} /> <ComfyTemplates {app} />
{:else if mode === "queue"} {:else if mode === "queue"}
<ComfyQueue {app} /> <ComfyQueue {app} />
{:else if mode === "journey"}
<ComfyJourneyView {app} />
{:else} {:else}
<div class="blank-panel">(Blank)</div> <div class="blank-panel">(Blank: {mode})</div>
{/if} {/if}
</div> </div>
{#if showSwitcher} {#if showSwitcher}

View File

@@ -13,7 +13,7 @@
import Textbox from "@gradio/form/src/Textbox.svelte"; import Textbox from "@gradio/form/src/Textbox.svelte";
import type { ModalData } from "$lib/stores/modalState"; import type { ModalData } from "$lib/stores/modalState";
import { writable, type Writable } from "svelte/store"; import { writable, type Writable } from "svelte/store";
import { negmod } from "$lib/utils"; import { negmod } from "$lib/utils";
const DOMPurify = createDOMPurify(window); const DOMPurify = createDOMPurify(window);
export let templateAndSvg: SerializedComfyBoxTemplate; export let templateAndSvg: SerializedComfyBoxTemplate;

View File

@@ -7,7 +7,18 @@ import type { ComfyBoxWorkflow } from "./stores/workflowState";
import { isSerializedPromptInputLink } from "./utils"; import { isSerializedPromptInputLink } from "./utils";
import ComfyBoxStdPromptSerializer from "./ComfyBoxStdPromptSerializer"; import ComfyBoxStdPromptSerializer from "./ComfyBoxStdPromptSerializer";
interface RestoreParamSource { export type RestoreParamType = "workflow" | "backend" | "stdPrompt";
/*
* Data of a parameter that can be restored. Paired with a parameter name.
*/
export interface RestoreParamSource<T extends RestoreParamType = any> {
type: T,
/*
* The actual value to copy to the widget after all conversions have been
* applied.
*/
finalValue: any finalValue: any
} }
@@ -16,7 +27,9 @@ interface RestoreParamSource {
* *exactly* to a node with the same ID in the current workflow. Easiest case * *exactly* to a node with the same ID in the current workflow. Easiest case
* since the parameter value can just be copied without much fuss. * since the parameter value can just be copied without much fuss.
*/ */
interface RestoreParamSourceWorkflowNode extends RestoreParamSource { export interface RestoreParamSourceWorkflowNode extends RestoreParamSource<"workflow"> {
type: "workflow",
sourceNode: SerializedComfyWidgetNode sourceNode: SerializedComfyWidgetNode
} }
@@ -31,7 +44,9 @@ interface RestoreParamSourceWorkflowNode extends RestoreParamSource {
* prompt endpoint. Hence this parameter source won't account for those kinds of * prompt endpoint. Hence this parameter source won't account for those kinds of
* values.) * values.)
*/ */
interface RestoreParamSourceBackendNodeInput extends RestoreParamSource { export interface RestoreParamSourceBackendNodeInput extends RestoreParamSource<"backend"> {
type: "backend",
backendNode: SerializedComfyWidgetNode, backendNode: SerializedComfyWidgetNode,
/* /*
@@ -45,7 +60,9 @@ interface RestoreParamSourceBackendNodeInput extends RestoreParamSource {
/* /*
* A value contained in the standard prompt extracted from the saved workflow. * A value contained in the standard prompt extracted from the saved workflow.
*/ */
interface RestoreParamSourceStdPrompt<T, K extends keyof T> extends RestoreParamSource { export interface RestoreParamSourceStdPrompt<T, K extends keyof T> extends RestoreParamSource<"stdPrompt"> {
type: "stdPrompt",
/* /*
* Name of the group containing the value to pass * Name of the group containing the value to pass
* *
@@ -128,35 +145,48 @@ function findUpstreamSerializedWidgetNode(prompt: SerializedPrompt, input: INode
return [null, null]; return [null, null];
} }
export default function restoreParameters(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets { const addSource = (result: RestoreParamTargets, targetNode: ComfyWidgetNode, source: RestoreParamSource) => {
const result = {} result[targetNode.id] ||= { targetNode, sources: [] }
result[targetNode.id].sources.push(source);
}
const addSource = (targetNode: ComfyWidgetNode, source: RestoreParamSource) => { const mergeSources = (a: RestoreParamTargets, b: RestoreParamTargets) => {
result[targetNode.id] ||= { targetNode, sources: [] } for (const [k, vs] of Object.entries(b)) {
result[targetNode.id].sources.push(source); a[vs.targetNode.id] ||= { targetNode: vs.targetNode, sources: [] }
for (const source of vs.sources) {
a[vs.targetNode.id].sources.push(source);
}
} }
}
export function getWorkflowRestoreParams(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets {
const result = {}
const graph = workflow.graph; const graph = workflow.graph;
// Step 1: Find nodes that correspond to *this* workflow exactly, since we
// can easily match up the nodes between each (their IDs will be the same)
for (const serNode of prompt.workflow.nodes) { for (const serNode of prompt.workflow.nodes) {
const foundNode = graph.getNodeByIdRecursive(serNode.id); const foundNode = graph.getNodeByIdRecursive(serNode.id);
if (isComfyWidgetNode(foundNode) && foundNode.type === serNode.type) { if (isComfyWidgetNode(foundNode) && foundNode.type === serNode.type) {
const finalValue = (serNode as SerializedComfyWidgetNode).comfyValue; const finalValue = (serNode as SerializedComfyWidgetNode).comfyValue;
if (finalValue != null) { if (finalValue != null) {
const source: RestoreParamSourceWorkflowNode = { const source: RestoreParamSourceWorkflowNode = {
type: "workflow",
finalValue, finalValue,
sourceNode: serNode sourceNode: serNode
} }
addSource(foundNode, source) addSource(result, foundNode, source)
} }
} }
} }
// Step 2: Figure out what parameters the backend received. If there was a return result
// widget node attached to a backend node's input upstream, then we can }
// use that value.
export function getBackendRestoreParams(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets {
const result = {}
const graph = workflow.graph;
for (const [serNodeID, inputs] of Object.entries(prompt.output)) { for (const [serNodeID, inputs] of Object.entries(prompt.output)) {
const serNode = prompt.workflow.nodes.find(sn => sn.id === serNodeID) const serNode = prompt.workflow.nodes.find(sn => sn.id === serNodeID)
if (serNode == null) if (serNode == null)
@@ -176,27 +206,47 @@ export default function restoreParameters(workflow: ComfyBoxWorkflow, prompt: Se
const foundNode = graph.getNodeByIdRecursive(serNode.id); const foundNode = graph.getNodeByIdRecursive(serNode.id);
if (isComfyWidgetNode(foundNode) && foundNode.type === serNode.type) { if (isComfyWidgetNode(foundNode) && foundNode.type === serNode.type) {
const source: RestoreParamSourceBackendNodeInput = { const source: RestoreParamSourceBackendNodeInput = {
type: "backend",
finalValue: inputValue, finalValue: inputValue,
backendNode: serNode, backendNode: serNode,
isDirectAttachment isDirectAttachment
} }
addSource(foundNode, source) addSource(result, foundNode, source)
} }
} }
} }
} }
return result
}
export default function restoreParameters(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets {
const result = {}
// Step 1: Find nodes that correspond to *this* workflow exactly, since we
// can easily match up the nodes between each (their IDs will be the same)
const workflowParams = getWorkflowRestoreParams(workflow, prompt);
mergeSources(result, workflowParams);
// Step 2: Figure out what parameters the backend received. If there was a
// widget node attached to a backend node's input upstream, then we can
// use that value.
const backendParams = getBackendRestoreParams(workflow, prompt);
mergeSources(result, backendParams);
// Step 3: Extract the standard prompt from the workflow and use that to // Step 3: Extract the standard prompt from the workflow and use that to
// infer parameter types // infer parameter types
const serializer = new ComfyBoxStdPromptSerializer(); // TODO
const stdPrompt = serializer.serialize(prompt);
const allWidgetNodes = Array.from(graph.iterateNodesInOrderRecursive()).filter(isComfyWidgetNode); // const serializer = new ComfyBoxStdPromptSerializer();
// const stdPrompt = serializer.serialize(prompt);
for (const widgetNode of allWidgetNodes) { // const allWidgetNodes = Array.from(graph.iterateNodesInOrderRecursive()).filter(isComfyWidgetNode);
} // for (const widgetNode of allWidgetNodes) {
// }
// for (const [groupName, groups] of Object.entries(stdPrompt)) { // for (const [groupName, groups] of Object.entries(stdPrompt)) {
// } // }