Bind widget values to special widget nodes
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import { download } from "$lib/utils"
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import type { LGraph } from "@litegraph-ts/core";
|
||||
import nodeState, { type WidgetUIState, type WidgetUIStateStore, type NodeStateStore, type NodeUIState, type NodeUIStateStore } from "./stores/nodeState";
|
||||
import type { LGraph, LGraphNode } from "@litegraph-ts/core";
|
||||
import type ComfyApp from "./components/ComfyApp";
|
||||
import type { Unsubscriber } from "svelte/store";
|
||||
import type { Unsubscriber, Writable } from "svelte/store";
|
||||
import type { ComfyWidgetNode } from "./nodes";
|
||||
|
||||
type WidgetSubStore = {
|
||||
store: WidgetUIStateStore,
|
||||
unsubscribe: Unsubscriber
|
||||
}
|
||||
|
||||
type NodeSubStore = {
|
||||
store: NodeUIStateStore,
|
||||
unsubscribe: Unsubscriber
|
||||
}
|
||||
|
||||
/*
|
||||
* Responsible for watching for and synchronizing state changes from the
|
||||
* frontend to the litegraph instance.
|
||||
@@ -29,68 +24,55 @@ type NodeSubStore = {
|
||||
*/
|
||||
export default class GraphSync {
|
||||
graph: LGraph;
|
||||
private _unsubscribe: Unsubscriber;
|
||||
private _finalizer: FinalizationRegistry<number>;
|
||||
|
||||
// nodeId -> widgetSubStores[]
|
||||
private stores: Record<string, WidgetSubStore[]> = {}
|
||||
// nodeId -> widgetSubStore
|
||||
private stores: Record<string, WidgetSubStore> = {}
|
||||
|
||||
constructor(app: ComfyApp) {
|
||||
this.graph = app.lGraph;
|
||||
this._unsubscribe = nodeState.subscribe(this.onAllNodeStateChanged.bind(this));
|
||||
this._finalizer = new FinalizationRegistry((id: number) => {
|
||||
console.log(`${this} has been garbage collected`);
|
||||
this._unsubscribe();
|
||||
});
|
||||
app.eventBus.on("nodeAdded", this.onNodeAdded.bind(this));
|
||||
app.eventBus.on("nodeRemoved", this.onNodeRemoved.bind(this));
|
||||
}
|
||||
|
||||
private onAllNodeStateChanged(state: NodeStateStore) {
|
||||
private onNodeAdded(node: LGraphNode) {
|
||||
// TODO assumes only a single graph's widget state.
|
||||
|
||||
for (let nodeId in state) {
|
||||
state[nodeId].node.title = state[nodeId].name;
|
||||
if (!this.stores[nodeId]) {
|
||||
this.addStores(state[nodeId].widgetStates, nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
for (let nodeId in this.stores) {
|
||||
if (!state[nodeId]) {
|
||||
this.removeStores(nodeId);
|
||||
}
|
||||
if ("svelteComponentType" in node) {
|
||||
this.addStore(node as ComfyWidgetNode);
|
||||
}
|
||||
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
|
||||
private addStores(widgetStates: WidgetUIState[], nodeId: string) {
|
||||
if (this.stores[nodeId]) {
|
||||
console.warn("Stores already exist!", nodeId, this.stores[nodeId])
|
||||
private onNodeRemoved(node: LGraphNode) {
|
||||
if ("svelteComponentType" in node) {
|
||||
this.removeStore(node as ComfyWidgetNode);
|
||||
}
|
||||
|
||||
this.stores[nodeId] = []
|
||||
|
||||
for (const wuis of widgetStates) {
|
||||
const unsub = wuis.value.subscribe((v) => this.onWidgetStateChanged(wuis, v))
|
||||
this.stores[nodeId].push({ store: wuis.value, unsubscribe: unsub });
|
||||
}
|
||||
|
||||
console.debug("NEWSTORES", this.stores[nodeId])
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
|
||||
private removeStores(nodeId: string) {
|
||||
console.debug("DELSTORES", this.stores[nodeId])
|
||||
for (const ss of this.stores[nodeId]) {
|
||||
ss.unsubscribe();
|
||||
private addStore(node: ComfyWidgetNode) {
|
||||
if (this.stores[node.id]) {
|
||||
console.warn("[GraphSync] Stores already exist!", node.id, this.stores[node.id])
|
||||
}
|
||||
delete this.stores[nodeId]
|
||||
|
||||
const unsub = node.value.subscribe((v) => this.onWidgetStateChanged(node, v))
|
||||
this.stores[node.id] = ({ store: node.value, unsubscribe: unsub });
|
||||
|
||||
console.debug("[GraphSync] NEWSTORE", this.stores[node.id])
|
||||
}
|
||||
|
||||
private removeStore(node: ComfyWidgetNode) {
|
||||
console.debug("[GraphSync] DELSTORE", this.stores[node.id])
|
||||
this.stores[node.id].unsubscribe()
|
||||
delete this.stores[node.id]
|
||||
}
|
||||
|
||||
/*
|
||||
* Fired when a single widget's value changes.
|
||||
*/
|
||||
private onWidgetStateChanged(wuis: WidgetUIState, value: any) {
|
||||
wuis.widget.value = value;
|
||||
private onWidgetStateChanged(node: ComfyWidgetNode, value: any) {
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import ComfyUIPane from "./ComfyUIPane.svelte";
|
||||
import ComfyApp, { type SerializedAppState } from "./ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
@@ -105,11 +104,6 @@
|
||||
onMount(async () => {
|
||||
app = new ComfyApp();
|
||||
|
||||
app.eventBus.on("nodeAdded", nodeState.nodeAdded);
|
||||
app.eventBus.on("nodeRemoved", nodeState.nodeRemoved);
|
||||
app.eventBus.on("configured", nodeState.configureFinished);
|
||||
app.eventBus.on("cleared", nodeState.clear);
|
||||
|
||||
app.eventBus.on("nodeAdded", layoutState.nodeAdded);
|
||||
app.eventBus.on("nodeRemoved", layoutState.nodeRemoved);
|
||||
app.eventBus.on("configured", layoutState.configureFinished);
|
||||
|
||||
@@ -433,7 +433,7 @@ export default class ComfyApp {
|
||||
this.clean();
|
||||
|
||||
if (!graphData) {
|
||||
// graphData = structuredClone(defaultGraph.workflow)
|
||||
graphData = structuredClone(defaultGraph.workflow)
|
||||
}
|
||||
|
||||
// Patch T2IAdapterLoader to ControlNetLoader since they are the same node now
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import ComfyApp from "./ComfyApp";
|
||||
import type { SerializedPanes } from "./ComfyApp"
|
||||
import WidgetContainer from "./WidgetContainer.svelte";
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import layoutState, { type ContainerLayout, type DragItem } from "$lib/stores/layoutState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import nodeState, { type WidgetUIState } from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
|
||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||
import { startDrag, stopDrag, getComponentForWidgetState } from "$lib/utils"
|
||||
import { startDrag, stopDrag } from "$lib/utils"
|
||||
import BlockContainer from "./BlockContainer.svelte"
|
||||
|
||||
export let dragItem: IDragItem | null = null;
|
||||
@@ -12,14 +11,12 @@
|
||||
export let classes: string[] = [];
|
||||
let container: ContainerLayout | null = null;
|
||||
let widget: WidgetLayout | null = null;
|
||||
let widgetState: WidgetUIState | null = null;
|
||||
let showHandles: boolean = false;
|
||||
|
||||
$: if (!dragItem || !$layoutState.allItems[dragItem.id]) {
|
||||
dragItem = null;
|
||||
container = null;
|
||||
widget = null;
|
||||
widgetState = null;
|
||||
}
|
||||
else if (dragItem.type === "container") {
|
||||
container = dragItem as ContainerLayout;
|
||||
@@ -48,7 +45,7 @@
|
||||
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(widget.id)}
|
||||
class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId == widget.node.id}
|
||||
>
|
||||
<svelte:component this={widget.node.svelteComponentType} node={widget.node} />
|
||||
<svelte:component this={widget.node.svelteComponentType} {widget} />
|
||||
</div>
|
||||
{#if showHandles}
|
||||
<div class="handle handle-widget" style="z-index: {zIndex+100}" data-drag-item-id={widget.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||
|
||||
@@ -4,398 +4,16 @@ const defaultGraph: SerializedAppState = {
|
||||
createdBy: "ComfyBox",
|
||||
version: 1,
|
||||
workflow: {
|
||||
last_node_id: 9,
|
||||
last_link_id: 9,
|
||||
nodes: [
|
||||
{
|
||||
id: 7,
|
||||
type: "CLIPTextEncode",
|
||||
pos: [
|
||||
413,
|
||||
389
|
||||
],
|
||||
size: [
|
||||
425.27801513671875,
|
||||
180.6060791015625
|
||||
],
|
||||
flags: {},
|
||||
order: 3,
|
||||
mode: 0,
|
||||
inputs: [
|
||||
{
|
||||
name: "clip",
|
||||
type: "CLIP",
|
||||
link: 5
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
name: "CONDITIONING",
|
||||
type: "CONDITIONING",
|
||||
links: [
|
||||
6
|
||||
],
|
||||
slot_index: 0
|
||||
}
|
||||
],
|
||||
title: "CLIPTextEncode",
|
||||
properties: {},
|
||||
widgets_values: [
|
||||
"bad hands"
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
type: "CLIPTextEncode",
|
||||
pos: [
|
||||
415,
|
||||
186
|
||||
],
|
||||
size: [
|
||||
422.84503173828125,
|
||||
164.31304931640625
|
||||
],
|
||||
flags: {},
|
||||
order: 2,
|
||||
mode: 0,
|
||||
inputs: [
|
||||
{
|
||||
name: "clip",
|
||||
type: "CLIP",
|
||||
link: 3
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
name: "CONDITIONING",
|
||||
type: "CONDITIONING",
|
||||
links: [
|
||||
4
|
||||
],
|
||||
slot_index: 0
|
||||
}
|
||||
],
|
||||
title: "CLIPTextEncode",
|
||||
properties: {},
|
||||
widgets_values: [
|
||||
"masterpiece best quality girl"
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: "EmptyLatentImage",
|
||||
pos: [
|
||||
473,
|
||||
609
|
||||
],
|
||||
size: [
|
||||
315,
|
||||
106
|
||||
],
|
||||
flags: {},
|
||||
order: 0,
|
||||
mode: 0,
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
name: "LATENT",
|
||||
type: "LATENT",
|
||||
links: [
|
||||
2
|
||||
],
|
||||
slot_index: 0
|
||||
}
|
||||
],
|
||||
title: "EmptyLatentImage",
|
||||
properties: {},
|
||||
widgets_values: [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: "KSampler",
|
||||
pos: [
|
||||
863,
|
||||
186
|
||||
],
|
||||
size: [
|
||||
315,
|
||||
262
|
||||
],
|
||||
flags: {},
|
||||
order: 4,
|
||||
mode: 0,
|
||||
inputs: [
|
||||
{
|
||||
name: "model",
|
||||
type: "MODEL",
|
||||
link: 1
|
||||
},
|
||||
{
|
||||
name: "positive",
|
||||
type: "CONDITIONING",
|
||||
link: 4
|
||||
},
|
||||
{
|
||||
name: "negative",
|
||||
type: "CONDITIONING",
|
||||
link: 6
|
||||
},
|
||||
{
|
||||
name: "latent_image",
|
||||
type: "LATENT",
|
||||
link: 2
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
name: "LATENT",
|
||||
type: "LATENT",
|
||||
links: [
|
||||
7
|
||||
],
|
||||
slot_index: 0
|
||||
}
|
||||
],
|
||||
title: "KSampler",
|
||||
properties: {},
|
||||
widgets_values: [
|
||||
8566257,
|
||||
"randomize",
|
||||
8,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
type: "VAEDecode",
|
||||
pos: [
|
||||
1209,
|
||||
188
|
||||
],
|
||||
size: [
|
||||
210,
|
||||
46
|
||||
],
|
||||
flags: {},
|
||||
order: 5,
|
||||
mode: 0,
|
||||
inputs: [
|
||||
{
|
||||
name: "samples",
|
||||
type: "LATENT",
|
||||
link: 7
|
||||
},
|
||||
{
|
||||
name: "vae",
|
||||
type: "VAE",
|
||||
link: 8
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
name: "IMAGE",
|
||||
type: "IMAGE",
|
||||
links: [
|
||||
9
|
||||
],
|
||||
slot_index: 0
|
||||
}
|
||||
],
|
||||
title: "VAEDecode",
|
||||
properties: {}
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
type: "SaveImage",
|
||||
pos: [
|
||||
1451,
|
||||
189
|
||||
],
|
||||
size: [
|
||||
210,
|
||||
82
|
||||
],
|
||||
flags: {},
|
||||
order: 6,
|
||||
mode: 0,
|
||||
inputs: [
|
||||
{
|
||||
name: "images",
|
||||
type: "IMAGE",
|
||||
link: 9
|
||||
}
|
||||
],
|
||||
outputs: [],
|
||||
title: "SaveImage",
|
||||
properties: {},
|
||||
widgets_values: [
|
||||
[],
|
||||
"ComfyUI"
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: "CheckpointLoaderSimple",
|
||||
pos: [
|
||||
26,
|
||||
474
|
||||
],
|
||||
size: [
|
||||
315,
|
||||
98
|
||||
],
|
||||
flags: {},
|
||||
order: 1,
|
||||
mode: 0,
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
name: "MODEL",
|
||||
type: "MODEL",
|
||||
links: [
|
||||
1
|
||||
],
|
||||
slot_index: 0
|
||||
},
|
||||
{
|
||||
name: "CLIP",
|
||||
type: "CLIP",
|
||||
links: [
|
||||
3,
|
||||
5
|
||||
],
|
||||
slot_index: 1
|
||||
},
|
||||
{
|
||||
name: "VAE",
|
||||
type: "VAE",
|
||||
links: [
|
||||
8
|
||||
],
|
||||
slot_index: 2
|
||||
}
|
||||
],
|
||||
title: "CheckpointLoaderSimple",
|
||||
properties: {},
|
||||
widgets_values: [
|
||||
"v1-5-pruned-emaonly.ckpt"
|
||||
]
|
||||
}
|
||||
],
|
||||
links: [
|
||||
[
|
||||
1,
|
||||
4,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
2,
|
||||
5,
|
||||
0,
|
||||
3,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
3,
|
||||
4,
|
||||
1,
|
||||
6,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
4,
|
||||
6,
|
||||
0,
|
||||
3,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
5,
|
||||
4,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
6,
|
||||
7,
|
||||
0,
|
||||
3,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
7,
|
||||
3,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
8,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
9,
|
||||
8,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
last_node_id: 0,
|
||||
last_link_id: 0,
|
||||
nodes: [],
|
||||
links: [],
|
||||
groups: [],
|
||||
config: {},
|
||||
extra: {},
|
||||
version: 10
|
||||
version: 0
|
||||
},
|
||||
panes: {
|
||||
panels: [
|
||||
[
|
||||
{
|
||||
nodeId: 7
|
||||
},
|
||||
{
|
||||
nodeId: 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
nodeId: 6
|
||||
},
|
||||
{
|
||||
nodeId: 9
|
||||
},
|
||||
{
|
||||
nodeId: 4
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
nodeId: 5
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
panes: {}
|
||||
}
|
||||
|
||||
export default defaultGraph;
|
||||
|
||||
@@ -17,7 +17,7 @@ export interface ComfyWidgetProperties extends Record<string, any> {
|
||||
* widget is changed, the value of the first output in the node is updated
|
||||
* in the litegraph instance.
|
||||
*/
|
||||
export abstract class ComfyWidgetNode<T> extends ComfyGraphNode {
|
||||
export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
abstract properties: ComfyWidgetProperties;
|
||||
|
||||
value: Writable<T>
|
||||
@@ -134,7 +134,7 @@ export class ComfySliderNode extends ComfyWidgetNode<number> {
|
||||
this.setProperty("min", Math.max(this.properties.min, input.config.min))
|
||||
this.setProperty("max", Math.max(this.properties.max, input.config.max))
|
||||
this.setProperty("step", Math.max(this.properties.step, input.config.step))
|
||||
this.setProperty("value", Math.max(Math.min(this.properties.value, this.properties.max), this.properties.min))
|
||||
this.setValue(Math.max(Math.min(this.properties.value, this.properties.max), this.properties.min))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ export interface ComfyComboProperties extends ComfyWidgetProperties {
|
||||
|
||||
export class ComfyComboNode extends ComfyWidgetNode<string> {
|
||||
override properties: ComfyComboProperties = {
|
||||
options: ["*"]
|
||||
options: ["A", "B", "C", "D"]
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
@@ -164,7 +164,7 @@ export class ComfyComboNode extends ComfyWidgetNode<string> {
|
||||
override inputWidgetTypes = ["combo", "enum"]
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name, "*")
|
||||
super(name, "A")
|
||||
}
|
||||
|
||||
onConnectOutput(
|
||||
@@ -194,9 +194,9 @@ export class ComfyComboNode extends ComfyWidgetNode<string> {
|
||||
override clampOneConfig(input: IComfyInputSlot) {
|
||||
if (!(this.properties.value in input.config.values)) {
|
||||
if (input.config.values.length === 0)
|
||||
this.setProperty("value", "")
|
||||
this.setValue("")
|
||||
else
|
||||
this.setProperty("value", input.config.values[0])
|
||||
this.setValue(input.config.values[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import { get, writable } from 'svelte/store';
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
import type ComfyApp from "$lib/components/ComfyApp"
|
||||
import type { LGraphNode, IWidget, LGraph } from "@litegraph-ts/core"
|
||||
import nodeState from "$lib/state/nodeState";
|
||||
import type { NodeStateStore } from './nodeState';
|
||||
import { dndzone, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
||||
import type { ComfyWidgetNode } from '$lib/nodes';
|
||||
|
||||
@@ -90,8 +88,8 @@ export interface WidgetLayout extends IDragItem {
|
||||
type DragItemID = string;
|
||||
|
||||
type LayoutStateOps = {
|
||||
addContainer: (parentId: DragItemID, attrs: Partial<Attributes>, index: number) => ContainerLayout,
|
||||
addWidget: (parentId: DragItemID, node: LGraphNode, widget: IWidget<any, any>, attrs: Partial<Attributes>, index: number) => WidgetLayout,
|
||||
addContainer: (parent: ContainerLayout | null, attrs: Partial<Attributes>, index: number) => ContainerLayout,
|
||||
addWidget: (parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partial<Attributes>, index: number) => WidgetLayout,
|
||||
findDefaultContainerForInsertion: () => ContainerLayout | null,
|
||||
updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[],
|
||||
nodeAdded: (node: LGraphNode) => void,
|
||||
@@ -100,7 +98,7 @@ type LayoutStateOps = {
|
||||
groupItems: (dragItems: IDragItem[]) => ContainerLayout,
|
||||
ungroup: (container: ContainerLayout) => void,
|
||||
getCurrentSelection: () => IDragItem[],
|
||||
clear: () => void,
|
||||
clear: (state?: Partial<LayoutState>) => void,
|
||||
resetLayout: () => void,
|
||||
}
|
||||
|
||||
@@ -135,7 +133,7 @@ function findDefaultContainerForInsertion(): ContainerLayout | null {
|
||||
return null
|
||||
}
|
||||
|
||||
function addContainer(parentId: DragItemID | null, attrs: Partial<Attributes> = {}, index: number = -1): ContainerLayout {
|
||||
function addContainer(parent: ContainerLayout | null, attrs: Partial<Attributes> = {}, index: number = -1): ContainerLayout {
|
||||
const state = get(store);
|
||||
const dragItem: ContainerLayout = {
|
||||
type: "container",
|
||||
@@ -148,15 +146,10 @@ function addContainer(parentId: DragItemID | null, attrs: Partial<Attributes> =
|
||||
...attrs
|
||||
}
|
||||
}
|
||||
const parent = parentId ? state.allItems[parentId] : null;
|
||||
const entry: DragItemEntry = { dragItem, children: [], parent: parent?.dragItem };
|
||||
const entry: DragItemEntry = { dragItem, children: [], parent: null };
|
||||
state.allItems[dragItem.id] = entry;
|
||||
if (parent) {
|
||||
parent.children ||= []
|
||||
if (index)
|
||||
parent.children.splice(index, 0, dragItem)
|
||||
else
|
||||
parent.children.push(dragItem)
|
||||
moveItem(dragItem, parent)
|
||||
}
|
||||
store.set(state)
|
||||
return dragItem;
|
||||
@@ -178,7 +171,7 @@ function addWidget(parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partia
|
||||
}
|
||||
}
|
||||
const parentEntry = state.allItems[parent.id]
|
||||
const entry: DragItemEntry = { dragItem, children: [], parent: parentEntry.dragItem };
|
||||
const entry: DragItemEntry = { dragItem, children: [], parent: null };
|
||||
state.allItems[dragItem.id] = entry;
|
||||
moveItem(dragItem, parent)
|
||||
return dragItem;
|
||||
@@ -209,6 +202,7 @@ function nodeAdded(node: LGraphNode) {
|
||||
// 2. User adds a node with inputs that can be filled by frontend widgets.
|
||||
// Depending on config, this means we should instantiate default UI nodes connected to those inputs.
|
||||
|
||||
console.debug(node)
|
||||
if ("svelteComponentType" in node) {
|
||||
addWidget(parent, node as ComfyWidgetNode);
|
||||
}
|
||||
@@ -254,21 +248,25 @@ function nodeRemoved(node: LGraphNode) {
|
||||
}
|
||||
|
||||
function configureFinished(graph: LGraph) {
|
||||
const state = get(store)
|
||||
const id = 0;
|
||||
|
||||
state.isConfiguring = false;
|
||||
clear({isConfiguring: false})
|
||||
|
||||
state.root = addContainer(null, { direction: "horizontal", showTitle: false });
|
||||
const left = addContainer(state.root.id, { direction: "vertical", showTitle: false });
|
||||
const right = addContainer(state.root.id, { direction: "vertical", showTitle: false });
|
||||
const root = addContainer(null, { direction: "horizontal", showTitle: false });
|
||||
const left = addContainer(root, { direction: "vertical", showTitle: false });
|
||||
const right = addContainer(root, { direction: "vertical", showTitle: false });
|
||||
|
||||
for (const node of graph._nodes_in_order) {
|
||||
const state = get(store)
|
||||
state.root = root;
|
||||
store.set(state)
|
||||
|
||||
console.debug("[layoutState] configure begin", state, graph)
|
||||
|
||||
for (const node of graph._nodes) {
|
||||
nodeAdded(node)
|
||||
}
|
||||
|
||||
console.debug("[layoutState] configureFinished", state)
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function moveItem(target: IDragItem, to: ContainerLayout, index: number = -1) {
|
||||
@@ -324,7 +322,7 @@ function groupItems(dragItems: IDragItem[]): ContainerLayout {
|
||||
index = indexFound
|
||||
}
|
||||
|
||||
const container = addContainer(parent.id, { title: "Group" }, index)
|
||||
const container = addContainer(parent as ContainerLayout, { title: "Group" }, index)
|
||||
|
||||
for (const item of dragItems) {
|
||||
moveItem(item, container)
|
||||
@@ -364,7 +362,7 @@ function ungroup(container: ContainerLayout) {
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function clear() {
|
||||
function clear(state: Partial<LayoutState> = {}) {
|
||||
store.set({
|
||||
root: null,
|
||||
allItems: {},
|
||||
@@ -372,8 +370,8 @@ function clear() {
|
||||
currentSelection: [],
|
||||
isMenuOpen: false,
|
||||
isConfiguring: true,
|
||||
...state
|
||||
})
|
||||
addContainer(null, { direction: "horizontal", showTitle: false });
|
||||
}
|
||||
|
||||
function resetLayout() {
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
import type { LGraph, LGraphNode, IWidget } from "@litegraph-ts/core";
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
import type ComfyGraphNode from '$lib/nodes/ComfyGraphNode';
|
||||
import type ComfyWidget from '$lib/widgets/ComfyWidget';
|
||||
|
||||
/** store for one node's state */
|
||||
export type NodeUIStateStore = Writable<any>
|
||||
|
||||
export type NodeUIState = {
|
||||
name: string,
|
||||
node: LGraphNode,
|
||||
widgetStates: WidgetUIState[]
|
||||
}
|
||||
|
||||
export type WidgetDrawState = {
|
||||
isNodeExecuting: boolean
|
||||
}
|
||||
|
||||
/** store for one widget's state */
|
||||
export type WidgetUIStateStore = Writable<any>
|
||||
|
||||
export type WidgetUIState = {
|
||||
/** position in the node's list of widgets */
|
||||
index: number,
|
||||
/** parent node containing the widget */
|
||||
node: LGraphNode,
|
||||
/** actual widget instance */
|
||||
widget: IWidget,
|
||||
/** widget value as a store, to react to changes */
|
||||
value: WidgetUIStateStore,
|
||||
/**
|
||||
* true if this widget was added purely from the frontend. what this means:
|
||||
* - this widget's state will not be saved to the workflow
|
||||
* - the widget was added on startup by some subclass of ComfyGraphNode
|
||||
*/
|
||||
isVirtual: boolean
|
||||
}
|
||||
|
||||
export type NodeID = number;
|
||||
|
||||
type NodeStateOps = {
|
||||
nodeAdded: (node: LGraphNode) => void,
|
||||
nodeRemoved: (node: LGraphNode) => void,
|
||||
configureFinished: (graph: LGraph) => void,
|
||||
nodeStateChanged: (node: LGraphNode) => void,
|
||||
widgetStateChanged: (nodeId: number, widget: IWidget<any, any>) => void,
|
||||
findWidgetByName: (nodeId: number, name: string) => WidgetUIState | null,
|
||||
clear: () => void,
|
||||
}
|
||||
|
||||
export type NodeStateStore = Record<NodeID, NodeUIState>;
|
||||
type WritableNodeStateStore = Writable<NodeStateStore> & NodeStateOps;
|
||||
|
||||
const store: Writable<NodeStateStore> = writable({})
|
||||
|
||||
function clear() {
|
||||
store.set({})
|
||||
}
|
||||
|
||||
function nodeAdded(node: LGraphNode) {
|
||||
let state = get(store)
|
||||
|
||||
const widgets = [];
|
||||
|
||||
// if (node.widgets) {
|
||||
// for (const [index, widget] of node.widgets.entries()) {
|
||||
// let isVirtual = false;
|
||||
// if ("isVirtual" in widget)
|
||||
// isVirtual = (widget as ComfyWidget<any, any>).isVirtual;
|
||||
// widgets.push({ index, widget, value: writable(widget.value), isVirtual: isVirtual })
|
||||
// }
|
||||
// }
|
||||
|
||||
state[node.id] = { node: node, name: node.title, widgetStates: widgets }
|
||||
store.set(state);
|
||||
|
||||
console.debug("call nodeAdded", state[node.id])
|
||||
}
|
||||
|
||||
function nodeRemoved(node: LGraphNode) {
|
||||
const state = get(store)
|
||||
delete state[node.id]
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function nodeStateChanged(node: LGraphNode) {
|
||||
const state = get(store)
|
||||
const nodeState = state[node.id]
|
||||
nodeState.name = node.title
|
||||
store.set(state);
|
||||
}
|
||||
|
||||
function configureFinished(graph: LGraph) {
|
||||
let state = get(store);
|
||||
|
||||
for (const node of graph.computeExecutionOrder(false, null)) {
|
||||
state[node.id].name = node.title;
|
||||
|
||||
// const widgetStates = state[node.id].widgetStates;
|
||||
// if (node.widgets_values) {
|
||||
// for (const [i, value] of node.widgets_values.entries()) {
|
||||
// if (i < widgetStates.length && !widgetStates[i].isVirtual) {
|
||||
// widgetStates[i].value.set(value);
|
||||
// }
|
||||
// else {
|
||||
// console.log("Skip virtual widget", node.id, node.type, widgetStates[i].widget)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function widgetStateChanged(nodeId: number, widget: IWidget<any, any>) {
|
||||
const state = get(store)
|
||||
const entries = state[nodeId].widgetStates
|
||||
if (entries) {
|
||||
let widgetState = entries.find(e => e.widget === widget);
|
||||
if (widgetState) {
|
||||
widgetState.value.set(widget.value);
|
||||
store.set(state);
|
||||
}
|
||||
else {
|
||||
console.error("Widget state changed and node was found, but widget was not found in state!", widget, state[nodeId].node, entries)
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error("Widget state changed but node was not found in state!", widget, state[nodeId].node)
|
||||
}
|
||||
}
|
||||
|
||||
function findWidgetByName(nodeId: NodeID, name: string): WidgetUIState | null {
|
||||
let state = get(store);
|
||||
|
||||
if (!(nodeId in state))
|
||||
return null;
|
||||
|
||||
return state[nodeId].widgetStates.find((v) => v.widget.name === name);
|
||||
}
|
||||
|
||||
const nodeStateStore: WritableNodeStateStore =
|
||||
{
|
||||
...store,
|
||||
nodeAdded,
|
||||
nodeRemoved,
|
||||
nodeStateChanged,
|
||||
configureFinished,
|
||||
widgetStateChanged,
|
||||
findWidgetByName,
|
||||
clear
|
||||
}
|
||||
export default nodeStateStore;
|
||||
@@ -14,7 +14,8 @@ export type UIState = {
|
||||
export type WritableUIStateStore = Writable<UIState>;
|
||||
const store: WritableUIStateStore = writable(
|
||||
{
|
||||
graphLocked: true,
|
||||
app: null,
|
||||
graphLocked: false,
|
||||
nodesLocked: false,
|
||||
uiEditMode: "disabled",
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@ import ComfyApp from "./components/ComfyApp";
|
||||
import ComboWidget from "$lib/widgets/ComboWidget.svelte";
|
||||
import RangeWidget from "$lib/widgets/RangeWidget.svelte";
|
||||
import TextWidget from "$lib/widgets/TextWidget.svelte";
|
||||
import { type WidgetUIState } from "$lib/stores/nodeState";
|
||||
import { get } from "svelte/store"
|
||||
import layoutState from "$lib/stores/layoutState"
|
||||
import type { SvelteComponentDev } from "svelte/internal";
|
||||
@@ -21,26 +20,6 @@ export function download(filename: string, text: string, type: string = "text/pl
|
||||
}, 0);
|
||||
}
|
||||
|
||||
export function getComponentForWidgetState(item: WidgetUIState): typeof SvelteComponentDev {
|
||||
// custom widgets with TypeScript sources
|
||||
let override = ComfyApp.widget_type_overrides[item.widget.type]
|
||||
if (override) {
|
||||
return override
|
||||
}
|
||||
|
||||
// litegraph.ts built-in widgets
|
||||
switch (item.widget.type) {
|
||||
case "combo":
|
||||
return ComboWidget;
|
||||
case "number":
|
||||
return RangeWidget;
|
||||
case "text":
|
||||
return TextWidget;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function startDrag(evt: MouseEvent) {
|
||||
const dragItemId: string = evt.target.dataset["dragItemId"];
|
||||
const ls = get(layoutState)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { WidgetDrawState, WidgetUIState, WidgetUIStateStore } from "$lib/stores/nodeState";
|
||||
import { BlockTitle } from "@gradio/atoms";
|
||||
import { Dropdown } from "@gradio/form";
|
||||
import Select from 'svelte-select';
|
||||
import type { ComfyComboNode } from "$lib/nodes/index";
|
||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
||||
@@ -9,32 +7,35 @@
|
||||
export let widget: WidgetLayout | null = null;
|
||||
let node: ComfyComboNode | null = null;
|
||||
let nodeValue: Writable<string> | null = null;
|
||||
let itemValue: WidgetUIStateStore | null = null;
|
||||
let option: any;
|
||||
let option: any
|
||||
|
||||
$: if(widget) {
|
||||
node = widget.node as ComfyComboNode
|
||||
nodeValue = node.value;
|
||||
updateOption(); // don't react on option
|
||||
};
|
||||
$: widget && setNodeValue(widget);
|
||||
|
||||
function updateOption() {
|
||||
option = get(nodeValue);
|
||||
function setNodeValue(widget: WidgetLayout) {
|
||||
if(widget && !node) {
|
||||
node = widget.node as ComfyComboNode
|
||||
nodeValue = node.value;
|
||||
setOption($nodeValue) // don't react on option
|
||||
}
|
||||
}
|
||||
|
||||
$: if (option && itemValue) {
|
||||
$itemValue = option.value
|
||||
function setOption(value: any) {
|
||||
option = value;
|
||||
}
|
||||
|
||||
$: if (nodeValue && option) {
|
||||
$nodeValue = option.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper gr-combo">
|
||||
{#if node !== null && option !== undefined}
|
||||
{#if node !== null && nodeValue !== null}
|
||||
<label>
|
||||
<BlockTitle show_label={true}>{widget.attrs.title}</BlockTitle>
|
||||
<Select
|
||||
bind:value={option}
|
||||
bind:items={node.properties.values}
|
||||
disabled={node.properties.values.length === 0}
|
||||
bind:items={node.properties.options}
|
||||
disabled={node.properties.options.length === 0}
|
||||
clearable={false}
|
||||
on:change
|
||||
on:select
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import type { WidgetUIState, WidgetUIStateStore } from "$lib/stores/nodeState";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { Gallery } from "@gradio/gallery";
|
||||
import type { Styles } from "@gradio/utils";
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { IEnumWidget, IEnumWidgetOptions, INumberWidget, LGraphNode, Widget
|
||||
import ComfyWidget from "./ComfyWidget";
|
||||
import type { ComfyImageResult } from "$lib/nodes/ComfySaveImageNode";
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import nodeState from "$lib/stores/nodeState"
|
||||
|
||||
export interface ComfyValueControlWidgetOptions extends IEnumWidgetOptions {
|
||||
}
|
||||
@@ -50,6 +49,6 @@ export default class ComfyValueControlWidget extends ComfyWidget<ComfyValueContr
|
||||
if (this.targetWidget.value > max)
|
||||
this.targetWidget.value = max;
|
||||
|
||||
nodeState.widgetStateChanged(this.node.id, this.targetWidget);
|
||||
// nodeState.widgetStateChanged(this.node.id, this.targetWidget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import type { IWidget, LGraphNode, SerializedLGraphNode, Vector2, WidgetCallback, WidgetTypes } from "@litegraph-ts/core";
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
|
||||
export default abstract class ComfyWidget<T = any, V = any> implements IWidget<T, V> {
|
||||
name: string;
|
||||
@@ -27,7 +26,6 @@ export default abstract class ComfyWidget<T = any, V = any> implements IWidget<T
|
||||
|
||||
setValue(value: V) {
|
||||
this.value = value;
|
||||
nodeState.widgetStateChanged(this.node.id, this);
|
||||
}
|
||||
|
||||
draw?(ctx: CanvasRenderingContext2D, node: LGraphNode, width: number, posY: number, height: number): void;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import type { WidgetUIStateStore } from "$lib/stores/nodeState";
|
||||
import { TextBox } from "@gradio/form";
|
||||
import type { ComfyComboNode } from "$lib/nodes/index";
|
||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
||||
@@ -18,7 +17,7 @@
|
||||
<div class="wrapper gr-textbox">
|
||||
{#if node !== null && nodeValue !== null}
|
||||
<TextBox
|
||||
bind:value={$itemValue}
|
||||
bind:value={$nodeValue}
|
||||
label={widget.attrs.title}
|
||||
lines={node.properties.multiline ? 5 : 1}
|
||||
max_lines={node.properties.multiline ? 5 : 1}
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { get } from "svelte/store";
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import { download } from "$lib/utils"
|
||||
|
||||
import { LGraph, LGraphNode } from "@litegraph-ts/core";
|
||||
import type { ComfyAPIStatus } from "$lib/api";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem, Toolbar } from "framework7-svelte"
|
||||
import { getComponentForWidgetState } from "$lib/utils"
|
||||
import { Link, Toolbar } from "framework7-svelte"
|
||||
import { f7 } from "framework7-svelte"
|
||||
|
||||
export let subworkflowID: number = -1;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import { download } from "$lib/utils"
|
||||
@@ -46,11 +45,6 @@
|
||||
return
|
||||
app = $uiState.app = new ComfyApp();
|
||||
|
||||
app.eventBus.on("nodeAdded", nodeState.nodeAdded);
|
||||
app.eventBus.on("nodeRemoved", nodeState.nodeRemoved);
|
||||
app.eventBus.on("configured", nodeState.configureFinished);
|
||||
app.eventBus.on("cleared", nodeState.clear);
|
||||
|
||||
app.eventBus.on("autosave", doAutosave);
|
||||
app.eventBus.on("restored", doRestore);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import { download } from "$lib/utils"
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { get } from "svelte/store";
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import { download } from "$lib/utils"
|
||||
|
||||
import { LGraph, LGraphNode } from "@litegraph-ts/core";
|
||||
import type { ComfyAPIStatus } from "$lib/api";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem, Toolbar } from "framework7-svelte"
|
||||
import { getComponentForWidgetState } from "$lib/utils"
|
||||
import { f7 } from "framework7-svelte"
|
||||
|
||||
export let subworkflowID: number = -1;
|
||||
let app: ComfyApp = undefined;
|
||||
@@ -28,21 +16,7 @@
|
||||
<Page name="subworkflow">
|
||||
<Navbar title="Workflow {subworkflowID}" backLink="Back" />
|
||||
|
||||
{#each Object.entries($nodeState) as [id, ws]}
|
||||
{@const node = app.lGraph.getNodeById(id)}
|
||||
<div class:is-executing={$queueState.runningNodeId === node.id}>
|
||||
<Block>
|
||||
<label for={String(id)}>
|
||||
<BlockTitle>
|
||||
{node.title}
|
||||
</BlockTitle>
|
||||
</label>
|
||||
{#each $nodeState[id].widgetStates as item}
|
||||
<svelte:component this={getComponentForWidgetState(item)} {item} />
|
||||
{/each}
|
||||
</Block>
|
||||
</div>
|
||||
{/each}
|
||||
<div>Workflow!</div>
|
||||
</Page>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user